Merge pull request #538 from frikky/launch

0.9.25 :D
This commit is contained in:
Frikky
2021-10-15 01:23:57 +02:00
committed by GitHub
85 changed files with 9387 additions and 3795 deletions
+1 -2
View File
@@ -8,7 +8,6 @@ ENV PATH /usr/src/app/node_modules/.bin:$PATH
COPY package.json /usr/src/app/package.json
#RUN npm install --verbose
RUN yarn install
# copy only required files to not trigger rebuilding every time
@@ -21,7 +20,7 @@ COPY ./*.json /usr/src/app/
RUN yarn build
# Production environment
FROM nginx:1.21
FROM nginx:1.21.3
RUN mkdir -p /usr/share/nginx/html/build
RUN mkdir -p /usr/share/nginx/html/css
+10 -8
View File
@@ -1,14 +1,16 @@
{
"name": "shuffler",
"homepage": "https://shuffler.io",
"version": "0.8.92",
"version": "0.9.24",
"private": true,
"dependencies": {
"@material-ui/core": "^4.5.2",
"@material-ui/data-grid": "^4.0.0-alpha.22",
"@material-ui/icons": "^4.5.1",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.58",
"@material-ui/styles": "^4.5.2",
"@material-ui/utils": "^4.11.2",
"@uiw/react-codemirror": "^3.2.1",
"@use-it/interval": "^1.0.0",
"babel-eslint": "^10.1.0",
"class-transformer": "^0.3.1",
@@ -17,23 +19,23 @@
"cytoscape-clipboard": "^2.2.1",
"cytoscape-cxtmenu": "^3.1.1",
"cytoscape-edgehandles": "^3.6.0",
"cytoscape-grid-guide": "~2.1.2",
"cytoscape-grid-guide": "~2.3.3",
"cytoscape-node-html-label": "^1.1.5",
"cytoscape-panzoom": "^2.5.2",
"cytoscape-undo-redo": "^1.3.2",
"d3": "~4.10.0",
"d3": "^7.1.1",
"dotenv": "^6.1.0",
"downshift": "^3.3.5",
"github-markdown-css": "^3.0.1",
"import": "0.0.6",
"interweave": "^11.2.0",
"material-icons": "^0.3.1",
"material-icons": "^0.7.7",
"material-icons-react": "^1.0.4",
"material-ui-chip-input": "^2.0.0-beta.2",
"material-ui-nested-menu-item": "^1.0.2",
"md5-file": "^4.0.0",
"mdbreact": "^4.21.1",
"moment": "~2.20.1",
"moment": "^2.29.1",
"react": "^16.14.0",
"react-alert": "^5.5.0",
"react-alert-template-basic": "^1.0.0",
@@ -64,10 +66,10 @@
"websocket": "^1.0.30",
"yaml": "^1.7.2",
"yamljs": "^0.3.0",
"zone.js": "~0.8.26"
"zone.js": "~0.11.4"
},
"scripts": {
"start": "set HTTPS=true&&react-scripts start",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

+32 -6
View File
@@ -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>
+1 -1
View File
@@ -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",
+46 -6
View File
@@ -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
+280 -29
View File
@@ -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 /> &nbsp;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;
+417
View File
@@ -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
+278 -47
View File
@@ -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}
+2 -9
View File
@@ -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.
+40
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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>
&nbsp;- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
&nbsp;- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
&nbsp;- <a href="https://github.com/APIs-guru/openapi-directory/tree/main/APIs" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
&nbsp;- <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
View File
@@ -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 /> &nbsp;&nbsp;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>
)
+91 -4
View File
@@ -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>
+147
View File
@@ -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;
+143
View File
@@ -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;
File diff suppressed because it is too large Load Diff