+32
-6
@@ -22,6 +22,8 @@ import AdminSetup from "./views/AdminSetup";
|
||||
import Admin from "./views/Admin";
|
||||
import Docs from "./views/Docs";
|
||||
import Introduction from "./views/Introduction";
|
||||
import SetAuthentication from "./views/SetAuthentication";
|
||||
import SetAuthenticationSSO from "./views/SetAuthenticationSSO";
|
||||
|
||||
import LandingPageNew from "./views/LandingpageNew";
|
||||
import LoginPage from "./views/LoginPage";
|
||||
@@ -40,14 +42,15 @@ import {isMobile} from "react-device-detect";
|
||||
var globalUrl = window.location.origin
|
||||
|
||||
// CORS used for testing purposes. Should only happen with specific port and http
|
||||
if (window.location.protocol == "http:" && window.location.port === "3000") {
|
||||
if ( window.location.port === "3000") {
|
||||
globalUrl = "http://localhost:5001"
|
||||
//globalUrl = "http://localhost:5002"
|
||||
}
|
||||
|
||||
const App = (message, props) => {
|
||||
const [userdata, setUserData] = useState({});
|
||||
const [cookies, setCookie, removeCookie] = useCookies([]);
|
||||
const [notifications, setNotifications] = useState([])
|
||||
const [cookies, setCookie, removeCookie] = useCookies([])
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
const [dataset, setDataset] = useState(false);
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
@@ -55,6 +58,7 @@ const App = (message, props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (dataset === false) {
|
||||
getUserNotifications()
|
||||
checkLogin()
|
||||
setDataset(true)
|
||||
}
|
||||
@@ -64,6 +68,25 @@ const App = (message, props) => {
|
||||
window.location = "/login"
|
||||
}
|
||||
|
||||
const getUserNotifications = () => {
|
||||
fetch(`${globalUrl}/api/v1/notifications`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(responseJson => {
|
||||
if (responseJson.success === true && responseJson.notifications !== null && responseJson.notifications !== undefined && responseJson.notifications.length > 0) {
|
||||
//console.log("RESP: ", responseJson)
|
||||
setNotifications(responseJson.notifications)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("Failed getting notifications for user: ", error)
|
||||
});
|
||||
}
|
||||
|
||||
const checkLogin = () => {
|
||||
var baseurl = globalUrl
|
||||
fetch(baseurl + "/api/v1/users/getinfo", {
|
||||
@@ -75,7 +98,7 @@ const App = (message, props) => {
|
||||
.then(response => response.json())
|
||||
.then(responseJson => {
|
||||
if (responseJson.success === true) {
|
||||
//console.log(responseJson.success)
|
||||
console.log(responseJson)
|
||||
setUserData(responseJson)
|
||||
setIsLoggedIn(true)
|
||||
//console.log("Cookies: ", cookies)
|
||||
@@ -104,9 +127,10 @@ 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" }}>
|
||||
<ScrollToTop setCurpath={setCurpath} />
|
||||
<Header cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} userdata={userdata} {...props} />
|
||||
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<ScrollToTop getUserNotifications={getUserNotifications} setCurpath={setCurpath} />
|
||||
<Header notifications={notifications} setNotifications={setNotifications} checkLogin={checkLogin} cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} userdata={userdata} {...props} />
|
||||
<div style={{height: 60}}/>
|
||||
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} checkLogin={checkLogin} {...props} />} />
|
||||
<Route exact path="/admin" render={props => <Admin userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/admin/:key" render={props => <Admin isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/settings" render={props => <SettingsPage isLoaded={isLoaded} userdata={userdata} globalUrl={globalUrl} {...props} />} />
|
||||
@@ -125,6 +149,8 @@ const App = (message, props) => {
|
||||
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
|
||||
<Route exact path="/introduction" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/introduction/:key" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||
<Route exact path="/set_authentication" render={props => <SetAuthentication userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/login_sso" render={props => <SetAuthenticationSSO userdata={userdata} isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
<Route exact path="/" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const alertStyle = {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
boxShadow: '0px 2px 2px 2px rgba(0, 0, 0, 0.03)',
|
||||
width: 400,
|
||||
width: 300,
|
||||
boxSizing: 'border-box',
|
||||
zIndex: 100001,
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -11,8 +11,8 @@ import { FixName } from "../views/Apps.jsx";
|
||||
// Triggers
|
||||
//
|
||||
// Specifically used for UNSAVED workflows only?
|
||||
const Workflow = (props) => {
|
||||
const { globalUrl, theme, workflow, appAuthentication, setSelectedAction, setAuthenticationModalOpen, setSelectedApp, apps, selectedAction,setConfigureWorkflowModalOpen, saveWorkflow, newWebhook, submitSchedule, referenceUrl, isCloud, } = props
|
||||
const ConfigureWorkflow = (props) => {
|
||||
const { globalUrl, theme, workflow, appAuthentication, setSelectedAction, setAuthenticationModalOpen, setSelectedApp, apps, selectedAction,setConfigureWorkflowModalOpen, saveWorkflow, newWebhook, submitSchedule, referenceUrl, isCloud, setAuthenticationType, alert, } = props
|
||||
const [requiredActions, setRequiredActions] = React.useState([])
|
||||
const [requiredVariables, setRequiredVariables] = React.useState([])
|
||||
const [requiredTriggers, setRequiredTriggers] = React.useState([])
|
||||
@@ -90,7 +90,7 @@ const Workflow = (props) => {
|
||||
|
||||
const app = apps.find(app => app.name === action.app_name && (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version))))
|
||||
if (app === undefined || app === null) {
|
||||
console.log("App not found!")
|
||||
console.log("App not found: ", action.app_name)
|
||||
|
||||
newaction.must_activate = true
|
||||
} else {
|
||||
@@ -374,6 +374,17 @@ const Workflow = (props) => {
|
||||
<CircularProgress />
|
||||
:
|
||||
<Button color="primary" variant="contained" onClick={() => {
|
||||
setAuthenticationType(action.app.authentication.type === "oauth2" && action.app.authentication.redirect_uri !== undefined && action.app.authentication.redirect_uri !== null ?
|
||||
{
|
||||
"type": "oauth2",
|
||||
"redirect_uri": action.app.authentication.redirect_uri,
|
||||
"token_uri": action.app.authentication.token_uri,
|
||||
"scope": action.app.authentication.scope,
|
||||
} : {
|
||||
"type": ""
|
||||
}
|
||||
)
|
||||
|
||||
setItemChanged(true)
|
||||
setSelectedAction(action.action)
|
||||
setSelectedApp(action.app)
|
||||
@@ -384,8 +395,8 @@ const Workflow = (props) => {
|
||||
:
|
||||
null}
|
||||
{action.must_activate ?
|
||||
<Button disabled={true} color="primary" variant="contained" onClick={() => {
|
||||
console.log("SHOULD ACTIVATE: ", action)
|
||||
<Button color="primary" variant="contained" onClick={() => {
|
||||
activateApp(action.app_id, action.app_name, action.app_version)
|
||||
setItemChanged(true)
|
||||
}}>
|
||||
Activate
|
||||
@@ -395,6 +406,35 @@ const Workflow = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
const activateApp = (app_id, app_name, app_version) => {
|
||||
fetch(`${globalUrl}/api/v1/apps/app_id/activate?app_name=${app_name}&app_version=${app_version}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
//window.location.pathname = "/search"
|
||||
//alert.error("Failed to find this app. Is it public?")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
alert.error("Failed to activate the app")
|
||||
} else {
|
||||
alert.success("App activated for your organization!")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h6">{workflow.name}</Typography>
|
||||
@@ -467,4 +507,4 @@ const Workflow = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default Workflow
|
||||
export default ConfigureWorkflow
|
||||
|
||||
@@ -3,26 +3,18 @@ import {BrowserView, MobileView} from "react-device-detect";
|
||||
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import List from '@material-ui/core/List';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import PolymerIcon from '@material-ui/icons/Polymer';
|
||||
import AppsIcon from '@material-ui/icons/Apps';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
|
||||
import { Chip, Badge, Typography, Paper, Tooltip, List, Avatar, Menu, ListItem, MenuItem, Select, Button, IconButton, Grid } from '@material-ui/core';
|
||||
import { MeetingRoom as MeetingRoomIcon, Settings as SettingsIcon, Notifications as NotificationsIcon, Home as HomeIcon, Polymer as PolymerIcon, Apps as AppsIcon, Description as DescriptionIcon} from '@material-ui/icons';
|
||||
//import LogoutIcon from '@mui/icons-material/Logout';
|
||||
import { useAlert } from "react-alert";
|
||||
|
||||
const hoverColor = "#f85a3e"
|
||||
const hoverOutColor = "#e8eaf6"
|
||||
|
||||
const Header = props => {
|
||||
const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded, userdata, cookies } = props;
|
||||
const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, homePage, isLoaded, userdata, cookies } = props;
|
||||
const theme = useTheme();
|
||||
|
||||
const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
|
||||
@@ -31,12 +23,78 @@ const Header = props => {
|
||||
const [DocsHoverColor, setDocsHoverColor] = useState(hoverOutColor);
|
||||
const [HelpHoverColor, setHelpHoverColor] = useState(hoverOutColor);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const [anchorElAvatar, setAnchorElAvatar] = React.useState(null);
|
||||
const alert = useAlert()
|
||||
|
||||
const hrefStyle = {
|
||||
color: hoverOutColor,
|
||||
textDecoration: "none",
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
setAnchorElAvatar(null);
|
||||
};
|
||||
|
||||
const clearNotifications = () => {
|
||||
// Don't really care about the logout
|
||||
fetch(`${globalUrl}/api/v1/notifications/clear`, {
|
||||
credentials: "include",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.status !== 200) {
|
||||
console.log("Error in response")
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}).then(function(responseJson) {
|
||||
if (responseJson.success === true) {
|
||||
setNotifications([])
|
||||
handleClose()
|
||||
} else {
|
||||
alert.error("Failed dismissing notifications. Please try again later.")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error in notification dismissal: ", error)
|
||||
//removeCookie("session_token", {path: "/"})
|
||||
})
|
||||
}
|
||||
|
||||
const dismissNotification = (alert_id) => {
|
||||
// Don't really care about the logout
|
||||
fetch(`${globalUrl}/api/v1/notifications/${alert_id}/markasread`, {
|
||||
credentials: "include",
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.status !== 200) {
|
||||
console.log("Error in response")
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}).then(function(responseJson) {
|
||||
if (responseJson.success === true) {
|
||||
const newNotifications = notifications.filter(data => data.id !== alert_id)
|
||||
console.log("NEW NOTIFICATIONS: ", newNotifications)
|
||||
setNotifications(newNotifications)
|
||||
} else {
|
||||
alert.error("Failed dismissing notification. Please try again later.")
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error in notification dismissal: ", error)
|
||||
//removeCookie("session_token", {path: "/"})
|
||||
})
|
||||
}
|
||||
|
||||
// DEBUG HERE
|
||||
const handleClickLogout = () => {
|
||||
console.log("COOKIES: ", cookies, "Remover: ", removeCookie)
|
||||
@@ -65,6 +123,47 @@ const Header = props => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleClickChangeOrg = (orgId) => {
|
||||
// Don't really care about the logout
|
||||
//name: org.name,
|
||||
//orgId = "asd"
|
||||
const data = {
|
||||
org_id: orgId,
|
||||
}
|
||||
|
||||
fetch(`${globalUrl}/api/v1/orgs/${orgId}/change`, {
|
||||
mode: 'cors',
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
credentials: 'include',
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.status !== 200) {
|
||||
console.log("Error in response")
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}).then(function(responseJson) {
|
||||
if (responseJson.success !== undefined && responseJson.success) {
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 2000)
|
||||
alert.success("Successfully changed active organization - refreshing!")
|
||||
} else {
|
||||
alert.error("Failed changing org: ", responseJson.reason)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("error changing: ", error)
|
||||
//removeCookie("session_token", {path: "/"})
|
||||
})
|
||||
}
|
||||
|
||||
// Rofl this is weird
|
||||
const handleDocsHover = () => {
|
||||
setDocsHoverColor(hoverColor)
|
||||
@@ -111,25 +210,129 @@ const Header = props => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const chipStyle = {
|
||||
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
|
||||
}
|
||||
|
||||
const notificationWidth = 350
|
||||
const NotificationItem = (props) => {
|
||||
const {data} = props
|
||||
|
||||
// Should be based on some path
|
||||
const avatarMenu =
|
||||
<span>
|
||||
<IconButton color="primary" style={{marginRight: 15, }} aria-controls="simple-menu" aria-haspopup="true" onClick={(event) => {
|
||||
return (
|
||||
<Paper style={{backgroundColor: theme.palette.surfaceColor, width: notificationWidth, padding: 25, borderBottom: "1px solid rgba(255,255,255,0.4)"}}>
|
||||
{/*<Typography variant="h6">
|
||||
{new Date(data.updated_at).toISOString()}
|
||||
</Typography >*/}
|
||||
{data.reference_url !== undefined && data.reference_url !== null && data.reference_url.length > 0 ?
|
||||
<Link to={data.reference_url} style={{color: "#f86a3e", textDecoration: "none",}}>
|
||||
<Typography variant="h6">
|
||||
{data.title}
|
||||
</Typography >
|
||||
</Link>
|
||||
:
|
||||
<Typography variant="h6">
|
||||
{data.title}
|
||||
</Typography >
|
||||
}
|
||||
|
||||
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
||||
<img alt={data.title} src={data.image} style={{height: 100, width: 100, }} />
|
||||
:
|
||||
null
|
||||
}
|
||||
<Typography variant="body1">
|
||||
{data.description}
|
||||
</Typography >
|
||||
{/*data.tags !== undefined && data.tags !== null && data.tags.length > 0 ?
|
||||
data.tags.map((tag, index) => {
|
||||
return (
|
||||
<Chip
|
||||
key={index}
|
||||
style={chipStyle}
|
||||
label={tag}
|
||||
onClick={() => {
|
||||
}}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null */}
|
||||
{data.read === false ?
|
||||
<Button color="primary" variant="contained" style={{marginTop: 15}} onClick={() => {
|
||||
dismissNotification(data.id)
|
||||
}}>
|
||||
Dismiss
|
||||
</Button>
|
||||
: null}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
const notificationMenu =
|
||||
<span style={{zIndex: 10001}}>
|
||||
<IconButton color="primary" style={{zIndex: 10001, marginRight: 15, }} aria-controls="simple-menu" aria-haspopup="true" onClick={(event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
}}>
|
||||
<Avatar style={{height: 35, width: 35,}} alt="Your username here" src="" />
|
||||
<Badge badgeContent={notifications.length} color="primary">
|
||||
<NotificationsIcon color="secondary" style={{height: 35, width: 35,}} alt="Your username here" src="" />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
anchorEl={anchorEl}
|
||||
keepMounted
|
||||
open={Boolean(anchorEl)}
|
||||
style={{zIndex: 10002, maxHeight: "90vh", overflowX: "hidden", overflowY: "auto",}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
handleClose()
|
||||
}}
|
||||
>
|
||||
<Paper style={{backgroundColor: theme.palette.surfaceColor, width: notificationWidth, padding: 25, borderBottom: "3px solid rgba(255,255,255,0.4)"}}>
|
||||
<div style={{display: "flex", marginBottom: 5, }}>
|
||||
<Typography variant="h6">
|
||||
Your Notifications ({notifications.length})
|
||||
</Typography>
|
||||
{notifications.length > 1 ?
|
||||
<Button color="primary" variant="contained" style={{marginLeft: 30, }} onClick={() => {
|
||||
clearNotifications()
|
||||
}}>
|
||||
Flush
|
||||
</Button>
|
||||
: null}
|
||||
</div>
|
||||
<Typography variant="body2">
|
||||
Notifications are made by Shuffle to help you discover issues or improvements.
|
||||
</Typography >
|
||||
</Paper>
|
||||
{notifications.map((data, index) => {
|
||||
return (
|
||||
<NotificationItem data={data} key={index} />
|
||||
)
|
||||
})}
|
||||
</Menu>
|
||||
</span>
|
||||
|
||||
// Should be based on some path
|
||||
const avatarMenu =
|
||||
<span style={{zIndex: 10001}}>
|
||||
<IconButton color="primary" style={{zIndex: 10001, marginRight: 15, }} aria-controls="simple-menu" aria-haspopup="true" onClick={(event) => {
|
||||
setAnchorElAvatar(event.currentTarget);
|
||||
}}>
|
||||
<Avatar style={{height: 35, width: 35,}} alt="Your username here" src="" />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
anchorEl={anchorElAvatar}
|
||||
keepMounted
|
||||
open={Boolean(anchorElAvatar)}
|
||||
style={{zIndex: 10012}}
|
||||
onClose={() => {
|
||||
handleClose()
|
||||
}}
|
||||
@@ -139,7 +342,7 @@ const Header = props => {
|
||||
handleClose()
|
||||
}}>
|
||||
<Link to="/settings" style={hrefStyle}>
|
||||
Settings
|
||||
<SettingsIcon /> Settings
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem style={{color: "white"}} onClick={(event) => {
|
||||
@@ -147,7 +350,7 @@ const Header = props => {
|
||||
handleClose()
|
||||
handleClickLogout()
|
||||
}}>
|
||||
Logout
|
||||
<MeetingRoomIcon /> Logout
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</span>
|
||||
@@ -155,8 +358,9 @@ const Header = props => {
|
||||
|
||||
// Handle top bar or something
|
||||
const logoCheck = !homePage ? null : null
|
||||
//<div style={{position: "fixed", top: 0, left: 0, display: "flex"}}>
|
||||
const loginTextBrowser = !isLoggedIn ?
|
||||
<div style={{display: "flex"}}>
|
||||
<div style={{display: "flex"}}>
|
||||
<List style={{display: "flex", flexDirect: "row"}} component="nav">
|
||||
<ListItem style={{textAlign: "center", marginLeft: "0px"}}>
|
||||
<Link to ="/docs/about" style={hrefStyle}>
|
||||
@@ -232,6 +436,7 @@ const Header = props => {
|
||||
</div>
|
||||
<div style={{flex: "10", display: "flex", flexDirection: "row-reverse"}}>
|
||||
{avatarMenu}
|
||||
{notificationMenu}
|
||||
{userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null :
|
||||
<Link to="/admin" style={hrefStyle}>
|
||||
<Button color="primary" variant="contained" style={{marginRight: 15, marginTop: 12}}>
|
||||
@@ -239,9 +444,55 @@ const Header = props => {
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
{userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null :
|
||||
<Select
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
marginLeft: 10,
|
||||
maxWidth: 200,
|
||||
overflow: "hidden",
|
||||
}
|
||||
}}
|
||||
value={userdata.active_org.id}
|
||||
fullWidth
|
||||
style={{zIndex: 10012, marginTop: 5, backgroundColor: theme.palette.surfaceColor, marginRight: 15, color: "white", height: 50, width: 200}}
|
||||
MenuProps={{
|
||||
style: {zIndex: 10012}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
handleClickChangeOrg(e.target.value)
|
||||
}}
|
||||
>
|
||||
{userdata.orgs.map((data, index) => {
|
||||
if (data.name === undefined || data.name === null || data.name.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const imagesize = 22
|
||||
const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", marginRight: 10, marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0}
|
||||
const image = data.image === "" ?
|
||||
<img alt={data.name} src={theme.palette.defaultImage} style={imageStyle} />
|
||||
:
|
||||
<img alt={data.name} src={data.image} style={imageStyle} />
|
||||
|
||||
return (
|
||||
<MenuItem key={index} disabled={data.id === userdata.active_org.id} style={{backgroundColor: theme.palette.inputColor, color: "white", zIndex: 10013,}} value={data.id}>
|
||||
|
||||
<Tooltip color="primary" title={`Suborg of ${data.creator_org}`} placement="left">
|
||||
<div style={{display: "flex"}}>
|
||||
{image} {data.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
//console.log("USR: ", userdata.orgs)
|
||||
|
||||
const loginTextMobile = !isLoggedIn ?
|
||||
<div style={{display: "flex"}}>
|
||||
<List style={{display: "flex", flexDirection: "row"}} component="nav">
|
||||
@@ -300,7 +551,7 @@ const Header = props => {
|
||||
|
||||
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
|
||||
const loadedCheck =
|
||||
<div style={{minHeight: 60}}>
|
||||
<div style={{minHeight: 60, }}>
|
||||
<BrowserView>
|
||||
{loginTextBrowser}
|
||||
</BrowserView>
|
||||
@@ -310,10 +561,10 @@ const Header = props => {
|
||||
</div>
|
||||
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
|
||||
return (
|
||||
<div>
|
||||
{loadedCheck}
|
||||
<div style={{width: "100%", position: "fixed", minHeight: 60, top: 0, zIndex: 10000, backgroundColor: "inherit",}}>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default Header;
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ListItemText, TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade } from '@material-ui/core';
|
||||
import { LockOpen as LockOpenIcon } from '@material-ui/icons';
|
||||
|
||||
const ITEM_HEIGHT = 55
|
||||
const ITEM_PADDING_TOP = 8
|
||||
const MenuProps = {
|
||||
PaperProps: {
|
||||
style: {
|
||||
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
|
||||
minWidth: 500,
|
||||
maxWidth: 500,
|
||||
scrollX: "auto",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const AuthenticationOauth2 = (props) => {
|
||||
const { saveWorkflow, selectedApp, workflow, selectedAction, authenticationType, getAppAuthentication, appAuthentication, setSelectedAction, setNewAppAuth, setAuthenticationModalOpen} = props;
|
||||
const theme = useTheme();
|
||||
|
||||
//const [update, setUpdate] = React.useState("|")
|
||||
const [defaultConfigSet, setDefaultConfigSet] = React.useState(authenticationType.client_id !== undefined && authenticationType.client_id !== null && authenticationType.client_id.length > 0 && authenticationType.client_secret !== undefined && authenticationType.client_secret !== null && authenticationType.client_secret.length > 0)
|
||||
const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : "")
|
||||
const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "")
|
||||
const [oauthUrl, setOauthUrl] = React.useState("")
|
||||
const [buttonClicked, setButtonClicked] = React.useState(false)
|
||||
const [selectedScopes, setSelectedScopes] = React.useState([])
|
||||
const allscopes = authenticationType.scope !== undefined ? authenticationType.scope: []
|
||||
|
||||
const [manuallyConfigure, setManuallyConfigure] = React.useState(defaultConfigSet ? false : true)
|
||||
const [authenticationOption, setAuthenticationOptions] = React.useState({
|
||||
app: JSON.parse(JSON.stringify(selectedApp)),
|
||||
fields: {},
|
||||
label: "",
|
||||
usage: [{
|
||||
workflow_id: workflow.id,
|
||||
}],
|
||||
id: uuidv4(),
|
||||
active: true,
|
||||
})
|
||||
|
||||
if (selectedApp.authentication === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => {
|
||||
setButtonClicked(true)
|
||||
console.log("SCOPES: ", scopes)
|
||||
|
||||
var resources = ""
|
||||
if (scopes !== undefined && scopes !== null & scopes.length > 0) {
|
||||
resources = scopes.join(",")
|
||||
}
|
||||
|
||||
const authentication_url = authenticationType.token_uri
|
||||
|
||||
console.log("SCOPES2: ", resources)
|
||||
const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`
|
||||
var state = `workflow_id%3D${workflow.id}%26reference_action_id%3d${selectedAction.app_id}%26app_name%3d${selectedAction.app_name}%26app_id%3d${selectedAction.app_id}%26app_version%3d${selectedAction.app_version}%26authentication_url%3d${authentication_url}%26scope%3d${resources}%26client_id%3d${client_id}%26client_secret%3d${client_secret}`
|
||||
if (oauth_url !== undefined && oauth_url !== null && oauth_url.length > 0) {
|
||||
state += `%26oauth_url%3d${oauth_url}`
|
||||
console.log("ADDING OAUTH2 URL: ", state)
|
||||
}
|
||||
const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}`
|
||||
|
||||
//const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent`
|
||||
console.log("Full URI: ", url)
|
||||
console.log("Redirect Uri: ", redirectUri)
|
||||
// &resource=https%3A%2F%2Fgraph.microsoft.com&
|
||||
|
||||
// FIXME: Awful, but works for prototyping
|
||||
// How can we get a callback properly realtime?
|
||||
// How can we properly try-catch without breaks on error?
|
||||
try {
|
||||
|
||||
var newwin = window.open(url, "", "width=800,height=600")
|
||||
//console.log(newwin)
|
||||
|
||||
var open = true
|
||||
const timer = setInterval(() => {
|
||||
if (newwin.closed) {
|
||||
setButtonClicked(false)
|
||||
clearInterval(timer);
|
||||
//alert('"Secure Payment" window closed!');
|
||||
|
||||
getAppAuthentication(true, true)
|
||||
}
|
||||
}, 1000);
|
||||
//do {
|
||||
// setTimeout(() => {
|
||||
// console.log(newwin)
|
||||
// console.log("CLOSED", newwin.closed)
|
||||
// if (newwin.closed) {
|
||||
|
||||
// open = false
|
||||
// }
|
||||
// }, 1000)
|
||||
//}
|
||||
//while(open === true)
|
||||
} catch (e) {
|
||||
alert.error("Failed authentication - probably bad credentials. Try again")
|
||||
setButtonClicked(false)
|
||||
}
|
||||
|
||||
return
|
||||
//do {
|
||||
//} while (
|
||||
}
|
||||
|
||||
|
||||
authenticationOption.app.actions = []
|
||||
|
||||
for (var key in selectedApp.authentication.parameters) {
|
||||
if (authenticationOption.fields[selectedApp.authentication.parameters[key].name] === undefined) {
|
||||
authenticationOption.fields[selectedApp.authentication.parameters[key].name] = ""
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmitCheck = () => {
|
||||
console.log("NEW AUTH: ", authenticationOption)
|
||||
if (authenticationOption.label.length === 0) {
|
||||
authenticationOption.label = `Auth for ${selectedApp.name}`
|
||||
//alert.info("Label can't be empty")
|
||||
//return
|
||||
}
|
||||
|
||||
// Automatically mapping fields that already exist (predefined).
|
||||
// Warning if fields are NOT filled
|
||||
for (var key in selectedApp.authentication.parameters) {
|
||||
if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) {
|
||||
if (selectedApp.authentication.parameters[key].value !== undefined && selectedApp.authentication.parameters[key].value !== null && selectedApp.authentication.parameters[key].value.length > 0) {
|
||||
authenticationOption.fields[selectedApp.authentication.parameters[key].name] = selectedApp.authentication.parameters[key].value
|
||||
} else {
|
||||
if (selectedApp.authentication.parameters[key].schema.type === "bool") {
|
||||
authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "false"
|
||||
} else {
|
||||
alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Action: ", selectedAction)
|
||||
selectedAction.authentication_id = authenticationOption.id
|
||||
selectedAction.selectedAuthentication = authenticationOption
|
||||
if (selectedAction.authentication === undefined || selectedAction.authentication === null) {
|
||||
selectedAction.authentication = [authenticationOption]
|
||||
} else {
|
||||
selectedAction.authentication.push(authenticationOption)
|
||||
}
|
||||
|
||||
setSelectedAction(selectedAction)
|
||||
|
||||
var newAuthOption = JSON.parse(JSON.stringify(authenticationOption))
|
||||
var newFields = []
|
||||
for (const key in newAuthOption.fields) {
|
||||
const value = newAuthOption.fields[key]
|
||||
newFields.push({
|
||||
key: key,
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
|
||||
console.log("FIELDS: ", newFields)
|
||||
newAuthOption.fields = newFields
|
||||
setNewAppAuth(newAuthOption)
|
||||
//appAuthentication.push(newAuthOption)
|
||||
//setAppAuthentication(appAuthentication)
|
||||
//
|
||||
|
||||
//if (configureWorkflowModalOpen) {
|
||||
// setSelectedAction({})
|
||||
//}
|
||||
//setUpdate(authenticationOption.id)
|
||||
|
||||
/*
|
||||
{selectedAction.authentication.map(data => (
|
||||
<MenuItem key={data.id} style={{backgroundColor: inputColor, color: "white"}} value={data}>
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
const handleScopeChange = (event) => {
|
||||
const {
|
||||
target: { value },
|
||||
} = event;
|
||||
|
||||
console.log("VALUE: ", value)
|
||||
|
||||
// On autofill we get a the stringified value.
|
||||
setSelectedScopes(typeof value === 'string' ? value.split(',') : value)
|
||||
}
|
||||
|
||||
|
||||
if (authenticationOption.label === null || authenticationOption.label === undefined) {
|
||||
authenticationOption.label = selectedApp.name+" authentication"
|
||||
}
|
||||
|
||||
//console.log(
|
||||
return (
|
||||
<div>
|
||||
<DialogTitle><div style={{color: "white"}}>Authentication for {selectedApp.name}</div></DialogTitle>
|
||||
<DialogContent>
|
||||
<span style={{}}>
|
||||
<b>Oauth2 requires a client ID and secret to authenticate. This is usually made in the remote system.</b>
|
||||
<a target="_blank" rel="norefferer" href="https://shuffler.io/docs/apps#authentication" style={{textDecoration: "none", color: "#f85a3e"}}> Learn more about Oauth2 with Shuffle</a><div/>
|
||||
</span>
|
||||
{/*<TextField
|
||||
style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
marginLeft: "5px",
|
||||
maxWidth: "95%",
|
||||
height: 50,
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
color="primary"
|
||||
placeholder={"Auth july 2020"}
|
||||
defaultValue={`Auth for ${selectedApp.name}`}
|
||||
onChange={(event) => {
|
||||
authenticationOption.label = event.target.value
|
||||
}}
|
||||
/>
|
||||
<Divider style={{marginTop: 15, marginBottom: 15, backgroundColor: "rgb(91, 96, 100)"}}/>
|
||||
*/}
|
||||
|
||||
{!manuallyConfigure ? null :
|
||||
<span>
|
||||
{selectedApp.authentication.parameters.map((data, index) => {
|
||||
//console.log(data, index)
|
||||
if (data.name === "client_id" || data.name === "client_secret") {
|
||||
return null
|
||||
}
|
||||
|
||||
if (data.name !== "url") {
|
||||
return null
|
||||
}
|
||||
|
||||
if (oauthUrl.length === 0) {
|
||||
setOauthUrl(data.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={index} style={{marginTop: 10}}>
|
||||
<LockOpenIcon style={{marginRight: 10}}/>
|
||||
<b>{data.name}</b>
|
||||
|
||||
{data.schema !== undefined && data.schema !== null && data.schema.type === "bool" ?
|
||||
<Select
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
marginLeft: 10,
|
||||
}
|
||||
}}
|
||||
defaultValue={"false"}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
console.log("Value: ", e.target.value)
|
||||
authenticationOption.fields[data.name] = e.target.value
|
||||
}}
|
||||
style={{backgroundColor: theme.palette.surfaceColor, color: "white", height: 50}}
|
||||
>
|
||||
<MenuItem key={"false"} style={{backgroundColor: theme.palette.inputColor, color: "white"}} value={"false"}>
|
||||
false
|
||||
</MenuItem>
|
||||
<MenuItem key={"true"} style={{backgroundColor: theme.palette.inputColor, color: "white"}} value={"true"}>
|
||||
true
|
||||
</MenuItem>
|
||||
</Select>
|
||||
:
|
||||
<TextField
|
||||
style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
marginLeft: "5px",
|
||||
maxWidth: "95%",
|
||||
height: 50,
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
type={data.example !== undefined && data.example.includes("***") ? "password" : "text"}
|
||||
color="primary"
|
||||
defaultValue={data.value !== undefined && data.value !== null ? data.value : ""}
|
||||
placeholder={data.example}
|
||||
onChange={(event) => {
|
||||
authenticationOption.fields[data.name] = event.target.value
|
||||
console.log("Setting oauth url")
|
||||
setOauthUrl(event.target.value)
|
||||
//const [oauthUrl, setOauthUrl] = React.useState("")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{allscopes.length === 0 ? null :
|
||||
<Select
|
||||
multiple
|
||||
value={selectedScopes}
|
||||
style={{backgroundColor: theme.palette.inputColor, color: "white", }}
|
||||
onChange={(e) => {
|
||||
handleScopeChange(e)
|
||||
}}
|
||||
fullWidth
|
||||
input={<Input id="select-multiple-native" />}
|
||||
renderValue={(selected) => selected.join(', ')}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{allscopes.map((data, index) => {
|
||||
return (
|
||||
<MenuItem key={index} value={data}>
|
||||
<Checkbox checked={selectedScopes.indexOf(data) > -1} />
|
||||
<ListItemText primary={data} />
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</Select>
|
||||
}
|
||||
<TextField
|
||||
style={{marginTop: 20, backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
marginLeft: "5px",
|
||||
maxWidth: "95%",
|
||||
height: 50,
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
color="primary"
|
||||
placeholder={"Client ID"}
|
||||
onChange={(event) => {
|
||||
setClientId(event.target.value)
|
||||
//authenticationOption.label = event.target.value
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius,}}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
marginLeft: "5px",
|
||||
maxWidth: "95%",
|
||||
height: 50,
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
fullWidth
|
||||
color="primary"
|
||||
placeholder={"Client Secret"}
|
||||
onChange={(event) => {
|
||||
setClientSecret(event.target.value)
|
||||
//authenticationOption.label = event.target.value
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
<Button
|
||||
style={{marginBottom: 40, marginTop: 20, borderRadius: theme.palette.borderRadius}}
|
||||
disabled={clientSecret.length === 0 || clientId.length === 0 || buttonClicked}
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
handleOauth2Request(clientId, clientSecret, oauthUrl, selectedScopes)
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{buttonClicked ?
|
||||
<CircularProgress style={{color: "white", }} />
|
||||
:
|
||||
"Oauth2 request"
|
||||
}
|
||||
</Button>
|
||||
|
||||
{defaultConfigSet ?
|
||||
<span style={{}}>
|
||||
... or
|
||||
<Button
|
||||
style={{marginLeft: 10, borderRadius: theme.palette.borderRadius}}
|
||||
disabled={clientSecret.length === 0 || clientId.length === 0}
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
setManuallyConfigure(!manuallyConfigure)
|
||||
|
||||
if (manuallyConfigure) {
|
||||
setClientId(authenticationType.client_id)
|
||||
setClientSecret(authenticationType.client_secret)
|
||||
} else {
|
||||
setClientId("")
|
||||
setClientSecret("")
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
{manuallyConfigure ? "Use auto-config" : "Manually configure Oauth2"}
|
||||
</Button>
|
||||
</span>
|
||||
:
|
||||
null
|
||||
}
|
||||
</DialogContent>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AuthenticationOauth2
|
||||
File diff suppressed because one or more lines are too long
@@ -8,10 +8,14 @@ import { useTheme } from '@material-ui/core/styles';
|
||||
import NestedMenuItem from "material-ui-nested-menu-item";
|
||||
//import NestedMenuItem from "./NestedMenu.jsx";
|
||||
|
||||
import {Popper, TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core';
|
||||
import {GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
|
||||
import {Popper, TextField, TextareaAutosize, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core';
|
||||
import {HelpOutline as HelpOutlineIcon, Description as DescriptionIcon, GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import 'codemirror/keymap/sublime';
|
||||
import 'codemirror/theme/gruvbox-dark.css';
|
||||
|
||||
|
||||
const useStyles = makeStyles({
|
||||
notchedOutline: {
|
||||
@@ -46,10 +50,13 @@ const useStyles = makeStyles({
|
||||
//)
|
||||
|
||||
const ParsedAction = (props) => {
|
||||
const {workflow, setWorkflow, setAction, setSelectedAction, setUpdate, appActionArguments, selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, hideExtraTypes, scrollConfig, setScrollConfig } = props
|
||||
const {workflow, setWorkflow, setAction, setSelectedAction, setUpdate, appActionArguments, selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, hideExtraTypes, scrollConfig, setScrollConfig, authenticationType, appAuthentication, getAppAuthentication } = props
|
||||
|
||||
const theme = useTheme();
|
||||
const classes = useStyles()
|
||||
|
||||
const [expansionModalOpen, setExpansionModalOpen] = React.useState(false);
|
||||
|
||||
const keywords = ["len(", "lower(", "upper(", "trim(", "split(", "length(", "number(", "parse(", "join("]
|
||||
const getParents = (action) => {
|
||||
if (cy === undefined) {
|
||||
@@ -327,7 +334,8 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
|
||||
// 1. Take
|
||||
const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": `${item.label.split(" ").join("_")}`, "example": exampledata}
|
||||
const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_")
|
||||
const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": itemlabelComplete, "example": exampledata}
|
||||
actionlist.push(actionvalue)
|
||||
}
|
||||
}
|
||||
@@ -337,12 +345,13 @@ const ParsedAction = (props) => {
|
||||
})
|
||||
|
||||
const changeActionParameter = (event, count, data) => {
|
||||
//console.log(event)
|
||||
if (data.name.startsWith("${") && data.name.endsWith("}")) {
|
||||
// PARAM FIX - Gonna use the ID field, even though it's a hack
|
||||
const paramcheck = selectedAction.parameters.find(param => param.name === "body")
|
||||
if (paramcheck !== undefined) {
|
||||
// Escapes all double quotes
|
||||
const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\"")
|
||||
const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\"");
|
||||
console.log("REPLACE WITH: ", toReplace)
|
||||
if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
|
||||
paramcheck["value_replace"] = [{
|
||||
@@ -472,6 +481,7 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
//console.log("CHANGING ACTION COUNT !")
|
||||
selectedActionParameters[count].value = event.target.value
|
||||
selectedAction.parameters[count].value = event.target.value
|
||||
setSelectedAction(selectedAction)
|
||||
@@ -479,6 +489,150 @@ const ParsedAction = (props) => {
|
||||
//setUpdate(event.target.value)
|
||||
}
|
||||
|
||||
const changeActionParameterCodemirror = (event, count, data) => {
|
||||
console.log(event)
|
||||
if (data.name.startsWith("${") && data.name.endsWith("}")) {
|
||||
// PARAM FIX - Gonna use the ID field, even though it's a hack
|
||||
const paramcheck = selectedAction.parameters.find(param => param.name === "body")
|
||||
if (paramcheck !== undefined) {
|
||||
// Escapes all double quotes
|
||||
const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\"");
|
||||
console.log("REPLACE WITH: ", toReplace)
|
||||
if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) {
|
||||
paramcheck["value_replace"] = [{
|
||||
"key": data.name,
|
||||
"value": toReplace,
|
||||
}]
|
||||
|
||||
console.log("IN IF: ", paramcheck)
|
||||
|
||||
} else {
|
||||
const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name)
|
||||
if (subparamindex === -1) {
|
||||
paramcheck["value_replace"].push({
|
||||
"key": data.name,
|
||||
"value": toReplace,
|
||||
})
|
||||
} else {
|
||||
paramcheck["value_replace"][subparamindex]["value"] = toReplace
|
||||
}
|
||||
|
||||
console.log("IN ELSE: ", paramcheck)
|
||||
}
|
||||
//console.log("PARAM: ", paramcheck)
|
||||
//if (paramcheck.id === undefined) {
|
||||
// console.log("Normal paramcheck")
|
||||
//} else {
|
||||
// selectedActionParameters[count]["value_replace"] = paramcheck
|
||||
// selectedAction.parameters[count]["value_replace"] = paramcheck
|
||||
//}
|
||||
|
||||
if (paramcheck["value_replace"] === undefined) {
|
||||
selectedActionParameters[count]["value_replace"] = paramcheck
|
||||
selectedAction.parameters[count]["value_replace"] = paramcheck
|
||||
} else {
|
||||
selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"]
|
||||
selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"]
|
||||
}
|
||||
console.log("RESULT: ", selectedAction)
|
||||
setSelectedAction(selectedAction)
|
||||
//setUpdate(Math.random())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "$") {
|
||||
if (!showDropdown) {
|
||||
setShowAutocomplete(false)
|
||||
setShowDropdown(true)
|
||||
setShowDropdownNumber(count)
|
||||
}
|
||||
} else {
|
||||
if (showDropdown) {
|
||||
setShowDropdown(false)
|
||||
}
|
||||
}
|
||||
|
||||
// bad detection mechanism probably
|
||||
if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "." && actionlist.length > 0) {
|
||||
console.log("GET THE LAST ARGUMENT FOR NODE!")
|
||||
// THIS IS AN EXAMPLE OF SHOWING IT
|
||||
/*
|
||||
const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"}
|
||||
setJsonList(GetParsedPaths(inputdata, ""))
|
||||
if (!showDropdown) {
|
||||
setShowAutocomplete(false)
|
||||
setShowDropdown(true)
|
||||
setShowDropdownNumber(count)
|
||||
}
|
||||
console.log(jsonList)
|
||||
*/
|
||||
|
||||
// Search for the item backwards
|
||||
// 1. Reverse search backwards from . -> $
|
||||
// 2. Search the actionlist for the item
|
||||
// 3. Find the data for the specific item
|
||||
|
||||
var curstring = ""
|
||||
var record = false
|
||||
for (var key in selectedActionParameters[count].value) {
|
||||
const item = selectedActionParameters[count].value[key]
|
||||
if (record) {
|
||||
curstring += item
|
||||
}
|
||||
|
||||
if (item === "$") {
|
||||
record = true
|
||||
curstring = ""
|
||||
}
|
||||
}
|
||||
|
||||
//console.log("CURSTRING: ", curstring)
|
||||
if (curstring.length > 0 && actionlist !== null) {
|
||||
// Search back in the action list
|
||||
curstring = curstring.split(" ").join("_").toLowerCase()
|
||||
var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring)
|
||||
if (actionItem !== undefined) {
|
||||
console.log("Found item: ", actionItem)
|
||||
|
||||
//actionItem.example = actionItem.example.trim()
|
||||
//actionItem.example = actionItem.example.split(" None").join(" \"None\"")
|
||||
//actionItem.example = actionItem.example.split("\'").join("\"")
|
||||
|
||||
var jsonvalid = true
|
||||
try {
|
||||
const tmp = String(JSON.parse(actionItem.example))
|
||||
if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) {
|
||||
jsonvalid = false
|
||||
}
|
||||
} catch (e) {
|
||||
jsonvalid = false
|
||||
}
|
||||
|
||||
if (jsonvalid) {
|
||||
setJsonList(GetParsedPaths(JSON.parse(actionItem.example), ""))
|
||||
|
||||
if (!showDropdown) {
|
||||
setShowAutocomplete(false)
|
||||
setShowDropdown(true)
|
||||
setShowDropdownNumber(count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (jsonList.length > 0) {
|
||||
setJsonList([])
|
||||
}
|
||||
}
|
||||
|
||||
selectedActionParameters[count].value = event.display.maxLine.text
|
||||
selectedAction.parameters[count].value = event.display.maxLine.text
|
||||
setSelectedAction(selectedAction)
|
||||
//setUpdate(Math.random())
|
||||
//setUpdate(event.target.value)
|
||||
}
|
||||
|
||||
const changeActionParameterVariable = (fieldvalue, count) => {
|
||||
//console.log("CALLED THIS ONE WITH VALUE!", fieldvalue)
|
||||
//if (selectedVariableParameter === fieldvalue) {
|
||||
@@ -599,6 +753,10 @@ const ParsedAction = (props) => {
|
||||
var placeholder = "Static value"
|
||||
if (data.example !== undefined && data.example !== null && data.example.length > 0) {
|
||||
placeholder = data.example
|
||||
|
||||
if (data.name === "url" && data.value.length === 0) {
|
||||
data.value = data.example
|
||||
}
|
||||
}
|
||||
|
||||
if (data.name.startsWith("${") && data.name.endsWith("}")) {
|
||||
@@ -684,10 +842,12 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
|
||||
const clickedFieldId = "rightside_field_"+count
|
||||
//<TextareaAutosize
|
||||
// <CodeMirror
|
||||
var datafield =
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, border: selectedActionParameters[count].required || selectedActionParameters[count].configuration ? "2px solid #f85a3e" : "",}}
|
||||
style={{backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, border: selectedActionParameters[count].required || selectedActionParameters[count].configuration ? "2px solid #f85a3e" : "", color: "white", width: "100%", fontSize: "1em", }}
|
||||
InputProps={{
|
||||
style:{
|
||||
color: "white",
|
||||
@@ -696,28 +856,29 @@ const ParsedAction = (props) => {
|
||||
maxWidth: "95%",
|
||||
fontSize: "1em",
|
||||
},
|
||||
endAdornment: (
|
||||
hideExtraTypes ? null :
|
||||
<InputAdornment position="end">
|
||||
<Tooltip title="Autocomplete text" placement="top">
|
||||
<AddCircleOutlineIcon style={{cursor: "pointer"}} onClick={(event) => {
|
||||
setMenuPosition({
|
||||
top: event.pageY+10,
|
||||
left: event.pageX+10,
|
||||
})
|
||||
setShowDropdownNumber(count)
|
||||
setShowDropdown(true)
|
||||
setShowAutocomplete(true)
|
||||
}}/>
|
||||
</Tooltip>
|
||||
</InputAdornment>
|
||||
|
||||
)
|
||||
endAdornment: (
|
||||
hideExtraTypes ? null :
|
||||
<InputAdornment position="end">
|
||||
<Tooltip title="Autocomplete the text" placement="top">
|
||||
<AddCircleOutlineIcon style={{cursor: "pointer"}} onClick={(event) => {
|
||||
setMenuPosition({
|
||||
top: event.pageY+10,
|
||||
left: event.pageX+10,
|
||||
})
|
||||
setShowDropdownNumber(count)
|
||||
setShowDropdown(true)
|
||||
setShowAutocomplete(true)
|
||||
}}/>
|
||||
</Tooltip>
|
||||
</InputAdornment>
|
||||
|
||||
)
|
||||
}}
|
||||
fullWidth
|
||||
multiline={multiline}
|
||||
onClick={() => {
|
||||
//console.log("Clicked field: ", clickedFieldId)
|
||||
console.log("Clicked field: ", clickedFieldId)
|
||||
setExpansionModalOpen(false)
|
||||
if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) {
|
||||
scrollConfig.selected = clickedFieldId
|
||||
setScrollConfig(scrollConfig)
|
||||
@@ -728,11 +889,22 @@ const ParsedAction = (props) => {
|
||||
rows={rows}
|
||||
color="primary"
|
||||
defaultValue={data.value}
|
||||
//value={data.value}
|
||||
//options={{
|
||||
// theme: 'gruvbox-dark',
|
||||
// keyMap: 'sublime',
|
||||
// mode: 'python',
|
||||
//}}
|
||||
//height={multiline ? 50 : 150}
|
||||
|
||||
type={placeholder.includes("***") || (data.configuration && (data.name.toLowerCase().includes("api") || data.name.toLowerCase().includes("key") || data.name.toLowerCase().includes("pass"))) ? "password" : "text"}
|
||||
placeholder={placeholder}
|
||||
|
||||
onChange={(event) => {
|
||||
changeActionParameter(event, count, data)
|
||||
//changeActionParameterCodemirror(event, count, data)
|
||||
changeActionParameter(event, count, data)
|
||||
}}
|
||||
|
||||
helperText={selectedApp.generated && selectedApp.activated && data.name === "body" ?
|
||||
<span style={{color:"white", marginBottom: 5, marginleft: 5,}}>
|
||||
{openApiHelperText}
|
||||
@@ -790,7 +962,7 @@ const ParsedAction = (props) => {
|
||||
endAdornment: (
|
||||
hideExtraTypes ? null :
|
||||
<InputAdornment position="end">
|
||||
<Tooltip title="Autocomplete text" placement="top">
|
||||
<Tooltip title="Autocomplete the text" placement="top">
|
||||
<AddCircleOutlineIcon style={{cursor: "pointer"}} onClick={(event) => {
|
||||
setMenuPosition({
|
||||
top: event.pageY+10,
|
||||
@@ -1102,6 +1274,13 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
|
||||
tmpitem = (tmpitem.charAt(0).toUpperCase()+tmpitem.substring(1)).replaceAll("_", " ")
|
||||
|
||||
if (tmpitem === "Username basic") {
|
||||
tmpitem = "Username"
|
||||
} else if (tmpitem === "Password basic") {
|
||||
tmpitem = "Password"
|
||||
}
|
||||
|
||||
const description = data.description === undefined ? "" : data.description
|
||||
const tooltipDescription =
|
||||
<span>
|
||||
@@ -1163,11 +1342,12 @@ const ParsedAction = (props) => {
|
||||
</Tooltip>
|
||||
</div>
|
||||
*/}
|
||||
{(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null :
|
||||
{/*(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null :
|
||||
<div style={{display: "flex"}}>
|
||||
<Tooltip color="secondary" title="Value must be unique" placement="top">
|
||||
<div style={{cursor: "pointer", color: staticcolor}} onClick={(e) => {}}>
|
||||
<Checkbox
|
||||
tabIndex="-1"
|
||||
checked={selectedActionParameters[count].unique_toggled}
|
||||
style={{
|
||||
color: theme.palette.primary.secondary,
|
||||
@@ -1185,7 +1365,7 @@ const ParsedAction = (props) => {
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
*/}
|
||||
</div>
|
||||
{datafield}
|
||||
{showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length > 0 ?
|
||||
@@ -1256,6 +1436,27 @@ const ParsedAction = (props) => {
|
||||
return null
|
||||
}
|
||||
|
||||
const expansionModal =
|
||||
<Dialog modal
|
||||
open={expansionModalOpen}
|
||||
onClose={() => {
|
||||
setExpansionModalOpen(false)
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
color: "white",
|
||||
minWidth: 600,
|
||||
padding: 50,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle><span style={{color: "white"}}>Workflow Variable</span></DialogTitle>
|
||||
<DialogContent>
|
||||
Hello
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
//const CustomPopper = function (props) {
|
||||
// const classes = useStyles()
|
||||
// return <Popper {...props} className={classes.root} placement="bottom" />
|
||||
@@ -1264,12 +1465,13 @@ const ParsedAction = (props) => {
|
||||
const baselabel = selectedAction.label
|
||||
return (
|
||||
<div style={appApiViewStyle} id="parsed_action_view">
|
||||
{expansionModal}
|
||||
{hideExtraTypes === true ? null :
|
||||
<span>
|
||||
<div style={{display: "flex", minHeight: 40, marginBottom: 30}}>
|
||||
<div style={{flex: 1}}>
|
||||
<h3 style={{marginBottom: 5}}>{(selectedAction.app_name.charAt(0).toUpperCase()+selectedAction.app_name.substring(1)).replaceAll("_", " ")}</h3>
|
||||
<div style={{display: "flex",}}>
|
||||
<div style={{display: "flex", marginTop: 10, }}>
|
||||
<IconButton style={{marginTop: "auto", marginBottom: "auto", height: 30, paddingLeft: 0, paddingRight: 0}} onClick={() => {
|
||||
console.log("FIND EXAMPLE RESULTS FOR ", selectedAction)
|
||||
if (workflowExecutions.length > 0) {
|
||||
@@ -1298,19 +1500,24 @@ const ParsedAction = (props) => {
|
||||
<ArrowLeftIcon style={{color: "white"}}/>
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<span style={{}}>
|
||||
<Typography style={{marginTop: 5,}}><a rel="norefferer" href="https://shuffler.io/docs/workflows#nodes" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>What are actions?</a></Typography>
|
||||
{selectedAction.errors !== undefined && selectedAction.errors !== null && selectedAction.errors.length > 0 ?
|
||||
<div>
|
||||
Errors: {selectedAction.errors.join("\n")}
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</span>
|
||||
<IconButton style={{marginTop: "auto", marginBottom: "auto", height: 30, paddingLeft: 25, paddingRight: 0}} onClick={() => {
|
||||
setAuthenticationModalOpen(true)
|
||||
}}>
|
||||
<Tooltip color="primary" title="Read app docs" placement="top">
|
||||
<DescriptionIcon style={{color: "white"}} />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<IconButton style={{marginTop: "auto", marginBottom: "auto", height: 30, paddingLeft: 25, paddingRight: 0}} onClick={() => {}}>
|
||||
<a rel="norefferer" href="https://shuffler.io/docs/workflows#nodes" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
|
||||
<Tooltip color="primary" title="What are actions?" placement="top">
|
||||
<HelpOutlineIcon style={{color: "white"}}/>
|
||||
</Tooltip>
|
||||
</a>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{display: "flex", flexDirection: "column",}}>
|
||||
{selectedAction.id === workflow.start ? null :
|
||||
{/*selectedAction.id === workflow.start ? null :
|
||||
<Tooltip color="primary" title={"Make this node the start action"} placement="top">
|
||||
<Button style={{zIndex: 5000, marginTop: 10,}} color="primary" variant="outlined" onClick={(e) => {
|
||||
defineStartnode(e)
|
||||
@@ -1318,7 +1525,7 @@ const ParsedAction = (props) => {
|
||||
<KeyboardArrowRightIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
*/}
|
||||
{selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 1 ?
|
||||
<Select
|
||||
defaultValue={selectedAction.app_version}
|
||||
@@ -1360,6 +1567,7 @@ const ParsedAction = (props) => {
|
||||
fullWidth
|
||||
color="primary"
|
||||
placeholder={selectedAction.label}
|
||||
defaultValue={selectedAction.label}
|
||||
onChange={selectedNameChange}
|
||||
onBlur={(e) => {
|
||||
const name = e.target.value
|
||||
@@ -1387,6 +1595,11 @@ const ParsedAction = (props) => {
|
||||
<Tooltip color="primary" title={"Add authentication option"} placement="top">
|
||||
<span>
|
||||
<Button color="primary" style={{}} fullWidth variant="contained" onClick={() => {
|
||||
console.log(authenticationType)
|
||||
//if (authenticationType.type === "oauth2" && authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null) {
|
||||
// return null
|
||||
//}
|
||||
|
||||
setAuthenticationModalOpen(true)
|
||||
}}>
|
||||
<AddIcon style={{marginRight: 10, }}/> Authenticate {selectedApp.name}
|
||||
@@ -1401,7 +1614,7 @@ const ParsedAction = (props) => {
|
||||
<div style={{display: "flex"}}>
|
||||
<Select
|
||||
labelId="select-app-auth"
|
||||
value={selectedAction.selectedAuthentication}
|
||||
value={Object.getOwnPropertyNames(selectedAction.selectedAuthentication).length === 0 ? "No selection" : selectedAction.selectedAuthentication}
|
||||
SelectDisplayProps={{
|
||||
style: {
|
||||
marginLeft: 10,
|
||||
@@ -1409,14 +1622,32 @@ const ParsedAction = (props) => {
|
||||
}}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
//console.log("CHOSE AN AUTHENTICATION OPTION: ", e.target.value)
|
||||
selectedAction.selectedAuthentication = e.target.value
|
||||
selectedAction.authentication_id = e.target.value.id
|
||||
setSelectedAction(selectedAction)
|
||||
setUpdate(Math.random())
|
||||
if (e.target.value === "No selection") {
|
||||
selectedAction.selectedAuthentication = {}
|
||||
selectedAction.authentication_id = ""
|
||||
|
||||
for (var key in selectedAction.parameters) {
|
||||
//console.log(selectedAction.parameters[key])
|
||||
if (selectedAction.parameters[key].configuration) {
|
||||
selectedAction.parameters[key].value = ""
|
||||
}
|
||||
}
|
||||
setSelectedAction(selectedAction)
|
||||
setUpdate(Math.random())
|
||||
} else {
|
||||
//console.log("CHOSE AN AUTHENTICATION OPTION: ", e.target.value)
|
||||
selectedAction.selectedAuthentication = e.target.value
|
||||
selectedAction.authentication_id = e.target.value.id
|
||||
setSelectedAction(selectedAction)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
}}
|
||||
style={{backgroundColor: theme.palette.inputColor, color: "white", height: 50, maxWidth: rightsidebarStyle.maxWidth-80, borderRadius: theme.palette.borderRadius,}}
|
||||
>
|
||||
|
||||
<MenuItem style={{backgroundColor: theme.palette.inputColor, color: "white"}} value="No selection">
|
||||
<em>No selection</em>
|
||||
</MenuItem>
|
||||
{selectedAction.authentication.map(data => {
|
||||
//console.log("AUTH DATA: ", data)
|
||||
return(
|
||||
@@ -1511,7 +1742,7 @@ const ParsedAction = (props) => {
|
||||
<MenuItem style={{backgroundColor: theme.palette.inputColor, color: "white"}} value="No selection">
|
||||
<em>No selection</em>
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<Divider style={{backgroundColor: theme.palette.inputColor }} />
|
||||
{workflow.execution_variables.map(data => (
|
||||
<MenuItem style={{backgroundColor: theme.palette.inputColor, color: "white"}} value={data.name}>
|
||||
{data.name}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import ReactGA from 'react-ga';
|
||||
|
||||
function ScrollToTop({setCurpath, history }) {
|
||||
function ScrollToTop({getUserNotifications, setCurpath, history }) {
|
||||
useEffect(() => {
|
||||
const unlisten = history.listen(() => {
|
||||
window.scroll({
|
||||
@@ -11,14 +10,8 @@ function ScrollToTop({setCurpath, history }) {
|
||||
behavior: "smooth",
|
||||
});
|
||||
|
||||
//ReactGA.event({
|
||||
// category: "referral",
|
||||
// action: "new_user_referral",
|
||||
// label: "",
|
||||
//})
|
||||
|
||||
ReactGA.pageview(window.location.pathname)
|
||||
setCurpath(window.location.pathname)
|
||||
getUserNotifications()
|
||||
});
|
||||
return () => {
|
||||
unlisten();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./font1.woff2') format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./font2.woff2') format('woff2');
|
||||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./font3.woff2') format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./font4.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Nunito Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('./font5.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Nunito+Sans');
|
||||
@import url('./css/nunito.css');
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
File diff suppressed because one or more lines are too long
+335
-115
@@ -4,7 +4,7 @@ import { makeStyles } from '@material-ui/styles';
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import {Paper, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress } from '@material-ui/core';
|
||||
import {FormControl, InputLabel, Paper, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress } from '@material-ui/core';
|
||||
|
||||
import {Edit as EditIcon, FileCopy as FileCopyIcon, Publish as PublishIcon, SelectAll as SelectAllIcon, OpenInNew as OpenInNewIcon, CloudDownload as CloudDownloadIcon, Description as DescriptionIcon, Polymer as PolymerIcon, CheckCircle as CheckCircleIcon, Close as CloseIcon, Apps as AppsIcon, Image as ImageIcon, Delete as DeleteIcon, Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, Eco as EcoIcon, Schedule as ScheduleIcon, Cloud as CloudIcon, Business as BusinessIcon} from '@material-ui/icons';
|
||||
|
||||
@@ -29,6 +29,7 @@ const Admin = (props) => {
|
||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||
const [orgRequest, setOrgRequest] = React.useState(true);
|
||||
const [modalUser, setModalUser] = React.useState({});
|
||||
const [orgName, setOrgName] = React.useState("")
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
|
||||
const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false);
|
||||
@@ -48,7 +49,10 @@ const Admin = (props) => {
|
||||
const [authentication, setAuthentication] = React.useState([]);
|
||||
const [schedules, setSchedules] = React.useState([])
|
||||
const [files, setFiles] = React.useState([])
|
||||
const [selectedNamespace, setSelectedNamespace] = React.useState("default")
|
||||
const [fileNamespaces, setFileNamespaces] = React.useState([]);
|
||||
const [selectedUser, setSelectedUser] = React.useState({})
|
||||
const [newUsername, setNewUsername] = React.useState("");
|
||||
const [newPassword, setNewPassword] = React.useState("");
|
||||
const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false)
|
||||
const [selectedAuthentication, setSelectedAuthentication] = React.useState({})
|
||||
@@ -155,7 +159,7 @@ const Admin = (props) => {
|
||||
setTimeout(() => {
|
||||
getAppAuthentication()
|
||||
}, 1000)
|
||||
alert.success("Successfully deleted authentication!")
|
||||
//alert.success("Successfully deleted authentication!")
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -184,8 +188,10 @@ const Admin = (props) => {
|
||||
if (responseJson["success"] === false) {
|
||||
alert.error("Failed stopping schedule")
|
||||
} else {
|
||||
getSchedules()
|
||||
alert.success("Successfully stopped schedule!")
|
||||
setTimeout(() => {
|
||||
getSchedules()
|
||||
}, 1500)
|
||||
//alert.success("Successfully stopped schedule!")
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -365,6 +371,45 @@ const Admin = (props) => {
|
||||
});
|
||||
}
|
||||
|
||||
const createSubOrg = (currentOrgId, name) => {
|
||||
const data = { "name": name, "org_id": currentOrgId}
|
||||
console.log(data)
|
||||
const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org`
|
||||
|
||||
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) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
alert.error(responseJson.reason)
|
||||
} else {
|
||||
alert.error("Failed creating suborg")
|
||||
}
|
||||
} else {
|
||||
alert.success("Successfully created suborg!")
|
||||
setSelectedUserModalOpen(false)
|
||||
}
|
||||
|
||||
setOrgName("")
|
||||
setModalOpen(false)
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
alert.error("Err: " + error.toString())
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const onPasswordChange = () => {
|
||||
const data = { "username": selectedUser.username, "newpassword": newPassword }
|
||||
const url = globalUrl + '/api/v1/users/passwordchange';
|
||||
@@ -457,6 +502,10 @@ const Admin = (props) => {
|
||||
if (responseJson["success"] === false) {
|
||||
alert.error("Failed getting org: ", responseJson.readon)
|
||||
} else {
|
||||
if (responseJson.sync_features === undefined || responseJson.sync_features === null) {
|
||||
responseJson.sync_features = {}
|
||||
|
||||
}
|
||||
setSelectedOrganization(responseJson)
|
||||
var lists = {
|
||||
"active": {
|
||||
@@ -559,19 +608,19 @@ const Admin = (props) => {
|
||||
}
|
||||
|
||||
// Horrible frontend fix for environments
|
||||
const setDefaultEnvironment = (name) => {
|
||||
// FIXME - add some check here ROFL
|
||||
alert.info("Setting default env to " + name)
|
||||
const setDefaultEnvironment = (environment) => {
|
||||
// FIXME - add more checks to this
|
||||
alert.info("Setting default env to " + environment.name)
|
||||
var newEnv = []
|
||||
for (var key in environments) {
|
||||
if (environments[key].Name == name) {
|
||||
if (environments[key].id == environment.id) {
|
||||
if (environments[key].archived) {
|
||||
alert.error("Can't set archived to default")
|
||||
return
|
||||
}
|
||||
|
||||
environments[key].default = true
|
||||
} else if (environments[key].default == true && environments[key].name !== name) {
|
||||
} else if (environments[key].default == true && environments[key].id !== environment.id) {
|
||||
environments[key].default = false
|
||||
}
|
||||
|
||||
@@ -592,11 +641,15 @@ const Admin = (props) => {
|
||||
response.json().then(responseJson => {
|
||||
if (responseJson["success"] === false) {
|
||||
alert.error(responseJson.reason)
|
||||
getEnvironments()
|
||||
setTimeout(() => {
|
||||
getEnvironments()
|
||||
}, 1500)
|
||||
} else {
|
||||
setLoginInfo("")
|
||||
setModalOpen(false)
|
||||
getEnvironments()
|
||||
setTimeout(() => {
|
||||
getEnvironments()
|
||||
}, 1500)
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -632,23 +685,46 @@ const Admin = (props) => {
|
||||
})
|
||||
}
|
||||
|
||||
const deleteEnvironment = (name) => {
|
||||
const deleteEnvironment = (environment) => {
|
||||
// FIXME - add some check here ROFL
|
||||
alert.info("Deleting environment " + name)
|
||||
//const name = environment.name
|
||||
|
||||
//alert.info("Modifying environment " + name)
|
||||
//var newEnv = []
|
||||
//for (var key in environments) {
|
||||
// if (environments[key].Name == name) {
|
||||
// if (environments[key].default) {
|
||||
// alert.error("Can't modify the default environment")
|
||||
// return
|
||||
// }
|
||||
|
||||
// if (environments[key].type === "cloud" && !environments[key].archived) {
|
||||
// alert.error("Can't modify cloud environments")
|
||||
// return
|
||||
// }
|
||||
|
||||
// environments[key].archived = !environments[key].archived
|
||||
// }
|
||||
|
||||
// newEnv.push(environments[key])
|
||||
//}
|
||||
const id = environment.id
|
||||
|
||||
//alert.info("Modifying environment " + environment.Name)
|
||||
var newEnv = []
|
||||
for (var key in environments) {
|
||||
if (environments[key].Name == name) {
|
||||
if (environments[key].id == id) {
|
||||
if (environments[key].default) {
|
||||
alert.error("Can't delete the default environment")
|
||||
alert.error("Can't modify the default environment")
|
||||
return
|
||||
}
|
||||
|
||||
if (environments[key].type === "cloud") {
|
||||
alert.error("Can't delete the cloud environments")
|
||||
if (environments[key].type === "cloud" && !environments[key].archived) {
|
||||
alert.error("Can't modify cloud environments")
|
||||
return
|
||||
}
|
||||
|
||||
environments[key].archived = true
|
||||
environments[key].archived = !environments[key].archived
|
||||
}
|
||||
|
||||
newEnv.push(environments[key])
|
||||
@@ -795,8 +871,16 @@ const Admin = (props) => {
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
//console.log(responseJson)
|
||||
setFiles(responseJson)
|
||||
if (responseJson.files !== undefined && responseJson.files !== null) {
|
||||
setFiles(responseJson.files)
|
||||
} else {
|
||||
setFiles([])
|
||||
}
|
||||
|
||||
console.log("NAMESPACES: ", responseJson.namespaces)
|
||||
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null) {
|
||||
setFileNamespaces(responseJson.namespaces)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert.error(error.toString())
|
||||
@@ -931,6 +1015,9 @@ const Admin = (props) => {
|
||||
}
|
||||
|
||||
const getOrgs = () => {
|
||||
// API no longer in use, as it's in handleInfo request
|
||||
return
|
||||
|
||||
fetch(globalUrl + "/api/v1/orgs", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -1118,6 +1205,7 @@ const Admin = (props) => {
|
||||
alert.error("Failed setting user: " + responseJson.reason)
|
||||
} else {
|
||||
alert.success("Set the user field " + field + " to " + value)
|
||||
setSelectedUserModalOpen(false)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -1257,6 +1345,44 @@ const Admin = (props) => {
|
||||
>
|
||||
<DialogTitle><span style={{ color: "white" }}><EditIcon style={{marginTop: 5}}/> Editing {selectedUser.username}</span></DialogTitle>
|
||||
<DialogContent>
|
||||
{isCloud ?
|
||||
null
|
||||
:
|
||||
<div style={{ display: "flex" }}>
|
||||
<TextField
|
||||
style={{ marginTop: 0, backgroundColor: theme.palette.inputColor, flex: 3 , marginRight: 10,}}
|
||||
InputProps={{
|
||||
style: {
|
||||
height: 50,
|
||||
color: "white",
|
||||
},
|
||||
}}
|
||||
color="primary"
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder="New username"
|
||||
type="text"
|
||||
id="standard-required"
|
||||
autoComplete="username"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
defaultValue={selectedUser.username}
|
||||
onChange={e => {
|
||||
setNewUsername(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
style={{ maxHeight: 50, flex: 1 }}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setUser(selectedUser.id, "username", newUsername)
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
{isCloud ?
|
||||
null
|
||||
:
|
||||
@@ -1543,7 +1669,7 @@ const Admin = (props) => {
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{selectedOrganization.name.length > 0 ?
|
||||
<OrgHeader setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
|
||||
<OrgHeader userdata={userdata} setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
|
||||
:
|
||||
<div style={{paddingTop: 250, width: 250, margin: "auto", textAlign: "center"}}>
|
||||
<CircularProgress />
|
||||
@@ -1657,65 +1783,65 @@ const Admin = (props) => {
|
||||
</div>
|
||||
}
|
||||
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>Cloud sync features</Typography>
|
||||
<Grid container style={{width: "100%", marginBottom: 15, }}>
|
||||
{Object.keys(selectedOrganization.sync_features).map(function(key, index) {
|
||||
if (key === "schedule") {
|
||||
return null
|
||||
}
|
||||
<Grid container style={{width: "100%", marginBottom: 15, }}>
|
||||
{selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? null : Object.keys(selectedOrganization.sync_features).map(function(key, index) {
|
||||
if (key === "schedule") {
|
||||
return null
|
||||
}
|
||||
|
||||
const item = selectedOrganization.sync_features[key]
|
||||
const newkey = key.replaceAll("_", " ")
|
||||
const griditem = {
|
||||
"primary": newkey,
|
||||
"secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
|
||||
"limit": item.limit,
|
||||
"usage": 0,
|
||||
"data_collection": "None",
|
||||
"active": item.active,
|
||||
"icon": <PolymerIcon style={{color: itemColor}}/>,
|
||||
}
|
||||
const item = selectedOrganization.sync_features[key]
|
||||
const newkey = key.replaceAll("_", " ")
|
||||
const griditem = {
|
||||
"primary": newkey,
|
||||
"secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
|
||||
"limit": item.limit,
|
||||
"usage": 0,
|
||||
"data_collection": "None",
|
||||
"active": item.active,
|
||||
"icon": <PolymerIcon style={{color: itemColor}}/>,
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom key={index} >
|
||||
<GridItem data={griditem} />
|
||||
</Zoom>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
||||
{isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ?
|
||||
<div style={{marginTop: 30, marginBottom: 20}}>
|
||||
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>
|
||||
Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""}
|
||||
</Typography>
|
||||
<Grid container spacing={3} style={{marginTop: 15}}>
|
||||
{selectedOrganization.subscriptions.reverse().map((sub, index) => {
|
||||
return (
|
||||
<Grid item key={index} xs={4}>
|
||||
<Card elevation={6} style={{backgroundColor: theme.palette.inputColor, color: "white", padding: 25, textAlign: "left",}}>
|
||||
<b>Type</b>: {sub.level}<div/>
|
||||
<b>Recurrence</b>: {sub.recurrence}<div/>
|
||||
{sub.active ?
|
||||
<div>
|
||||
<b>Started</b>: {new Date(sub.startdate*1000).toISOString()}<div/>
|
||||
<Button variant="outlined" color="primary" style={{marginTop: 15}} onClick={() => {
|
||||
cancelSubscriptions(sub.reference)
|
||||
}}>
|
||||
Cancel subscription
|
||||
</Button>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<b>Cancelled</b>: {new Date(sub.cancellationdate*1000).toISOString()}<div/>
|
||||
<Typography color="textSecondary">
|
||||
<b>Status</b>: Deactivated
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
return (
|
||||
<Zoom key={index} >
|
||||
<GridItem data={griditem} />
|
||||
</Zoom>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
||||
{isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ?
|
||||
<div style={{marginTop: 30, marginBottom: 20}}>
|
||||
<Typography style={{marginTop: 40, marginLeft: 10, marginBottom: 5,}}>
|
||||
Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""}
|
||||
</Typography>
|
||||
<Grid container spacing={3} style={{marginTop: 15}}>
|
||||
{selectedOrganization.subscriptions.reverse().map((sub, index) => {
|
||||
return (
|
||||
<Grid item key={index} xs={4}>
|
||||
<Card elevation={6} style={{backgroundColor: theme.palette.inputColor, color: "white", padding: 25, textAlign: "left",}}>
|
||||
<b>Type</b>: {sub.level}<div/>
|
||||
<b>Recurrence</b>: {sub.recurrence}<div/>
|
||||
{sub.active ?
|
||||
<div>
|
||||
<b>Started</b>: {new Date(sub.startdate*1000).toISOString()}<div/>
|
||||
<Button variant="outlined" color="primary" style={{marginTop: 15}} onClick={() => {
|
||||
cancelSubscriptions(sub.reference)
|
||||
}}>
|
||||
Cancel subscription
|
||||
</Button>
|
||||
</div>
|
||||
:
|
||||
<div>
|
||||
<b>Cancelled</b>: {new Date(sub.cancellationdate*1000).toISOString()}<div/>
|
||||
<Typography color="textSecondary">
|
||||
<b>Status</b>: Deactivated
|
||||
</Typography>
|
||||
</div>
|
||||
}
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
<Divider style={{ marginTop: 20, backgroundColor: theme.palette.inputColor }} />
|
||||
</div>
|
||||
@@ -1744,14 +1870,20 @@ const Admin = (props) => {
|
||||
}}
|
||||
>
|
||||
<DialogTitle><span style={{ color: "white" }}>
|
||||
{curTab === 1 ? "Add user" : "Add environment"}
|
||||
{curTab === 1 ? "Add user" : curTab === 6 ? "Add Sub-Organization" : "Add environment"}
|
||||
</span></DialogTitle>
|
||||
<DialogContent>
|
||||
{curTab === 1 && isCloud ?
|
||||
<Typography variant="body1" style={{marginBottom: 10}}>
|
||||
We'll send an email to invite them to your organization.
|
||||
</Typography>
|
||||
: null}
|
||||
:
|
||||
curTab === 6 ?
|
||||
<Typography variant="body1" style={{marginBottom: 10}}>
|
||||
The organization created will become a child of your current organization, and be available to you.
|
||||
</Typography>
|
||||
:
|
||||
null }
|
||||
{curTab === 1 ?
|
||||
<div>
|
||||
Username
|
||||
@@ -1801,6 +1933,31 @@ const Admin = (props) => {
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
: curTab === 6 ?
|
||||
<div>
|
||||
Name
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
placeholder={`${selectedOrganization.name} Copycat Inc.`}
|
||||
id="orgname"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setOrgName(event.target.value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
: curTab === 5 ?
|
||||
<div>
|
||||
Environment Name
|
||||
@@ -1838,6 +1995,8 @@ const Admin = (props) => {
|
||||
} else {
|
||||
submitUser(modalUser)
|
||||
}
|
||||
} else if (curTab === 6) {
|
||||
createSubOrg(selectedOrganization.id, orgName)
|
||||
} else if (curTab === 5) {
|
||||
submitEnvironment(modalUser)
|
||||
}
|
||||
@@ -1889,7 +2048,11 @@ const Admin = (props) => {
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Active"
|
||||
style={{ minWidth: 180, maxWidth: 180 }}
|
||||
style={{ minWidth: 150, maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Type"
|
||||
style={{ minWidth: 150 , maxWidth: 150 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Actions"
|
||||
@@ -1949,10 +2112,9 @@ const Admin = (props) => {
|
||||
value={data.role}
|
||||
fullWidth
|
||||
onChange={(e) => {
|
||||
console.log("VALUE: ", e.target.value)
|
||||
|
||||
setUser(data.id, "role", e.target.value)
|
||||
}}
|
||||
console.log("VALUE: ", e.target.value)
|
||||
setUser(data.id, "role", e.target.value)
|
||||
}}
|
||||
style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }}
|
||||
>
|
||||
<MenuItem style={{ backgroundColor: theme.palette.inputColor, color: "white" }} value={"admin"}>
|
||||
@@ -1965,10 +2127,14 @@ const Admin = (props) => {
|
||||
}
|
||||
style ={{ minWidth: 135, maxWidth: 135, marginRight: 15,}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.active ? "True" : "False"}
|
||||
style={{ minWidth: 180, maxWidth: 180 }}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.active ? "True" : "False"}
|
||||
style={{ minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.login_type === undefined || data.login_type === null || data.login_type.length === 0 ? "Normal" : data.login_type}
|
||||
style={{ minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText style={{ display: "flex" }}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
@@ -2021,7 +2187,9 @@ const Admin = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
getFiles()
|
||||
setTimeout(() => {
|
||||
getFiles()
|
||||
}, 2500)
|
||||
}
|
||||
|
||||
const uploadFile = (e) => {
|
||||
@@ -2061,6 +2229,30 @@ const Admin = (props) => {
|
||||
>
|
||||
<CachedIcon />
|
||||
</Button>
|
||||
|
||||
{fileNamespaces !== undefined && fileNamespaces !== null && fileNamespaces.length > 1 ?
|
||||
<FormControl>
|
||||
<InputLabel id="input-namespace-label">Namespace</InputLabel>
|
||||
<Select
|
||||
labelId="input-namespace-select-label"
|
||||
id="input-namespace-select-id"
|
||||
style={{color: "white", minWidth: 100, float: "right",}}
|
||||
value={selectedNamespace}
|
||||
onChange={(event) => {
|
||||
console.log("CHANGE NAMESPACE: ", event.target)
|
||||
setSelectedNamespace(event.target.value)
|
||||
}}
|
||||
>
|
||||
{fileNamespaces.map((data, index) => {
|
||||
return (
|
||||
<MenuItem key={index} value={data} style={{color: "white"}}>{data}</MenuItem>
|
||||
)
|
||||
})}
|
||||
|
||||
</Select>
|
||||
</FormControl>
|
||||
: null}
|
||||
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
|
||||
<List>
|
||||
<ListItem>
|
||||
@@ -2095,7 +2287,15 @@ const Admin = (props) => {
|
||||
primary="File ID"
|
||||
/>
|
||||
</ListItem>
|
||||
{files === undefined || files === null ? null : files.map((file, index) => {
|
||||
{files === undefined || files === null || files.length === 0 ? null : files.map((file, index) => {
|
||||
if (file.namespace === "") {
|
||||
file.namespace = "default"
|
||||
}
|
||||
|
||||
if (file.namespace !== selectedNamespace) {
|
||||
return null
|
||||
}
|
||||
|
||||
var bgColor = "#27292d"
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023"
|
||||
@@ -2303,13 +2503,13 @@ const Admin = (props) => {
|
||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||
/>
|
||||
</ListItem>
|
||||
{categories.map(data => {
|
||||
{categories.map((data, index) => {
|
||||
if (data.apps.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<ListItem key={index}>
|
||||
<ListItemText
|
||||
primary={data.name}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
@@ -2409,6 +2609,7 @@ const Admin = (props) => {
|
||||
bgColor = "#1f2023"
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<ListItem key={index} style={{backgroundColor: bgColor}}>
|
||||
<ListItemText
|
||||
@@ -2489,7 +2690,7 @@ const Admin = (props) => {
|
||||
<div>
|
||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||
<h2 style={{display: "inline",}}>Environments</h2>
|
||||
<span style={{marginLeft: 25}}>Decides what Orborus environment to execute an action in a workflow in.<a target="_blank" href="https://shuffler.io/docs/organizations#environments" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a></span>
|
||||
<span style={{marginLeft: 25}}>Decides what Orborus environment to execute an action in a workflow in. <a target="_blank" href="https://shuffler.io/docs/organizations#environments" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more</a></span>
|
||||
</div>
|
||||
<Button
|
||||
style={{}}
|
||||
@@ -2572,13 +2773,13 @@ const Admin = (props) => {
|
||||
{environment.default ?
|
||||
null
|
||||
:
|
||||
<Button variant="outlined" style={{borderRadius: "0px"}} onClick={() => setDefaultEnvironment(environment.Name)} color="primary">Set default</Button>
|
||||
<Button variant="outlined" style={{borderRadius: "0px"}} onClick={() => setDefaultEnvironment(environment)} color="primary">Set default</Button>
|
||||
}
|
||||
</ListItemText>
|
||||
<ListItemText
|
||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||
>
|
||||
<Button disabled={environment.archived} variant="outlined" style={{borderRadius: "0px"}} onClick={() => deleteEnvironment(environment.Name)} color="primary">Archive</Button>
|
||||
<Button variant={environment.archived ? "contained" : "outlined"} style={{borderRadius: "0px"}} onClick={() => deleteEnvironment(environment)} color="primary">{environment.archived ? "Activate" : "Disable"}</Button>
|
||||
{/*<Button disabled={environment.archived} variant="outlined" style={{borderRadius: "0px"}} onClick={() => flushQueue(environment.Name)} color="primary">Flush Queue</Button>*/}
|
||||
</ListItemText>
|
||||
<ListItemText
|
||||
@@ -2592,7 +2793,7 @@ const Admin = (props) => {
|
||||
</div>
|
||||
: null
|
||||
|
||||
const organizationsTab = curTab === 7 ?
|
||||
const organizationsTab = curTab === 6 ?
|
||||
<div>
|
||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||
<h2 style={{display: "inline",}}>Organizations</h2>
|
||||
@@ -2602,28 +2803,31 @@ const Admin = (props) => {
|
||||
style={{}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled
|
||||
onClick={() => {
|
||||
setModalOpen(true)
|
||||
}}
|
||||
>
|
||||
Add organization
|
||||
Add suborganization
|
||||
</Button>
|
||||
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor}}/>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
primary="Logo"
|
||||
style={{minWidth: 100, maxWidth: 100}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="id"
|
||||
style={{minWidth: 200, maxWidth: 200}}
|
||||
primary="Name"
|
||||
style={{minWidth: 250, maxWidth: 250}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Your role"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="id"
|
||||
style={{minWidth: 400, maxWidth: 400}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary="Selected"
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
@@ -2633,25 +2837,41 @@ const Admin = (props) => {
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
</ListItem>
|
||||
{organizations !== undefined && organizations !== null && organizations.length > 0 ?
|
||||
{userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
|
||||
<span>
|
||||
{organizations.map((data, index) => {
|
||||
{userdata.orgs.map((data, index) => {
|
||||
const isSelected = props.userdata.active_org.id === undefined ? "False" : props.userdata.active_org.id === data.id ? "True" : "False"
|
||||
|
||||
const imagesize = 40
|
||||
const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", }
|
||||
const image = data.image === "" ?
|
||||
<img alt={data.name} src={theme.palette.defaultImage} style={imageStyle} />
|
||||
:
|
||||
<img alt={data.name} src={data.image} style={imageStyle} />
|
||||
|
||||
var bgColor = "#27292d"
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023"
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem key={index}>
|
||||
<ListItem key={index} style={{backgroundColor: bgColor,}}>
|
||||
<ListItemText
|
||||
primary={data.name}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
primary={image}
|
||||
style={{minWidth: 100, maxWidth: 100}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.id}
|
||||
style={{minWidth: 200, maxWidth: 200}}
|
||||
primary={data.name}
|
||||
style={{minWidth: 250, maxWidth: 250}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.role}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={data.id}
|
||||
style={{minWidth: 400, maxWidth: 400}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={isSelected}
|
||||
style={{minWidth: 150, maxWidth: 150}}
|
||||
@@ -2673,7 +2893,7 @@ const Admin = (props) => {
|
||||
</div>
|
||||
: null
|
||||
|
||||
const hybridTab = curTab === 6 ?
|
||||
const hybridTab = curTab === 7 ?
|
||||
<div>
|
||||
<div style={{marginTop: 20, marginBottom: 20,}}>
|
||||
<h2 style={{display: "inline",}}>Hybrid</h2>
|
||||
@@ -2719,7 +2939,7 @@ const Admin = (props) => {
|
||||
|
||||
const iconStyle = {marginRight: 10}
|
||||
const data =
|
||||
<div style={{width: 1366, margin: "auto", overflowX: "hidden", marginTop: 25,}}>
|
||||
<div style={{width: 1300, margin: "auto", overflowX: "hidden", marginTop: 25,}}>
|
||||
<Paper style={paperStyle}>
|
||||
<Tabs
|
||||
value={curTab}
|
||||
@@ -2733,21 +2953,21 @@ const Admin = (props) => {
|
||||
<Tab label=<span><DescriptionIcon style={iconStyle} />Files</span> />
|
||||
<Tab label=<span><ScheduleIcon style={iconStyle} />Schedules</span> />
|
||||
{isCloud ? null : <Tab label=<span><EcoIcon style={iconStyle} />Environments</span>/>}
|
||||
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null}
|
||||
{window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/> : null}
|
||||
{window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null}
|
||||
{isCloud ? null : <Tab label=<span><BusinessIcon style={iconStyle} /> Organizations</span>/>}
|
||||
{/*window.location.protocol == "http:" && window.location.port === "3000" ? <Tab label=<span><CloudIcon style={iconStyle} /> Hybrid</span>/> : null*/}
|
||||
{/*window.location.protocol === "http:" && window.location.port === "3000" ? <Tab label=<span><LockIcon style={iconStyle} />Categories</span>/> : null*/}
|
||||
</Tabs>
|
||||
<Divider style={{marginTop: 0, marginBottom: 10, backgroundColor: "rgb(91, 96, 100)"}} />
|
||||
<div style={{padding: 15}}>
|
||||
{organizationView}
|
||||
{authenticationView}
|
||||
{appCategoryView}
|
||||
{usersView}
|
||||
{environmentView}
|
||||
{schedulesView}
|
||||
{filesView}
|
||||
{hybridTab}
|
||||
{organizationsTab}
|
||||
{appCategoryView}
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
+50
-44
@@ -9,7 +9,6 @@ import { useTheme } from '@material-ui/core/styles';
|
||||
|
||||
import YAML from 'yaml'
|
||||
import {Link} from 'react-router-dom';
|
||||
import ReactJson from 'react-json-view'
|
||||
import { useAlert } from "react-alert";
|
||||
import Dropzone from '../components/Dropzone';
|
||||
|
||||
@@ -130,6 +129,7 @@ const Apps = (props) => {
|
||||
const upload = React.useRef(null);
|
||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" ? true : false
|
||||
const borderRadius = 3
|
||||
const viewWidth = 590
|
||||
|
||||
const { start, stop } = useInterval({
|
||||
duration: 5000,
|
||||
@@ -178,6 +178,7 @@ const Apps = (props) => {
|
||||
color: "#ffffff",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
margin: "auto",
|
||||
}
|
||||
|
||||
const paperAppStyle = {
|
||||
@@ -474,13 +475,14 @@ const Apps = (props) => {
|
||||
|
||||
const dividerColor = "rgb(225, 228, 232)"
|
||||
const uploadViewPaperStyle = {
|
||||
minWidth: 662.5,
|
||||
maxWidth: 662.5,
|
||||
minWidth: viewWidth,
|
||||
maxWidth: viewWidth,
|
||||
color: "white",
|
||||
borderRadius: 5,
|
||||
backgroundColor: surfaceColor,
|
||||
display: "flex",
|
||||
//display: "flex",
|
||||
marginBottom: 10,
|
||||
overflow: "hidden",
|
||||
}
|
||||
|
||||
const UploadView = () => {
|
||||
@@ -520,7 +522,7 @@ const Apps = (props) => {
|
||||
<Link to={editUrl} style={{textDecoration: "none"}}>
|
||||
<Tooltip title={"Edit OpenAPI app"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
variant="contained"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{marginTop: 10, marginRight: 10,}}
|
||||
@@ -620,15 +622,6 @@ const Apps = (props) => {
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
{/*
|
||||
<ReactJson
|
||||
src={JSON.parse(showResult)}
|
||||
theme="solarized"
|
||||
collapsed={false}
|
||||
displayDataTypes={true}
|
||||
name={"Example return value"}
|
||||
/>
|
||||
*/}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -707,8 +700,8 @@ const Apps = (props) => {
|
||||
{activateButton}
|
||||
{(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ?
|
||||
<div>
|
||||
{downloadButton}
|
||||
{editButton}
|
||||
{downloadButton}
|
||||
{deleteButton}
|
||||
</div>
|
||||
: null}
|
||||
@@ -771,7 +764,7 @@ const Apps = (props) => {
|
||||
{/*<p><b>Owner:</b> {selectedApp.owner}</p>*/}
|
||||
{selectedApp.privateId !== undefined && selectedApp.privateId.length > 0 ? <p><b>PrivateID:</b> {selectedApp.privateId}</p> : null}
|
||||
<Divider style={{marginBottom: 10, marginTop: 10, backgroundColor: dividerColor}}/>
|
||||
<div style={{padding: 20}}>
|
||||
<div style={{paddingTop: 20, paddingBottom: 20, }}>
|
||||
{selectedApp.link.length > 0 ? <p><b>URL:</b> {selectedApp.link}</p> : null}
|
||||
<div style={{marginTop: 15, marginBottom: 15}}>
|
||||
<b>Actions</b>
|
||||
@@ -852,7 +845,7 @@ const Apps = (props) => {
|
||||
<h2>App Creator</h2>
|
||||
<a rel="norefferer" href="https://shuffler.io/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a>
|
||||
- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
|
||||
- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
|
||||
- <a href="https://github.com/APIs-guru/openapi-directory/tree/main/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/>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
@@ -932,7 +925,11 @@ const Apps = (props) => {
|
||||
console.log("Error in dropzone: ", e)
|
||||
}
|
||||
|
||||
reader.readAsText(files[0]);
|
||||
try {
|
||||
reader.readAsText(files[0]);
|
||||
} catch(error) {
|
||||
alert.error("Failed to read file")
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -950,9 +947,9 @@ const Apps = (props) => {
|
||||
}, [appValidation, isDropzone]);
|
||||
|
||||
const appView = isLoggedIn ?
|
||||
<Dropzone style={{maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}>
|
||||
<Dropzone style={{width: viewWidth*2+20, margin: "auto", padding: 20 }} onDrop={uploadFile}>
|
||||
<div style={appViewStyle}>
|
||||
<div style={{flex: 1, }}>
|
||||
<div style={{flex: 1, maxWidth: viewWidth, marginRight: 10,}}>
|
||||
<Breadcrumbs aria-label="breadcrumb" separator="›" style={{color: "white",}}>
|
||||
<Link to="/apps" style={{textDecoration: "none", color: "inherit",}}>
|
||||
<Typography variant="h6" style={{color: "rgba(255,255,255,0.5)"}}>
|
||||
@@ -969,9 +966,9 @@ const Apps = (props) => {
|
||||
: null}
|
||||
</Breadcrumbs>
|
||||
<div style={{marginTop: 15}} />
|
||||
<UploadView/>
|
||||
<UploadView />
|
||||
</div>
|
||||
<div style={{flex: 1, marginLeft: 10, marginRight: 10, }}>
|
||||
<div style={{flex: 1, marginLeft: 10, maxWidth: viewWidth, }}>
|
||||
<div style={{display: "flex",}}>
|
||||
<div style={{flex: 1, marginBottom: 15, }}>
|
||||
<Typography variant="h6">
|
||||
@@ -980,35 +977,39 @@ const Apps = (props) => {
|
||||
</div>
|
||||
{isCloud ? null :
|
||||
<span>
|
||||
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{margin: 5, maxHeight: 50, marginTop: 10}}
|
||||
onClick={() => {
|
||||
hotloadApps()
|
||||
}}
|
||||
>
|
||||
<CachedIcon />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{isLoading ? null :
|
||||
<Tooltip title={"Reload apps locally"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{margin: 5, maxHeight: 50, marginTop: 10}}
|
||||
disabled={isLoading}
|
||||
onClick={() => {
|
||||
hotloadApps()
|
||||
}}
|
||||
>
|
||||
{isLoading ? <CircularProgress size={25} /> : <CachedIcon />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
<Tooltip title={"Download from Github"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component="label"
|
||||
color="primary"
|
||||
style={{margin: 5, maxHeight: 50, marginTop: 10}}
|
||||
disabled={isLoading}
|
||||
onClick={() => {
|
||||
setOpenApi(baseRepository)
|
||||
setLoadAppsModalOpen(true)
|
||||
}}
|
||||
>
|
||||
<CloudDownloadIcon />
|
||||
{isLoading ? <CircularProgress size={25} /> : <CloudDownloadIcon />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div style={{height: 50}}>
|
||||
<TextField
|
||||
@@ -1072,9 +1073,12 @@ const Apps = (props) => {
|
||||
<CircularProgress style={{width: 40, height: 40, margin: "auto"}}/>
|
||||
:
|
||||
<Paper square style={uploadViewPaperStyle}>
|
||||
<h4 style={{margin: 10}}>
|
||||
<Typography variant="body1" style={{margin: 10}}>
|
||||
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>
|
||||
</Typography>
|
||||
<Typography variant="body1" style={{margin: 10}}>
|
||||
If you're still not able to see any apps, please follow our <a href={"https://shuffler.io/docs/troubleshooting#load_all_apps_locally"} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">troubleshooting guide for loading apps!</a>
|
||||
</Typography>
|
||||
</Paper>
|
||||
}
|
||||
</div>
|
||||
@@ -1571,11 +1575,13 @@ const Apps = (props) => {
|
||||
<Button style={{borderRadius: "0px"}} onClick={() => setLoadAppsModalOpen(false)} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button style={{borderRadius: "0px"}} disabled={openApi.length === 0 || !openApi.includes("http")} onClick={() => {
|
||||
handleGithubValidation(true)
|
||||
}} color="primary">
|
||||
Force update
|
||||
</Button>
|
||||
{isCloud ? null :
|
||||
<Button style={{borderRadius: "0px"}} disabled={openApi.length === 0 || !openApi.includes("http")} onClick={() => {
|
||||
handleGithubValidation(true)
|
||||
}} color="primary">
|
||||
Force update
|
||||
</Button>
|
||||
}
|
||||
<Button variant="outlined" style={{float: "left", borderRadius: "0px"}} disabled={openApi.length === 0 || !openApi.includes("http")} onClick={() => {
|
||||
handleGithubValidation(false)
|
||||
}} color="primary">
|
||||
|
||||
+183
-21
@@ -5,14 +5,15 @@ import ReactMarkdown from 'react-markdown';
|
||||
import {BrowserView, MobileView} from "react-device-detect";
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import {Divider, Button, Menu, MenuItem, Typography, Paper, List} from '@material-ui/core';
|
||||
import {Tooltip, Divider, Button, Menu, MenuItem, Typography, Paper, List} from '@material-ui/core';
|
||||
import {Link as LinkIcon, Edit as EditIcon} from '@material-ui/icons';
|
||||
|
||||
const Body = {
|
||||
maxWidth: '1000px',
|
||||
minWidth: '768px',
|
||||
margin: 'auto',
|
||||
display: "flex",
|
||||
heigth: "100%",
|
||||
height: "100%",
|
||||
color: "white",
|
||||
//textAlign: "center",
|
||||
};
|
||||
@@ -23,15 +24,24 @@ const hrefStyle = {
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const innerHrefStyle = {
|
||||
color: "rgba(255, 255, 255, 0.75)",
|
||||
textDecoration: "none"
|
||||
}
|
||||
|
||||
const Docs = (props) => {
|
||||
const { globalUrl, selectedDoc, serverside, isMobile, } = props;
|
||||
|
||||
const theme = useTheme();
|
||||
const [mobile, setMobile] = useState(isMobile === true ? true : false);
|
||||
const [data, setData] = useState("");
|
||||
const [firstrequest, setFirstrequest] = useState(true);
|
||||
const [list, setList] = useState([]);
|
||||
const [, setListLoaded] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const [headingSet, setHeadingSet] = React.useState(false);
|
||||
const [selectedMeta, setSelectedMeta] = React.useState({link: "hello", read_time: 2, });
|
||||
const [tocLines, setTocLines] = React.useState([]);
|
||||
const [baseUrl, setBaseUrl] = React.useState(serverside === true ? "" : window.location.href)
|
||||
|
||||
function handleClick(event) {
|
||||
@@ -48,14 +58,14 @@ const Docs = (props) => {
|
||||
position: "relative",
|
||||
padding: 30,
|
||||
paddingTop: 15,
|
||||
borderRadius: 5,
|
||||
height: "80vh",
|
||||
marginTop: 15,
|
||||
}
|
||||
|
||||
const SideBar = {
|
||||
maxWidth: 250,
|
||||
flex: "1",
|
||||
position: "fixed",
|
||||
marginTop: 35,
|
||||
}
|
||||
|
||||
const fetchDocList = () => {
|
||||
@@ -71,7 +81,7 @@ const Docs = (props) => {
|
||||
if (responseJson.success) {
|
||||
setList(responseJson.list)
|
||||
} else {
|
||||
setList(["error"])
|
||||
setList(["# Error loading documentation. Please contact us if this persists."])
|
||||
}
|
||||
setListLoaded(true)
|
||||
})
|
||||
@@ -91,6 +101,58 @@ const Docs = (props) => {
|
||||
if (responseJson.success) {
|
||||
setData(responseJson.reason)
|
||||
document.title = "Shuffle "+docId+" documentation"
|
||||
|
||||
if (responseJson.meta !== undefined) {
|
||||
setSelectedMeta(responseJson.meta)
|
||||
}
|
||||
|
||||
//console.log("TOC list: ", responseJson.reason)
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null) {
|
||||
const splitkey = responseJson.reason.split("\n")
|
||||
var innerTocLines = []
|
||||
var record = false
|
||||
for (var key in splitkey) {
|
||||
const line = splitkey[key]
|
||||
//console.log("Line: ", line)
|
||||
if (line.toLowerCase().includes("table of contents")) {
|
||||
record = true
|
||||
continue
|
||||
}
|
||||
|
||||
if (record && line.length < 3) {
|
||||
record = false
|
||||
}
|
||||
|
||||
if (record) {
|
||||
const parsedline = line.split("](")
|
||||
if (parsedline.length > 1) {
|
||||
parsedline[0] = parsedline[0].replaceAll("*", "")
|
||||
parsedline[0] = parsedline[0].replaceAll("[", "")
|
||||
parsedline[0] = parsedline[0].replaceAll("]", "")
|
||||
parsedline[0] = parsedline[0].replaceAll("(", "")
|
||||
parsedline[0] = parsedline[0].replaceAll(")", "")
|
||||
parsedline[0] = parsedline[0].trim()
|
||||
|
||||
parsedline[1] = parsedline[1].replaceAll("*", "")
|
||||
parsedline[1] = parsedline[1].replaceAll("[", "")
|
||||
parsedline[1] = parsedline[1].replaceAll("]", "")
|
||||
parsedline[1] = parsedline[1].replaceAll(")", "")
|
||||
parsedline[1] = parsedline[1].replaceAll("(", "")
|
||||
parsedline[1] = parsedline[1].trim()
|
||||
//console.log(parsedline[0], parsedline[1])
|
||||
|
||||
innerTocLines.push({
|
||||
"text": parsedline[0],
|
||||
"link": parsedline[1]
|
||||
})
|
||||
} else {
|
||||
console.log("Bad line for parsing: ", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTocLines(innerTocLines)
|
||||
}
|
||||
} else {
|
||||
setData("# Error\nThis page doesn't exist.")
|
||||
}
|
||||
@@ -100,14 +162,21 @@ const Docs = (props) => {
|
||||
|
||||
if (firstrequest) {
|
||||
setFirstrequest(false)
|
||||
if (!serverside) {
|
||||
if (window.innerWidth < 768) {
|
||||
setMobile(true)
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedDoc !== undefined) {
|
||||
setData(selectedDoc.reason)
|
||||
setList(selectedDoc.list)
|
||||
setListLoaded(true)
|
||||
} else {
|
||||
fetchDocList()
|
||||
fetchDocs(props.match.params.key)
|
||||
if (!serverside) {
|
||||
fetchDocList()
|
||||
fetchDocs(props.match.params.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +187,7 @@ const Docs = (props) => {
|
||||
}
|
||||
|
||||
const parseElementScroll = () => {
|
||||
const offset = 45
|
||||
var parent = document.getElementById("markdown_wrapper_outer")
|
||||
if (parent !== null) {
|
||||
//console.log("IN PARENT")
|
||||
@@ -135,7 +205,12 @@ const Docs = (props) => {
|
||||
|
||||
// Fix location..
|
||||
if (element.innerHTML.toLowerCase() === name) {
|
||||
//console.log(element.offsetTop)
|
||||
element.scrollIntoView({behavior: "smooth"})
|
||||
//element.scrollTo({
|
||||
// top: element.offsetTop+offset,
|
||||
// behavior: "smooth"
|
||||
//})
|
||||
found = true
|
||||
//element.scrollTo({
|
||||
// top: element.offsetTop-100,
|
||||
@@ -147,7 +222,7 @@ const Docs = (props) => {
|
||||
// H#
|
||||
if (!found) {
|
||||
elements = parent.getElementsByTagName('h3')
|
||||
console.log(name)
|
||||
//console.log("NAMe: ", name)
|
||||
found = false
|
||||
for (key in elements) {
|
||||
const element = elements[key]
|
||||
@@ -158,6 +233,10 @@ const Docs = (props) => {
|
||||
// Fix location..
|
||||
if (element.innerHTML.toLowerCase() === name) {
|
||||
element.scrollIntoView({behavior: "smooth"})
|
||||
//element.scrollTo({
|
||||
// top: element.offsetTop-offset,
|
||||
// behavior: "smooth"
|
||||
//})
|
||||
found = true
|
||||
//element.scrollTo({
|
||||
// top: element.offsetTop-100,
|
||||
@@ -187,10 +266,10 @@ const Docs = (props) => {
|
||||
const markdownStyle = {
|
||||
color: "rgba(255, 255, 255, 0.65)",
|
||||
flex: "1",
|
||||
maxWidth: isMobile ? "100%" : 750,
|
||||
maxWidth: mobile ? "100%" : 750,
|
||||
overflow: "hidden",
|
||||
paddingBottom: 200,
|
||||
marginLeft: isMobile ? 0 : 275,
|
||||
marginLeft: mobile ? 0 : 275,
|
||||
}
|
||||
|
||||
function OuterLink(props) {
|
||||
@@ -214,12 +293,65 @@ const Docs = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
function Heading(props) {
|
||||
const element = React.createElement(`h${props.level}`, {style: {marginTop: 40}}, props.children)
|
||||
const Heading = (props) => {
|
||||
const element = React.createElement(`h${props.level}`, {style: {marginTop: props.level === 1 ? 20 : 50}}, props.children)
|
||||
const [hover, setHover] = useState(false)
|
||||
|
||||
var extraInfo = ""
|
||||
if (props.level === 1) {
|
||||
extraInfo =
|
||||
<div style={{backgroundColor: theme.palette.inputColor, padding: 15, borderRadius: theme.palette.borderRadius, marginBottom: 30, display: "flex",}}>
|
||||
<div style={{flex: 3, display: "flex", vAlign: "center",}}>
|
||||
{mobile ? null :
|
||||
<Typography style={{display: "inline", marginTop: 6, }}>
|
||||
<a rel="norefferer" target="_blank" href={selectedMeta.link} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
|
||||
<Button style={{}} variant="outlined">
|
||||
<EditIcon /> Edit
|
||||
</Button>
|
||||
</a>
|
||||
</Typography>
|
||||
}
|
||||
{mobile ? null :
|
||||
<div style={{height: "100%", width: 1, backgroundColor: "white", marginLeft: 50, marginRight: 50, }} />
|
||||
}
|
||||
<Typography style={{display: "inline", marginTop: 11, }}>
|
||||
{selectedMeta.read_time} minute{selectedMeta.read_time === 1 ? "" : "s"} to read
|
||||
</Typography>
|
||||
</div>
|
||||
<div style={{flex: 2}}>
|
||||
{mobile || selectedMeta.contributors === undefined || selectedMeta.contributors === null ? "" :
|
||||
<div style={{margin: 10, height: "100%", display: "inline",}}>
|
||||
{selectedMeta.contributors.slice(0,7).map((data, index) => {
|
||||
return (
|
||||
<a rel="norefferer" target="_blank" href={data.url} target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>
|
||||
<Tooltip title={data.url} placement="bottom">
|
||||
<img alt={data.url} src={data.image} style={{marginTop: 5, marginRight: 10, height: 40, borderRadius: 40, }} />
|
||||
</Tooltip>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<Typography>
|
||||
<Typography
|
||||
onMouseOver={() => {
|
||||
setHover(true)
|
||||
}} >
|
||||
{props.level !== 1 ? <Divider style={{width: "90%", marginTop: 40, backgroundColor: theme.palette.inputColor}} /> : null}
|
||||
{element}
|
||||
{/*hover ? <LinkIcon onMouseOver={() => {setHover(true)}} style={{cursor: "pointer", display: "inline", }} onClick={() => {
|
||||
window.location.href += "#hello"
|
||||
console.log(window.location)
|
||||
//window.history.pushState('page2', 'Title', '/page2.php');
|
||||
//window.history.replaceState('page2', 'Title', '/page2.php');
|
||||
}} />
|
||||
: ""
|
||||
*/}
|
||||
{extraInfo}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
@@ -234,19 +366,44 @@ const Docs = (props) => {
|
||||
// );
|
||||
//}
|
||||
|
||||
const postDataBrowser =
|
||||
const postDataBrowser = list === undefined || list === null ? null :
|
||||
<div style={Body}>
|
||||
<div style={SideBar}>
|
||||
<Paper style={SidebarPaperStyle}>
|
||||
<List style={{listStyle: "none", paddingLeft: "0", }}>
|
||||
{list.map((item, index) => {
|
||||
{list.map((data, index) => {
|
||||
const item = data.name
|
||||
if (item === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const path = "/docs/"+item
|
||||
const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
|
||||
const itemMatching = props.match.params.key.toLowerCase() === item.toLowerCase()
|
||||
//const [tocLines, setTocLines] = React.useState([]);
|
||||
return (
|
||||
<li key={index} style={{marginTop: 15,}}>
|
||||
<Link key={index} style={hrefStyle} to={path} onClick={() => {fetchDocs(item)}}>
|
||||
<Typography variant="h6"><b>{newname}</b></Typography>
|
||||
<li key={index} style={{marginTop: 10,}}>
|
||||
<Link key={index} style={hrefStyle} to={path} onClick={() => {
|
||||
setTocLines([])
|
||||
fetchDocs(item)
|
||||
}}>
|
||||
<Typography style={{color: itemMatching ? "#f86a3e" : "inherit"}} variant="body1"><b>> {newname}</b></Typography>
|
||||
</Link>
|
||||
{itemMatching && tocLines !== null && tocLines !== undefined && tocLines.length > 0 ?
|
||||
<div style={{marginLeft: 5}}>
|
||||
{tocLines.map((data, index) => {
|
||||
//console.log(data)
|
||||
|
||||
return (
|
||||
<Link key={index} style={innerHrefStyle} to={data.link} onClick={() => {}}>
|
||||
<Typography variant="body2" style={{cursor: "pointer"}}>
|
||||
- {data.text}
|
||||
</Typography>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
@@ -278,7 +435,7 @@ const Docs = (props) => {
|
||||
flexDirection: "column",
|
||||
}
|
||||
|
||||
const postDataMobile =
|
||||
const postDataMobile = list === undefined || list === null ? null :
|
||||
<div style={mobileStyle}>
|
||||
<div>
|
||||
<Button fullWidth aria-controls="simple-menu" aria-haspopup="true" variant="outlined" color="primary" onClick={handleClick}>
|
||||
@@ -294,7 +451,12 @@ const Docs = (props) => {
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{list.map((item, index) => {
|
||||
{list.map((data, index) => {
|
||||
const item = data.name
|
||||
if (item === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const path = "/docs/"+item
|
||||
const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
|
||||
return (
|
||||
@@ -344,7 +506,7 @@ const Docs = (props) => {
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{}}>
|
||||
{loadedCheck}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import React, { useState } from 'react';
|
||||
import { makeStyles } from '@material-ui/styles';
|
||||
import { useInterval } from 'react-powerhooks';
|
||||
|
||||
import {CircularProgress, TextField, Button, Paper, Typography} from '@material-ui/core'
|
||||
import { useTheme } from '@material-ui/core/styles';
|
||||
@@ -27,11 +28,13 @@ const useStyles = makeStyles({
|
||||
const LoginDialog = props => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register } = props;
|
||||
const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, checkLogin } = props;
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstRequest, setFirstRequest] = useState(true);
|
||||
const [loginLoading, setLoginLoading] = useState(false);
|
||||
const [loginViewLoading, setLoginViewLoading] = useState(false);
|
||||
const [ssoUrl, setSSOUrl] = useState("")
|
||||
|
||||
// Used to swap from login to register. True = login, false = register
|
||||
|
||||
@@ -47,7 +50,6 @@ const LoginDialog = props => {
|
||||
window.location.pathname = "/workflows"
|
||||
}
|
||||
|
||||
|
||||
const checkAdmin = () => {
|
||||
const url = globalUrl + '/api/v1/checkusers';
|
||||
fetch(url, {
|
||||
@@ -61,6 +63,20 @@ const LoginDialog = props => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"])
|
||||
} else {
|
||||
if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) {
|
||||
setSSOUrl(responseJson.sso_url)
|
||||
}
|
||||
|
||||
if (loginViewLoading) {
|
||||
setLoginViewLoading(false)
|
||||
checkLogin()
|
||||
stop()
|
||||
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null) {
|
||||
setLoginInfo(responseJson.reason)
|
||||
}
|
||||
}
|
||||
|
||||
if (responseJson.reason === "stay") {
|
||||
window.location.pathname = "/adminsetup"
|
||||
}
|
||||
@@ -68,10 +84,21 @@ const LoginDialog = props => {
|
||||
}),
|
||||
)
|
||||
.catch(error => {
|
||||
setLoginInfo("Error logging in - please refresh in a minute ", error)
|
||||
if (!loginViewLoading) {
|
||||
setLoginViewLoading(true)
|
||||
start()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { start, stop } = useInterval({
|
||||
duration: 3000,
|
||||
startImmediate: false,
|
||||
callback: () => {
|
||||
checkAdmin()
|
||||
}
|
||||
})
|
||||
|
||||
if (firstRequest) {
|
||||
setFirstRequest(false)
|
||||
checkAdmin()
|
||||
@@ -178,6 +205,50 @@ const LoginDialog = props => {
|
||||
<div style={{position: "absolute", top: -imgsize/2-10, left: 250-imgsize/2, height: imgsize, width: imgsize, }}>
|
||||
<img src="images/Shuffle_logo.png" style={{height: imgsize+10, width: imgsize+10, border: "2px solid rgba(255,255,255,0.6)", borderRadius: imgsize,}}/>
|
||||
</div>
|
||||
{loginViewLoading ?
|
||||
<div style={{textAlign: "center", marginTop: 50, }}>
|
||||
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
|
||||
Waiting for the Shuffle database to become available. This may take up to a minute.
|
||||
</Typography>
|
||||
|
||||
{loginInfo === undefined || loginInfo === null || loginInfo.length === 0 ?
|
||||
null
|
||||
:
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
Response: {loginInfo}
|
||||
</div>
|
||||
}
|
||||
<CircularProgress color="secondary" style={{color: "white",}} />
|
||||
|
||||
|
||||
<Paper style={{
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
position: "relative",
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
textAlign: "left",
|
||||
marginTop: 15,
|
||||
}}>
|
||||
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
|
||||
<b>Are you sure Shuffle is <a rel="norefferer" target="_blank" href="https://github.com/frikky/Shuffle/blob/master/.github/install-guide.md" style={{textDecoration: "none", color: "#f86a3e"}}>installed correctly</a>?</b>
|
||||
</Typography>
|
||||
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
|
||||
<b>1.</b> Make sure shuffle-database folder has correct access: <br/><br/>
|
||||
sudo chown 1000:1000 -R shuffle-database
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" style={{marginBottom: 20, color: "white",}}>
|
||||
<b>2</b>. Restart docker-compose:<br/><br/>
|
||||
sudo docker-compose restart
|
||||
</Typography>
|
||||
</Paper>
|
||||
<Typography variant="body2" style={{marginBottom: 10, color: "white", marginTop: 20, }}>
|
||||
Need help? <a rel="norefferer" target="_blank" href="https://discord.gg/B2CBzUm" style={{textDecoration: "none", color: "#f86a3e"}}>Join the Discord!</a>
|
||||
</Typography>
|
||||
</div>
|
||||
:
|
||||
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px", color: "white", }}>
|
||||
<h2>{formtitle}</h2>
|
||||
Username
|
||||
@@ -233,14 +304,30 @@ const LoginDialog = props => {
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", marginTop: "15px" }}>
|
||||
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", marginRight: "5px" }} disabled={!handleValidateForm() || loginLoading}>
|
||||
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", }} disabled={!handleValidateForm() || loginLoading}>
|
||||
{loginLoading ? <CircularProgress color="secondary" style={{color: "white",}} /> : "SUBMIT"}
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
{loginInfo}
|
||||
</div>
|
||||
{ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0 ?
|
||||
<div>
|
||||
<Typography style={{textAlign: "center", }}>
|
||||
Or
|
||||
</Typography>
|
||||
<div style={{textAlign: "center", margin: 10, }}>
|
||||
<Button fullWidth color="secondary" variant="outlined" type="button" style={{ flex: "1", marginTop: 5}} onClick={() => {
|
||||
console.log("CLICK")
|
||||
window.location = ssoUrl
|
||||
}}>
|
||||
Use SSO
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
</form>
|
||||
}
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
|
||||
|
||||
import { Typography, CircularProgress } from '@material-ui/core';
|
||||
|
||||
const SetAuthentication = (props) => {
|
||||
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
|
||||
|
||||
const [firstRequest, setFirstRequest] = useState(true)
|
||||
const [finished, setFinished] = useState(false)
|
||||
const [response, setResponse] = useState("")
|
||||
const [failed, setFailed] = useState(false)
|
||||
|
||||
if (firstRequest) {
|
||||
setFirstRequest(false)
|
||||
|
||||
//code
|
||||
//session_state
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
|
||||
const authenticationStore = []
|
||||
var appAuthData = {
|
||||
"label": "",
|
||||
"app": {
|
||||
"name": "",
|
||||
"id": "",
|
||||
"app_version": "",
|
||||
},
|
||||
"fields": [],
|
||||
"type": "oauth2",
|
||||
}
|
||||
|
||||
if (window !== undefined && window !== null) {
|
||||
console.log(window.location)
|
||||
appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname})
|
||||
}
|
||||
|
||||
if (params.code !== undefined && params.code !== null) {
|
||||
appAuthData.fields.push({"key": "code", "value": params.code})
|
||||
}
|
||||
|
||||
if (params.session_state !== undefined && params.session_state !== null) {
|
||||
appAuthData.fields.push({"key": "session_state", "value": params.session_state})
|
||||
}
|
||||
|
||||
if (params.state !== undefined && params.state !== null) {
|
||||
const paramsplit = params.state.split("&")
|
||||
console.log(paramsplit)
|
||||
for (var key in paramsplit) {
|
||||
const query = paramsplit[key].split("=")
|
||||
console.log(query)
|
||||
|
||||
if (query.length !== 2) {
|
||||
console.log("INVALID QUERY: ", query)
|
||||
continue
|
||||
}
|
||||
|
||||
if (query[0] === "workflow_id") {
|
||||
appAuthData.reference_workflow = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "reference_action_id") {
|
||||
//appAuthData.ReferenceWorkflow = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "app_name") {
|
||||
appAuthData.app.name = query[1]
|
||||
appAuthData.label = "Oauth2 for "+query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "app_id") {
|
||||
appAuthData.app.id = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "app_version") {
|
||||
appAuthData.app.app_version = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "authentication_url") {
|
||||
appAuthData.fields.push({"key": "authentication_url", "value": query[1]})
|
||||
}
|
||||
|
||||
if (query[0] === "scope") {
|
||||
appAuthData.fields.push({"key": "scope", "value": query[1]})
|
||||
}
|
||||
|
||||
if (query[0] === "client_id") {
|
||||
appAuthData.fields.push({"key": "client_id", "value": query[1]})
|
||||
}
|
||||
|
||||
if (query[0] === "client_secret") {
|
||||
appAuthData.fields.push({"key": "client_secret", "value": query[1]})
|
||||
}
|
||||
|
||||
if (query[0] === "oauth_url") {
|
||||
appAuthData.fields.push({"key": "oauth_url", "value": query[1]})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(appAuthData)
|
||||
|
||||
fetch(globalUrl+"/api/v1/apps/authentication", {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(appAuthData),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for oauth2 authentication")
|
||||
setFailed(true)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
//setUserSettings(responseJson)
|
||||
console.log("Resp: ", responseJson)
|
||||
setFinished(true)
|
||||
setResponse(responseJson.reason)
|
||||
|
||||
setTimeout(() => {
|
||||
window.close()
|
||||
}, 1000)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{width: 1000, margin: "auto", itemAlign: "center",}}>
|
||||
<Typography variant="h6" style={{marginLeft: "auto", marginRight: "auto", marginTop: 200, }}>
|
||||
{!finished ? <CircularProgress /> : "DONE WITH AUTH - this will close soon!!"}
|
||||
<div />
|
||||
{failed ? "Failed setup. Error: " : ""} {response}
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SetAuthentication;
|
||||
@@ -0,0 +1,143 @@
|
||||
import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
|
||||
|
||||
import { Typography, CircularProgress } from '@material-ui/core';
|
||||
|
||||
const SetAuthentication = (props) => {
|
||||
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
|
||||
|
||||
const [firstRequest, setFirstRequest] = useState(true)
|
||||
const [finished, setFinished] = useState(false)
|
||||
const [response, setResponse] = useState("")
|
||||
const [failed, setFailed] = useState(false)
|
||||
|
||||
if (firstRequest) {
|
||||
setFirstRequest(false)
|
||||
|
||||
//code
|
||||
//session_state
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
|
||||
const authenticationStore = []
|
||||
var appAuthData = {
|
||||
"label": "",
|
||||
"app": {
|
||||
"name": "",
|
||||
"id": "",
|
||||
"app_version": "",
|
||||
},
|
||||
"fields": [],
|
||||
"type": "oauth2",
|
||||
}
|
||||
|
||||
if (window !== undefined && window !== null) {
|
||||
console.log(window.location)
|
||||
appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname})
|
||||
}
|
||||
|
||||
if (params.code !== undefined && params.code !== null) {
|
||||
appAuthData.fields.push({"key": "code", "value": params.code})
|
||||
}
|
||||
|
||||
if (params.session_state !== undefined && params.session_state !== null) {
|
||||
appAuthData.fields.push({"key": "session_state", "value": params.session_state})
|
||||
}
|
||||
|
||||
if (params.state !== undefined && params.state !== null) {
|
||||
const paramsplit = params.state.split("&")
|
||||
console.log(paramsplit)
|
||||
for (var key in paramsplit) {
|
||||
const query = paramsplit[key].split("=")
|
||||
console.log(query)
|
||||
|
||||
if (query.length !== 2) {
|
||||
console.log("INVALID QUERY: ", query)
|
||||
continue
|
||||
}
|
||||
|
||||
if (query[0] === "workflow_id") {
|
||||
appAuthData.reference_workflow = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "reference_action_id") {
|
||||
//appAuthData.ReferenceWorkflow = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "app_name") {
|
||||
appAuthData.app.name = query[1]
|
||||
appAuthData.label = "Oauth2 for "+query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "app_id") {
|
||||
appAuthData.app.id = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "app_version") {
|
||||
appAuthData.app.app_version = query[1]
|
||||
}
|
||||
|
||||
if (query[0] === "authentication_url") {
|
||||
appAuthData.fields.push({"key": "authentication_url", "value": query[1]})
|
||||
}
|
||||
|
||||
if (query[0] === "scope") {
|
||||
appAuthData.fields.push({"key": "scope", "value": query[1]})
|
||||
}
|
||||
|
||||
if (query[0] === "client_id") {
|
||||
appAuthData.fields.push({"key": "client_id", "value": query[1]})
|
||||
}
|
||||
|
||||
if (query[0] === "client_secret") {
|
||||
appAuthData.fields.push({"key": "client_secret", "value": query[1]})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(appAuthData)
|
||||
|
||||
fetch(globalUrl+"/api/v1/apps/authentication", {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(appAuthData),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for oauth2 authentication")
|
||||
setFailed(true)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
//setUserSettings(responseJson)
|
||||
console.log("Resp: ", responseJson)
|
||||
setFinished(true)
|
||||
setResponse(responseJson.reason)
|
||||
|
||||
setTimeout(() => {
|
||||
window.close()
|
||||
}, 1000)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{width: 1000, margin: "auto", itemAlign: "center",}}>
|
||||
<Typography variant="h6" style={{marginLeft: "auto", marginRight: "auto", marginTop: 200, }}>
|
||||
{!finished ? <CircularProgress /> : "DONE WITH AUTH - this will close soon!!"}
|
||||
<div />
|
||||
{failed ? "Failed setup. Error: " : ""} {response}
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SetAuthentication;
|
||||
+403
-298
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user