Loads of frontend fixes
This commit is contained in:
@@ -59,9 +59,9 @@ SHUFFLE_ORBORUS_STARTUP_DELAY= # Used for setting up a startup delay for Orbor
|
|||||||
SHUFFLE_SKIPSSL_VERIFY=true
|
SHUFFLE_SKIPSSL_VERIFY=true
|
||||||
IS_KUBERNETES=false # Used for controlling if the environment should run in kubernetes or not
|
IS_KUBERNETES=false # Used for controlling if the environment should run in kubernetes or not
|
||||||
|
|
||||||
SHUFFLE_BASE_IMAGE_NAME=shuffle
|
#SHUFFLE_BASE_IMAGE_NAME=shuffle
|
||||||
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
|
#SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
|
||||||
SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.3.1"
|
#SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-1.3.1"
|
||||||
|
|
||||||
# The eth0 interface inside a container corresponds
|
# The eth0 interface inside a container corresponds
|
||||||
# to the virtual Ethernet interface that connects
|
# to the virtual Ethernet interface that connects
|
||||||
|
|||||||
@@ -466,8 +466,8 @@ class AppBase:
|
|||||||
"success": True,
|
"success": True,
|
||||||
"status": request.status_code,
|
"status": request.status_code,
|
||||||
"url": request.url,
|
"url": request.url,
|
||||||
"headers": parsedheaders,
|
|
||||||
"body": jsondata,
|
"body": jsondata,
|
||||||
|
"headers": parsedheaders,
|
||||||
"cookies":cookies,
|
"cookies":cookies,
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -615,7 +615,7 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho
|
|||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
<div style={{flex: 1, marginTop: 10,}}>
|
<div style={{flex: 1, marginTop: 10,}}>
|
||||||
<SearchField serverside={serverside} userdata={userdata} />
|
<SearchField globalUrl={globalUrl} serverside={serverside} userdata={userdata} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{flex: 1, display: "flex", flexDirection: "row-reverse"}}>
|
<div style={{flex: 1, display: "flex", flexDirection: "row-reverse"}}>
|
||||||
<List style={{display: 'flex', flexDirection: 'row-reverse'}} component="nav">
|
<List style={{display: 'flex', flexDirection: 'row-reverse'}} component="nav">
|
||||||
|
|||||||
@@ -741,7 +741,7 @@ const Header = (props) => {
|
|||||||
>
|
>
|
||||||
<div style={{maxWidth: 70, minWidth: 70, }}/>
|
<div style={{maxWidth: 70, minWidth: 70, }}/>
|
||||||
<span style={{marginTop: 8, marginRight: 15, }}>
|
<span style={{marginTop: 8, marginRight: 15, }}>
|
||||||
<SearchField isHeader={true} isLoggedIn={isLoggedIn} isLoaded={isLoaded} serverside={serverside} userdata={userdata} small={true} rounded={true} />
|
<SearchField globalUrl={globalUrl} isHeader={true} isLoggedIn={isLoggedIn} isLoaded={isLoaded} serverside={serverside} userdata={userdata} small={true} rounded={true} />
|
||||||
</span>
|
</span>
|
||||||
<Link to="/register" style={hrefStyle}>
|
<Link to="/register" style={hrefStyle}>
|
||||||
<Button
|
<Button
|
||||||
@@ -912,7 +912,7 @@ const Header = (props) => {
|
|||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, marginTop: 10, }}>
|
<div style={{ flex: 1, marginTop: 10, }}>
|
||||||
<SearchField isHeader={true} isLoggedIn={isLoggedIn} isLoaded={isLoaded} serverside={serverside} userdata={userdata} hidemargins={true}/>
|
<SearchField globalUrl={globalUrl} isHeader={true} isLoggedIn={isLoggedIn} isLoaded={isLoaded} serverside={serverside} userdata={userdata} hidemargins={true}/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: isLoggedIn ? null : 1, display: "flex", flexDirection: "row-reverse" }}>
|
<div style={{ flex: isLoggedIn ? null : 1, display: "flex", flexDirection: "row-reverse" }}>
|
||||||
<List
|
<List
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ const AuthenticationOauth2 = (props) => {
|
|||||||
},
|
},
|
||||||
"fields": parsedFields,
|
"fields": parsedFields,
|
||||||
"type": "oauth2-app",
|
"type": "oauth2-app",
|
||||||
"reference_workflow": workflowId,
|
//"reference_workflow": workflowId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setNewAppAuth !== undefined) {
|
if (setNewAppAuth !== undefined) {
|
||||||
|
|||||||
@@ -1644,13 +1644,11 @@ const ParsedAction = (props) => {
|
|||||||
multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline}
|
multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline}
|
||||||
helperText={returnHelperText(data.name, data.value)}
|
helperText={returnHelperText(data.name, data.value)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
console.log("Clicked field: ", clickedFieldId, data.name)
|
|
||||||
/*
|
/*
|
||||||
setExpansionModalOpen(false);
|
setExpansionModalOpen(false);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) {
|
if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) {
|
||||||
console.log("IN SCROLL CONFIG!")
|
|
||||||
|
|
||||||
scrollConfig.selected = clickedFieldId
|
scrollConfig.selected = clickedFieldId
|
||||||
setScrollConfig(scrollConfig)
|
setScrollConfig(scrollConfig)
|
||||||
@@ -3737,6 +3735,11 @@ const ParsedAction = (props) => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if it starts with "Get List" and method is "Get"
|
||||||
|
if (params.inputProps.value.startsWith("Get List")) {
|
||||||
|
console.log("Get List")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef } from 'react';
|
|||||||
|
|
||||||
import theme from '../theme.jsx';
|
import theme from '../theme.jsx';
|
||||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Chip,
|
Chip,
|
||||||
@@ -41,7 +42,7 @@ const chipStyle = {
|
|||||||
|
|
||||||
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
|
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
|
||||||
const SearchData = props => {
|
const SearchData = props => {
|
||||||
const { serverside, userdata, setModalOpen, modalOpen } = props
|
const { serverside, globalUrl, userdata, setModalOpen, modalOpen } = props
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
const borderRadius = 3
|
const borderRadius = 3
|
||||||
const node = useRef()
|
const node = useRef()
|
||||||
@@ -326,6 +327,57 @@ const SearchData = props => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activateApp = (name, appid, type) => {
|
||||||
|
if (globalUrl === undefined || globalUrl === null) {
|
||||||
|
console.log(`Global URL not set`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === undefined || name === null) {
|
||||||
|
name = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
name = name.replaceAll("_", " ")
|
||||||
|
|
||||||
|
if (userdata === undefined || userdata === null || userdata.id === undefined) {
|
||||||
|
toast(`You need to be logged in to activate the ${name} app. Redirecting`)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate(`/register?message=You need to be logged in to use the ${name} app.`)
|
||||||
|
}, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/apps/${appid}/${type}`
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
toast(`Failed to ${type} the app for your organization. Please try again or contact support@shuffler.io`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
if (responseJson.success === false) {
|
||||||
|
toast(`Failed to ${type} the app for your organization. Please try again or contact support@shuffler.io for more info`)
|
||||||
|
} else {
|
||||||
|
toast(`App successfully ${type}d. Please refresh the page to use it.`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
//toast(error.toString())
|
||||||
|
console.log("Activate app error: ", error.toString())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const AppHits = ({ hits }) => {
|
const AppHits = ({ hits }) => {
|
||||||
const [mouseHoverIndex, setMouseHoverIndex] = useState(0)
|
const [mouseHoverIndex, setMouseHoverIndex] = useState(0)
|
||||||
|
|
||||||
@@ -431,7 +483,6 @@ const SearchData = props => {
|
|||||||
return (
|
return (
|
||||||
<Link key={hit.objectID} to={parsedUrl} style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
|
<Link key={hit.objectID} to={parsedUrl} style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
|
||||||
setSearchOpen(true)
|
setSearchOpen(true)
|
||||||
|
|
||||||
setModalOpen(false)
|
setModalOpen(false)
|
||||||
aa('init', {
|
aa('init', {
|
||||||
appId: searchClient.appId,
|
appId: searchClient.appId,
|
||||||
@@ -474,6 +525,29 @@ const SearchData = props => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</ListItemSecondaryAction>
|
</ListItemSecondaryAction>
|
||||||
*/}
|
*/}
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
console.log("OBJECT CHANGE: ", hit.objectID)
|
||||||
|
|
||||||
|
// This does nothing rofl
|
||||||
|
if (userdata.active_apps === undefined || userdata.active_apps === null) {
|
||||||
|
activateApp(hit.name, hit.objectID, "activate")
|
||||||
|
} else {
|
||||||
|
if (userdata.active_apps.includes(hit.objectID)) {
|
||||||
|
activateApp(hit.name, hit.objectID, "deactivate")
|
||||||
|
} else {
|
||||||
|
activateApp(hit.name, hit.objectID, "activate")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(hit.objectID) ? "Deactivate" : "Activate"}
|
||||||
|
</Button>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const SearchField = props => {
|
|||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
<DialogContent style={{}}>
|
<DialogContent style={{}}>
|
||||||
<SearchBox setModalOpen={setModalOpen} modalOpen={modalOpen} serverside={serverside} userdata={userdata} />
|
<SearchBox globalUrl={globalUrl} setModalOpen={setModalOpen} modalOpen={modalOpen} serverside={serverside} userdata={userdata} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<Divider style={{overflow: "hidden"}}/>
|
<Divider style={{overflow: "hidden"}}/>
|
||||||
<span style={{display:"flex", width:"100%", height:30}}>
|
<span style={{display:"flex", width:"100%", height:30}}>
|
||||||
|
|||||||
@@ -236,8 +236,6 @@ const CodeEditor = (props) => {
|
|||||||
|
|
||||||
//var newitem = JSON.parse(base);
|
//var newitem = JSON.parse(base);
|
||||||
var newitem = validateJson(base).result
|
var newitem = validateJson(base).result
|
||||||
|
|
||||||
|
|
||||||
to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_");
|
to_be_copied = "$" + base_node_name.toLowerCase().replaceAll(" ", "_");
|
||||||
for (let copykey in copy.namespace) {
|
for (let copykey in copy.namespace) {
|
||||||
if (copy.namespace[copykey].includes("Results for")) {
|
if (copy.namespace[copykey].includes("Results for")) {
|
||||||
@@ -825,7 +823,7 @@ const CodeEditor = (props) => {
|
|||||||
console.log("ERR IN INPUT: ", e)
|
console.log("ERR IN INPUT: ", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Got output for: ", fullpath, new_input, actionlist[k].example, typeof new_input)
|
//console.log("Got output for: ", fullpath, new_input, actionlist[k].example, typeof new_input)
|
||||||
|
|
||||||
if (typeof new_input === "object") {
|
if (typeof new_input === "object") {
|
||||||
new_input = JSON.stringify(new_input)
|
new_input = JSON.stringify(new_input)
|
||||||
@@ -1078,114 +1076,122 @@ const CodeEditor = (props) => {
|
|||||||
*/}
|
*/}
|
||||||
{ isFileEditor ? null :
|
{ isFileEditor ? null :
|
||||||
<div style={{display: "flex", maxHeight: 40, }}>
|
<div style={{display: "flex", maxHeight: 40, }}>
|
||||||
<Button
|
{selectedAction.name === "execute_python" ?
|
||||||
id="basic-button"
|
<Typography variant="body1" style={{marginTop: 5, }}>
|
||||||
aria-haspopup="true"
|
Run Python Code
|
||||||
aria-controls={liquidOpen ? 'basic-menu' : undefined}
|
</Typography>
|
||||||
aria-expanded={liquidOpen ? 'true' : undefined}
|
:
|
||||||
variant="outlined"
|
<div style={{display: "flex", }}>
|
||||||
color="secondary"
|
<Button
|
||||||
style={{
|
id="basic-button"
|
||||||
textTransform: "none",
|
aria-haspopup="true"
|
||||||
width: 100,
|
aria-controls={liquidOpen ? 'basic-menu' : undefined}
|
||||||
}}
|
aria-expanded={liquidOpen ? 'true' : undefined}
|
||||||
onClick={(event) => {
|
variant="outlined"
|
||||||
setAnchorEl(event.currentTarget);
|
color="secondary"
|
||||||
}}
|
style={{
|
||||||
>
|
textTransform: "none",
|
||||||
Filters
|
width: 100,
|
||||||
</Button>
|
}}
|
||||||
<Menu
|
onClick={(event) => {
|
||||||
id="basic-menu"
|
setAnchorEl(event.currentTarget);
|
||||||
anchorEl={anchorEl}
|
}}
|
||||||
open={liquidOpen}
|
>
|
||||||
onClose={() => {
|
Filters
|
||||||
setAnchorEl(null);
|
</Button>
|
||||||
}}
|
<Menu
|
||||||
MenuListProps={{
|
id="basic-menu"
|
||||||
'aria-labelledby': 'basic-button',
|
anchorEl={anchorEl}
|
||||||
}}
|
open={liquidOpen}
|
||||||
>
|
onClose={() => {
|
||||||
{liquidFilters.map((item, index) => {
|
setAnchorEl(null);
|
||||||
return (
|
}}
|
||||||
<MenuItem key={index} onClick={() => {
|
MenuListProps={{
|
||||||
handleClick(item)
|
'aria-labelledby': 'basic-button',
|
||||||
}}>{item.name}</MenuItem>
|
}}
|
||||||
)
|
>
|
||||||
})}
|
{liquidFilters.map((item, index) => {
|
||||||
</Menu>
|
return (
|
||||||
<Button
|
<MenuItem key={index} onClick={() => {
|
||||||
id="basic-button"
|
handleClick(item)
|
||||||
aria-haspopup="true"
|
}}>{item.name}</MenuItem>
|
||||||
aria-controls={mathOpen ? 'basic-menu' : undefined}
|
)
|
||||||
aria-expanded={mathOpen ? 'true' : undefined}
|
})}
|
||||||
variant="outlined"
|
</Menu>
|
||||||
color="secondary"
|
<Button
|
||||||
style={{
|
id="basic-button"
|
||||||
textTransform: "none",
|
aria-haspopup="true"
|
||||||
width: 100,
|
aria-controls={mathOpen ? 'basic-menu' : undefined}
|
||||||
}}
|
aria-expanded={mathOpen ? 'true' : undefined}
|
||||||
onClick={(event) => {
|
variant="outlined"
|
||||||
setAnchorEl2(event.currentTarget);
|
color="secondary"
|
||||||
}}
|
style={{
|
||||||
>
|
textTransform: "none",
|
||||||
Math
|
width: 100,
|
||||||
</Button>
|
}}
|
||||||
<Menu
|
onClick={(event) => {
|
||||||
id="basic-menu"
|
setAnchorEl2(event.currentTarget);
|
||||||
anchorEl={anchorEl2}
|
}}
|
||||||
open={mathOpen}
|
>
|
||||||
onClose={() => {
|
Math
|
||||||
setAnchorEl2(null);
|
</Button>
|
||||||
}}
|
<Menu
|
||||||
MenuListProps={{
|
id="basic-menu"
|
||||||
'aria-labelledby': 'basic-button',
|
anchorEl={anchorEl2}
|
||||||
}}
|
open={mathOpen}
|
||||||
>
|
onClose={() => {
|
||||||
{mathFilters.map((item, index) => {
|
setAnchorEl2(null);
|
||||||
return (
|
}}
|
||||||
<MenuItem key={index} onClick={() => {
|
MenuListProps={{
|
||||||
handleClick(item)
|
'aria-labelledby': 'basic-button',
|
||||||
}}>{item.name}</MenuItem>
|
}}
|
||||||
)
|
>
|
||||||
})}
|
{mathFilters.map((item, index) => {
|
||||||
</Menu>
|
return (
|
||||||
<Button
|
<MenuItem key={index} onClick={() => {
|
||||||
id="basic-button"
|
handleClick(item)
|
||||||
aria-haspopup="true"
|
}}>{item.name}</MenuItem>
|
||||||
aria-controls={pythonOpen ? 'basic-menu' : undefined}
|
)
|
||||||
aria-expanded={pythonOpen ? 'true' : undefined}
|
})}
|
||||||
variant="outlined"
|
</Menu>
|
||||||
color="secondary"
|
<Button
|
||||||
style={{
|
id="basic-button"
|
||||||
textTransform: "none",
|
aria-haspopup="true"
|
||||||
width: 100,
|
aria-controls={pythonOpen ? 'basic-menu' : undefined}
|
||||||
}}
|
aria-expanded={pythonOpen ? 'true' : undefined}
|
||||||
onClick={(event) => {
|
variant="outlined"
|
||||||
setAnchorEl3(event.currentTarget);
|
color="secondary"
|
||||||
}}
|
style={{
|
||||||
>
|
textTransform: "none",
|
||||||
Python
|
width: 100,
|
||||||
</Button>
|
}}
|
||||||
<Menu
|
onClick={(event) => {
|
||||||
id="basic-menu"
|
setAnchorEl3(event.currentTarget);
|
||||||
anchorEl={anchorEl3}
|
}}
|
||||||
open={pythonOpen}
|
>
|
||||||
onClose={() => {
|
Python
|
||||||
setAnchorEl3(null);
|
</Button>
|
||||||
}}
|
<Menu
|
||||||
MenuListProps={{
|
id="basic-menu"
|
||||||
'aria-labelledby': 'basic-button',
|
anchorEl={anchorEl3}
|
||||||
}}
|
open={pythonOpen}
|
||||||
>
|
onClose={() => {
|
||||||
{pythonFilters.map((item, index) => {
|
setAnchorEl3(null);
|
||||||
return (
|
}}
|
||||||
<MenuItem key={index} onClick={() => {
|
MenuListProps={{
|
||||||
handleClick(item)
|
'aria-labelledby': 'basic-button',
|
||||||
}}>{item.name}</MenuItem>
|
}}
|
||||||
)
|
>
|
||||||
})}
|
{pythonFilters.map((item, index) => {
|
||||||
</Menu>
|
return (
|
||||||
|
<MenuItem key={index} onClick={() => {
|
||||||
|
handleClick(item)
|
||||||
|
}}>{item.name}</MenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<Button
|
<Button
|
||||||
id="basic-button"
|
id="basic-button"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
@@ -1589,7 +1595,6 @@ const CodeEditor = (props) => {
|
|||||||
maxHeight: 35,
|
maxHeight: 35,
|
||||||
minWidth: 70,
|
minWidth: 70,
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
executeSingleAction(expOutput)
|
executeSingleAction(expOutput)
|
||||||
}}
|
}}
|
||||||
|
|||||||
+365
-205
@@ -140,6 +140,11 @@ const Admin = (props) => {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
|
|
||||||
|
const [logsViewModal, setLogsViewModal] = React.useState(false);
|
||||||
|
const [userLogViewing, setUserLogViewing] = React.useState({});
|
||||||
|
const [ipSelected, setIpSelected] = React.useState("");
|
||||||
|
const [logsLoading, setLogsLoading] = React.useState(true);
|
||||||
|
const [logs, setLogs] = React.useState([]);
|
||||||
const [firstRequest, setFirstRequest] = React.useState(true);
|
const [firstRequest, setFirstRequest] = React.useState(true);
|
||||||
const [orgRequest, setOrgRequest] = React.useState(true);
|
const [orgRequest, setOrgRequest] = React.useState(true);
|
||||||
const [modalUser, setModalUser] = React.useState({});
|
const [modalUser, setModalUser] = React.useState({});
|
||||||
@@ -3343,6 +3348,111 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
backgroundColor: theme.palette.inputColor,
|
backgroundColor: theme.palette.inputColor,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
{logsViewModal ?
|
||||||
|
<Dialog
|
||||||
|
open={logsViewModal}
|
||||||
|
onClose={() => {
|
||||||
|
setLogsViewModal(false);
|
||||||
|
}}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
|
color: "white",
|
||||||
|
minWidth: "1200px",
|
||||||
|
minHeight: "320px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle>
|
||||||
|
<span style={{ color: "white" }}>User Logs</span>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{/* ask user for which IP they want to see logs for by iterating of user.login_info */}
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel style={{ size: 10 }} id="user-ip-simple-select-label">
|
||||||
|
User IP
|
||||||
|
</InputLabel>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
labelId="user-ip-simple-select-label"
|
||||||
|
id="user-ip-simple-select"
|
||||||
|
onChange={async (event) => {
|
||||||
|
setIpSelected(event.target.value);
|
||||||
|
await getLogs(event.target.value, userLogViewing.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(() => {
|
||||||
|
const uniqueIPs = new Set();
|
||||||
|
|
||||||
|
return userLogViewing.login_info.map((data, index) => {
|
||||||
|
if (data.ip.includes("127.0.0.1") || uniqueIPs.has(data.ip)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
uniqueIPs.add(data.ip);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem key={index} value={data.ip}>
|
||||||
|
{data.ip}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})()}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{logsLoading && ipSelected.length !== 0 ?
|
||||||
|
<div style={{ marginTop: 20, marginBottom: 20, display: 'flex', alignItems: 'center' }}>
|
||||||
|
<CircularProgress style={{ marginRight: 10 }} />
|
||||||
|
<Typography>Loading logs</Typography>
|
||||||
|
</div>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<List>
|
||||||
|
{logs.map((data, index) => (
|
||||||
|
// redirect user to logs
|
||||||
|
// using request id or trace id
|
||||||
|
<ListItem key={index} style={{ backgroundColor: index % 2 === 0 ? "#1f2023" : "#27292d" }}>
|
||||||
|
<ListItemText
|
||||||
|
primary={new Date(data.start_time.seconds * 1000).toISOString().slice(0, 10)}
|
||||||
|
style={{
|
||||||
|
minWidth: 150,
|
||||||
|
maxWidth: 150,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.status}
|
||||||
|
style={{
|
||||||
|
minWidth: 70,
|
||||||
|
maxWidth: 70,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.method}
|
||||||
|
style={{
|
||||||
|
minWidth: 150,
|
||||||
|
maxWidth: 150,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.resource}
|
||||||
|
style={{
|
||||||
|
minWidth: 700,
|
||||||
|
maxWidth: 700,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
</Dialog>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
@@ -3392,6 +3502,7 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
primary="Last Login"
|
primary="Last Login"
|
||||||
style={{ minWidth: 150, maxWidth: 150, }}
|
style={{ minWidth: 150, maxWidth: 150, }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</ListItem>
|
</ListItem>
|
||||||
{users === undefined || users === null
|
{users === undefined || users === null
|
||||||
? null
|
? null
|
||||||
@@ -3418,223 +3529,232 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
var userData = data.username
|
||||||
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
if (userdata.support === true) {
|
||||||
<ListItemText
|
userData = <a style={{ cursor: "pointer", textDecoration: 'underline', textDecorationColor: '#F76742', color: '#F76742' }} onClick={() => {
|
||||||
primary={data.username}
|
setLogsViewModal(true)
|
||||||
style={{
|
setUserLogViewing(data)
|
||||||
minWidth: 350,
|
}}
|
||||||
maxWidth: 350,
|
>{data.username}</a>
|
||||||
overflow: "hidden",
|
}
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ListItemText
|
return (
|
||||||
style={{ marginLeft: 10, maxWidth: 100, minWidth: 100 }}
|
<ListItem key={index} style={{ backgroundColor: bgColor }}>
|
||||||
primary={
|
<ListItemText
|
||||||
data.apikey === undefined ||
|
primary={userData}
|
||||||
data.apikey.length === 0 ? (
|
style={{
|
||||||
""
|
minWidth: 350,
|
||||||
) : (
|
maxWidth: 350,
|
||||||
<Tooltip
|
overflow: "hidden",
|
||||||
title={"Copy Api Key"}
|
}}
|
||||||
style={{}}
|
/>
|
||||||
aria-label={"Copy APIkey"}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
style={{}}
|
|
||||||
onClick={() => {
|
|
||||||
const elementName = "copy_element_shuffle";
|
|
||||||
var copyText =
|
|
||||||
document.getElementById(elementName);
|
|
||||||
if (
|
|
||||||
copyText !== null &&
|
|
||||||
copyText !== undefined
|
|
||||||
) {
|
|
||||||
const clipboard = navigator.clipboard;
|
|
||||||
if (clipboard === undefined) {
|
|
||||||
toast(
|
|
||||||
"Can only copy over HTTPS (port 3443)"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
navigator.clipboard.writeText(data.apikey);
|
<ListItemText
|
||||||
copyText.select();
|
style={{ marginLeft: 10, maxWidth: 100, minWidth: 100 }}
|
||||||
copyText.setSelectionRange(
|
primary={
|
||||||
0,
|
data.apikey === undefined ||
|
||||||
99999
|
data.apikey.length === 0 ? (
|
||||||
); /* For mobile devices */
|
""
|
||||||
|
) : (
|
||||||
|
<Tooltip
|
||||||
|
title={"Copy Api Key"}
|
||||||
|
style={{}}
|
||||||
|
aria-label={"Copy APIkey"}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{}}
|
||||||
|
onClick={() => {
|
||||||
|
const elementName = "copy_element_shuffle";
|
||||||
|
var copyText =
|
||||||
|
document.getElementById(elementName);
|
||||||
|
if (
|
||||||
|
copyText !== null &&
|
||||||
|
copyText !== undefined
|
||||||
|
) {
|
||||||
|
const clipboard = navigator.clipboard;
|
||||||
|
if (clipboard === undefined) {
|
||||||
|
toast(
|
||||||
|
"Can only copy over HTTPS (port 3443)"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Copy the text inside the text field */
|
navigator.clipboard.writeText(data.apikey);
|
||||||
document.execCommand("copy");
|
copyText.select();
|
||||||
|
copyText.setSelectionRange(
|
||||||
|
0,
|
||||||
|
99999
|
||||||
|
); /* For mobile devices */
|
||||||
|
|
||||||
toast("Apikey copied to clipboard");
|
/* Copy the text inside the text field */
|
||||||
}
|
document.execCommand("copy");
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileCopyIcon
|
|
||||||
style={{ color: "rgba(255,255,255,0.8)" }}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ListItemText
|
toast("Apikey copied to clipboard");
|
||||||
primary={
|
}
|
||||||
<Select
|
|
||||||
SelectDisplayProps={{
|
|
||||||
style: {
|
|
||||||
marginLeft: 10,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
value={data.role}
|
|
||||||
fullWidth
|
|
||||||
onChange={(e) => {
|
|
||||||
console.log("VALUE: ", e.target.value);
|
|
||||||
setUser(data.id, "role", e.target.value);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.surfaceColor,
|
|
||||||
color: "white",
|
|
||||||
height: "50px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem
|
<FileCopyIcon
|
||||||
style={{
|
style={{ color: "rgba(255,255,255,0.8)" }}
|
||||||
backgroundColor: theme.palette.inputColor,
|
/>
|
||||||
color: "white",
|
</IconButton>
|
||||||
}}
|
</Tooltip>
|
||||||
value={"admin"}
|
)
|
||||||
>
|
}
|
||||||
Org Admin
|
/>
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
color: "white",
|
|
||||||
}}
|
|
||||||
value={"user"}
|
|
||||||
>
|
|
||||||
Org User
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
style={{
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
color: "white",
|
|
||||||
}}
|
|
||||||
value={"org-reader"}
|
|
||||||
>
|
|
||||||
Org Reader
|
|
||||||
</MenuItem>
|
|
||||||
</Select>
|
|
||||||
}
|
|
||||||
style={{ minWidth: 135, maxWidth: 135, marginRight: 15 }}
|
|
||||||
/>
|
|
||||||
<ListItemText
|
|
||||||
primary={data.active ? "True" : "False"}
|
|
||||||
style={{ minWidth: 100, maxWidth: 100 }}
|
|
||||||
/>
|
|
||||||
<ListItemText
|
|
||||||
primary={
|
|
||||||
data.login_type === undefined ||
|
|
||||||
data.login_type === null ||
|
|
||||||
data.login_type.length === 0
|
|
||||||
? "Normal"
|
|
||||||
: data.login_type
|
|
||||||
}
|
|
||||||
style={{ minWidth: 100, maxWidth: 100 }}
|
|
||||||
/>
|
|
||||||
<ListItemText
|
|
||||||
primary={
|
|
||||||
data.mfa_info !== undefined &&
|
|
||||||
data.mfa_info !== null &&
|
|
||||||
data.mfa_info.active === true
|
|
||||||
? "Active"
|
|
||||||
: "Inactive"
|
|
||||||
}
|
|
||||||
style={{ minWidth: 100, maxWidth: 100 }}
|
|
||||||
/>
|
|
||||||
{selectedOrganization.child_orgs !== undefined &&
|
|
||||||
selectedOrganization.child_orgs !== null &&
|
|
||||||
selectedOrganization.child_orgs.length > 0 ? (
|
|
||||||
<ListItemText
|
|
||||||
style={{ display: "flex" }}
|
|
||||||
primary={
|
|
||||||
data.orgs === undefined || data.orgs === null
|
|
||||||
? 0
|
|
||||||
: data.orgs.length - 1
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<ListItemText style={{ display: "flex", minWidth: 100, maxWidth: 100, }}>
|
|
||||||
<IconButton
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedUserModalOpen(true);
|
|
||||||
setSelectedUser(data);
|
|
||||||
|
|
||||||
// Find matching orgs between current org and current user's access to those orgs
|
<ListItemText
|
||||||
if (
|
primary={
|
||||||
userdata.orgs !== undefined &&
|
<Select
|
||||||
userdata.orgs !== null &&
|
SelectDisplayProps={{
|
||||||
userdata.orgs.length > 0 &&
|
style: {
|
||||||
selectedOrganization.child_orgs !== undefined &&
|
marginLeft: 10,
|
||||||
selectedOrganization.child_orgs !== null &&
|
},
|
||||||
selectedOrganization.child_orgs.length > 0
|
}}
|
||||||
) {
|
value={data.role}
|
||||||
var active = [];
|
fullWidth
|
||||||
for (var key in userdata.orgs) {
|
onChange={(e) => {
|
||||||
const found =
|
console.log("VALUE: ", e.target.value);
|
||||||
selectedOrganization.child_orgs.find(
|
setUser(data.id, "role", e.target.value);
|
||||||
(item) => item.id === userdata.orgs[key].id
|
}}
|
||||||
);
|
style={{
|
||||||
if (found !== null && found !== undefined) {
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
if (
|
color: "white",
|
||||||
data.orgs === undefined ||
|
height: "50px",
|
||||||
data.orgs === null
|
}}
|
||||||
) {
|
>
|
||||||
continue;
|
<MenuItem
|
||||||
}
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
value={"admin"}
|
||||||
|
>
|
||||||
|
Org Admin
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
value={"user"}
|
||||||
|
>
|
||||||
|
Org User
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
style={{
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
color: "white",
|
||||||
|
}}
|
||||||
|
value={"org-reader"}
|
||||||
|
>
|
||||||
|
Org Reader
|
||||||
|
</MenuItem>
|
||||||
|
</Select>
|
||||||
|
}
|
||||||
|
style={{ minWidth: 135, maxWidth: 135, marginRight: 15 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={data.active ? "True" : "False"}
|
||||||
|
style={{ minWidth: 100, maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
data.login_type === undefined ||
|
||||||
|
data.login_type === null ||
|
||||||
|
data.login_type.length === 0
|
||||||
|
? "Normal"
|
||||||
|
: data.login_type
|
||||||
|
}
|
||||||
|
style={{ minWidth: 100, maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
data.mfa_info !== undefined &&
|
||||||
|
data.mfa_info !== null &&
|
||||||
|
data.mfa_info.active === true
|
||||||
|
? "Active"
|
||||||
|
: "Inactive"
|
||||||
|
}
|
||||||
|
style={{ minWidth: 100, maxWidth: 100 }}
|
||||||
|
/>
|
||||||
|
{selectedOrganization.child_orgs !== undefined &&
|
||||||
|
selectedOrganization.child_orgs !== null &&
|
||||||
|
selectedOrganization.child_orgs.length > 0 ? (
|
||||||
|
<ListItemText
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
primary={
|
||||||
|
data.orgs === undefined || data.orgs === null
|
||||||
|
? 0
|
||||||
|
: data.orgs.length - 1
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<ListItemText style={{ display: "flex", minWidth: 100, maxWidth: 100, }}>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedUserModalOpen(true);
|
||||||
|
setSelectedUser(data);
|
||||||
|
|
||||||
const subfound = data.orgs.find(
|
// Find matching orgs between current org and current user's access to those orgs
|
||||||
(item) => item === found.id
|
if (
|
||||||
);
|
userdata.orgs !== undefined &&
|
||||||
if (
|
userdata.orgs !== null &&
|
||||||
subfound !== null &&
|
userdata.orgs.length > 0 &&
|
||||||
subfound !== undefined
|
selectedOrganization.child_orgs !== undefined &&
|
||||||
) {
|
selectedOrganization.child_orgs !== null &&
|
||||||
active.push(subfound);
|
selectedOrganization.child_orgs.length > 0
|
||||||
}
|
) {
|
||||||
}
|
var active = [];
|
||||||
|
for (var key in userdata.orgs) {
|
||||||
|
const found =
|
||||||
|
selectedOrganization.child_orgs.find(
|
||||||
|
(item) => item.id === userdata.orgs[key].id
|
||||||
|
);
|
||||||
|
if (found !== null && found !== undefined) {
|
||||||
|
if (
|
||||||
|
data.orgs === undefined ||
|
||||||
|
data.orgs === null
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
setMatchingOrganizations(active);
|
const subfound = data.orgs.find(
|
||||||
|
(item) => item === found.id
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
subfound !== null &&
|
||||||
|
subfound !== undefined
|
||||||
|
) {
|
||||||
|
active.push(subfound);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
>
|
|
||||||
<EditIcon color="primary" />
|
setMatchingOrganizations(active);
|
||||||
</IconButton>
|
}
|
||||||
{/*<Button
|
}}
|
||||||
onClick={() => {
|
>
|
||||||
generateApikey(data)
|
<EditIcon color="primary" />
|
||||||
}}
|
</IconButton>
|
||||||
disabled={data.role === "admin" && data.username !== userdata.username}
|
{/*<Button
|
||||||
variant="outlined"
|
onClick={() => {
|
||||||
color="primary"
|
generateApikey(data)
|
||||||
>
|
}}
|
||||||
New apikey
|
disabled={data.role === "admin" && data.username !== userdata.username}
|
||||||
</Button>*/}
|
variant="outlined"
|
||||||
</ListItemText>
|
color="primary"
|
||||||
<ListItemText
|
>
|
||||||
style={{ minWidth: 150, maxWidth: 150, }}
|
New apikey
|
||||||
primary={lastLogin}
|
</Button>*/}
|
||||||
><span/>
|
</ListItemText>
|
||||||
</ListItemText>
|
<ListItemText
|
||||||
</ListItem>
|
style={{ minWidth: 150, maxWidth: 150, }}
|
||||||
);
|
primary={lastLogin}
|
||||||
})}
|
><span/>
|
||||||
|
</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</List>
|
</List>
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
@@ -4131,6 +4251,46 @@ If you're interested, please let me know a time that works for you, or set up a
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
const getLogs = async (ip, userId) => {
|
||||||
|
setLogsLoading(true);
|
||||||
|
console.log("logs loading: ", logsLoading);
|
||||||
|
fetch(`${globalUrl}/api/v1/users/${userId}/audit?user_ip=${ip}`, {
|
||||||
|
mode: "cors",
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
crossDomain: true,
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
console.log("ResponseJSON: ", responseJson);
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
setLogs(responseJson.logs);
|
||||||
|
} else {
|
||||||
|
if (responseJson.success === false || responseJson.reason !== undefined) {
|
||||||
|
console.log("Reason given: ", responseJson.reason)
|
||||||
|
toast("Failed getting logs: " + responseJson.reason)
|
||||||
|
setLogs([])
|
||||||
|
} else {
|
||||||
|
toast("Failed getting logs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("logs loading now: ", logsLoading);
|
||||||
|
setLogsLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error: ", error);
|
||||||
|
toast("Failed getting logs. Please contact: ", error);
|
||||||
|
console.log("logs loading now: ", logsLoading);
|
||||||
|
setLogsLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const changeRecommendation = (recommendation, action) => {
|
const changeRecommendation = (recommendation, action) => {
|
||||||
const data = {
|
const data = {
|
||||||
action: action,
|
action: action,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { ToastContainer, toast } from "react-toastify"
|
|||||||
import { isMobile } from "react-device-detect"
|
import { isMobile } from "react-device-detect"
|
||||||
import aa from 'search-insights'
|
import aa from 'search-insights'
|
||||||
import Drift from "react-driftjs";
|
import Drift from "react-driftjs";
|
||||||
import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx";
|
import { CodeHandler, Img, OuterLink, } from "../views/Docs.jsx";
|
||||||
|
|
||||||
import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom';
|
import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom';
|
||||||
import algoliasearch from 'algoliasearch/lite';
|
import algoliasearch from 'algoliasearch/lite';
|
||||||
@@ -116,17 +116,12 @@ import {
|
|||||||
AutoAwesome as AutoAwesomeIcon,
|
AutoAwesome as AutoAwesomeIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
|
|
||||||
import * as cytoscape from "cytoscape";
|
import * as cytoscape from "cytoscape";
|
||||||
import * as edgehandles from "cytoscape-edgehandles";
|
import * as edgehandles from "cytoscape-edgehandles";
|
||||||
//import * as clipboard from "cytoscape-clipboard";
|
|
||||||
//import undoRedo from "cytoscape-undo-redo";
|
|
||||||
//import cxtmenu from "cytoscape-cxtmenu";
|
|
||||||
import CytoscapeComponent from "react-cytoscapejs";
|
import CytoscapeComponent from "react-cytoscapejs";
|
||||||
|
|
||||||
import Draggable from "react-draggable";
|
import Draggable from "react-draggable";
|
||||||
|
|
||||||
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
|
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
|
||||||
|
import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx";
|
||||||
|
|
||||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||||
import { GetParsedPaths, internalIds, } from "../views/Apps.jsx";
|
import { GetParsedPaths, internalIds, } from "../views/Apps.jsx";
|
||||||
@@ -842,48 +837,6 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function OuterLink(props) {
|
|
||||||
if (props.href.includes("http") || props.href.includes("mailto")) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={props.href}
|
|
||||||
style={{ color: "#f85a3e", textDecoration: "none" }}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to={props.href}
|
|
||||||
style={{ color: "#f85a3e", textDecoration: "none" }}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Img(props) {
|
|
||||||
return <img style={{ maxWidth: "100%" }} alt={props.alt} src={props.src} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CodeHandler(props) {
|
|
||||||
return (
|
|
||||||
<pre
|
|
||||||
style={{
|
|
||||||
padding: 15,
|
|
||||||
minWidth: "50%",
|
|
||||||
maxWidth: "100%",
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
overflowX: "auto",
|
|
||||||
overflowY: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<code>{props.value}</code>
|
|
||||||
</pre>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Heading(props) {
|
function Heading(props) {
|
||||||
const element = React.createElement(
|
const element = React.createElement(
|
||||||
`h${props.level}`,
|
`h${props.level}`,
|
||||||
@@ -1021,8 +974,6 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
console.log("GOT A RESPONSE??")
|
|
||||||
// getWorkflowExecutionCount(id);
|
|
||||||
if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) {
|
if (responseJson !== undefined && responseJson !== null && responseJson.executions !== undefined && responseJson.executions !== null) {
|
||||||
|
|
||||||
// - means it's opposite
|
// - means it's opposite
|
||||||
@@ -1035,8 +986,6 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
tmpView = execution_id;
|
tmpView = execution_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("EXECUTION ID: ", tmpView)
|
|
||||||
|
|
||||||
// Compare with currently selected item
|
// Compare with currently selected item
|
||||||
if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
|
if (tmpView !== undefined && tmpView !== null && tmpView.length > 0) {
|
||||||
// Don't clean up if it's already open
|
// Don't clean up if it's already open
|
||||||
@@ -1820,7 +1769,6 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("FOUNDMISSING: ", foundmissing)
|
|
||||||
if (foundmissing) {
|
if (foundmissing) {
|
||||||
//toast("This workflow contains a node that requires an execution argument. Please provide one.")
|
//toast("This workflow contains a node that requires an execution argument. Please provide one.")
|
||||||
setExecutionRequestStarted(false)
|
setExecutionRequestStarted(false)
|
||||||
@@ -2788,11 +2736,17 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
if (response.status >= 500) {
|
if (response.status >= 500) {
|
||||||
toast("Something went wrong while loading the workflow. Please reload.")
|
toast("Something went wrong while loading the workflow. Please reload.")
|
||||||
} else {
|
} else {
|
||||||
toast("You don't access to this workflow or loading failed. Redirecting to workflows in a few seconds..")
|
|
||||||
|
|
||||||
setTimeout(() => {
|
// Check for execution_id in URL
|
||||||
window.location.pathname = "/workflows";
|
// don't redirect if it exists
|
||||||
}, 2000);
|
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
|
||||||
|
var execFound = new URLSearchParams(cursearch).get("execution_id");
|
||||||
|
if (execFound === null) {
|
||||||
|
toast(`You don't access to this workflow or loading failed. Redirecting to workflows in a few seconds..`)
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.pathname = "/workflows";
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8518,8 +8472,6 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (param.name === "headers") {
|
if (param.name === "headers") {
|
||||||
console.log("Swap header? For now, yes. File found: ", fileid_found)
|
|
||||||
|
|
||||||
if (fileid_found) {
|
if (fileid_found) {
|
||||||
newSelectedAction.parameters[paramkey].value = ""
|
newSelectedAction.parameters[paramkey].value = ""
|
||||||
newSelectedAction.parameters[paramkey].autocompleted = true
|
newSelectedAction.parameters[paramkey].autocompleted = true
|
||||||
@@ -8614,7 +8566,6 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
|
|
||||||
if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
|
if (workflow.actions !== undefined && workflow.actions !== null && workflow.actions.length > 0) {
|
||||||
const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id)
|
const foundActionIndex = workflow.actions.findIndex(actiondata => actiondata.id === newSelectedAction.id)
|
||||||
console.log("Found action on index ", foundActionIndex)
|
|
||||||
if (foundActionIndex >= 0) {
|
if (foundActionIndex >= 0) {
|
||||||
workflow.actions[foundActionIndex] = newSelectedAction
|
workflow.actions[foundActionIndex] = newSelectedAction
|
||||||
setWorkflow(workflow)
|
setWorkflow(workflow)
|
||||||
@@ -9444,14 +9395,18 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
bottom: 10,
|
bottom: 10,
|
||||||
left: 10,
|
left: 10,
|
||||||
color: "rgba(255,255,255,0.6)",
|
color: "rgba(255,255,255,0.6)",
|
||||||
|
zIndex: 10000,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Conditions can't be used for loops [ .# ]{" "}
|
Conditions can't be used for loops [ .# ]{" "}
|
||||||
<a
|
<a
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://shuffler.io/docs/workflows#conditions"
|
href="/docs/workflows#conditions"
|
||||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
style={{
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "#f85a3e",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</a>
|
</a>
|
||||||
@@ -15935,7 +15890,10 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
setSelectedResult(data);
|
setSelectedResult(data);
|
||||||
setCodeModalOpen(true);
|
setCodeModalOpen(true);
|
||||||
} else {
|
} else {
|
||||||
toast("Please wait until the workflow is loaded and try again")
|
toast("Please wait until the workflow is loaded and try again")
|
||||||
|
setCodeModalOpen(true)
|
||||||
|
setSelectedResult(data)
|
||||||
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -16197,6 +16155,25 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span id="top_bar">
|
<span id="top_bar">
|
||||||
|
<Tooltip
|
||||||
|
title="Suggest solution"
|
||||||
|
placement="top"
|
||||||
|
style={{ zIndex: 50000 }}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
style={{
|
||||||
|
zIndex: 5000,
|
||||||
|
position: "absolute",
|
||||||
|
top: 34,
|
||||||
|
right: 210,
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AutoFixHighIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="Find successful execution"
|
title="Find successful execution"
|
||||||
placement="top"
|
placement="top"
|
||||||
@@ -16210,32 +16187,32 @@ const AngularWorkflow = (defaultprops) => {
|
|||||||
right: 170,
|
right: 170,
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
|
|
||||||
if (workflowExecutions !== null) {
|
if (workflowExecutions !== null) {
|
||||||
for (let execkey in workflowExecutions) {
|
for (let execkey in workflowExecutions) {
|
||||||
const execution = workflowExecutions[execkey];
|
const execution = workflowExecutions[execkey];
|
||||||
if (execution.execution_argument.includes("too large")) {
|
if (execution.execution_argument.includes("too large")) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = execution.results.find((data) => data.status === "SUCCESS" && data.action.id === selectedResult.action.id)
|
const result = execution.results.find((data) => data.status === "SUCCESS" && data.action.id === selectedResult.action.id)
|
||||||
|
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
const oldstartnode = cy.getElementById(selectedResult.action.id);
|
const oldstartnode = cy.getElementById(selectedResult.action.id);
|
||||||
if (oldstartnode !== undefined && oldstartnode !== null) {
|
if (oldstartnode !== undefined && oldstartnode !== null) {
|
||||||
const foundname = oldstartnode.data("label")
|
const foundname = oldstartnode.data("label")
|
||||||
if (foundname !== undefined && foundname !== null) {
|
if (foundname !== undefined && foundname !== null) {
|
||||||
result.action.label = foundname
|
result.action.label = foundname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedResult(result);
|
setSelectedResult(result);
|
||||||
setUpdate(Math.random());
|
setUpdate(Math.random());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DoneIcon style={{ color: "white" }} />
|
<DoneIcon style={{ color: "white" }} />
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export const appCategories = [
|
|||||||
"name": "IAM",
|
"name": "IAM",
|
||||||
"color": "#FFC107",
|
"color": "#FFC107",
|
||||||
"icon": "iam",
|
"icon": "iam",
|
||||||
"action_labels": ["Reset Password", "Enable user", "Disable user", "Get Identity", "Get Asset", "Search Identity", ],
|
"action_labels": ["Reset Password", "Enable user", "Disable user", "Get Identity", "Get Asset", "Search Identity", "Get KMS Key",],
|
||||||
}, {
|
}, {
|
||||||
"name": "Network",
|
"name": "Network",
|
||||||
"color": "#FFC107",
|
"color": "#FFC107",
|
||||||
@@ -1240,7 +1240,7 @@ const AppCreator = (defaultprops) => {
|
|||||||
|
|
||||||
if (methodvalue.responses.default.content["text/plain"]["schema"]["format"] === "binary" && methodvalue.responses.default.content["text/plain"]["schema"]["type"] === "string") {
|
if (methodvalue.responses.default.content["text/plain"]["schema"]["format"] === "binary" && methodvalue.responses.default.content["text/plain"]["schema"]["type"] === "string") {
|
||||||
newaction.example_response = "shuffle_file_download"
|
newaction.example_response = "shuffle_file_download"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2174,6 +2174,10 @@ const AppCreator = (defaultprops) => {
|
|||||||
queryitem.name.toLowerCase() == "ssl_verify" ||
|
queryitem.name.toLowerCase() == "ssl_verify" ||
|
||||||
queryitem.name.toLowerCase() == "queries" ||
|
queryitem.name.toLowerCase() == "queries" ||
|
||||||
queryitem.name.toLowerCase() == "headers" ||
|
queryitem.name.toLowerCase() == "headers" ||
|
||||||
|
queryitem.name.toLowerCase() == "list" ||
|
||||||
|
queryitem.name.toLowerCase() == "dict" ||
|
||||||
|
queryitem.name.toLowerCase() == "str" ||
|
||||||
|
queryitem.name.toLowerCase() == "int" ||
|
||||||
queryitem.name.toLowerCase() == "access_token") {
|
queryitem.name.toLowerCase() == "access_token") {
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -3748,7 +3752,7 @@ const AppCreator = (defaultprops) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormControl style={{ backgroundColor: surfaceColor, color: "white" }}>
|
<FormControl style={{ backgroundColor: surfaceColor, color: "white" }}>
|
||||||
<DialogTitle style={{marginTop: 30, }}>
|
<DialogTitle style={{marginTop: 45, }}>
|
||||||
<div style={{ color: "white" }}>New action</div>
|
<div style={{ color: "white" }}>New action</div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent style={{paddingBottom: 100, }}>
|
<DialogContent style={{paddingBottom: 100, }}>
|
||||||
|
|||||||
+237
-186
@@ -3,10 +3,12 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import Markdown from 'react-markdown'
|
import Markdown from 'react-markdown'
|
||||||
|
|
||||||
|
import theme from '../theme.jsx';
|
||||||
|
import ReactJson from "react-json-view";
|
||||||
|
import { isMobile } from "react-device-detect";
|
||||||
import { BrowserView, MobileView } from "react-device-detect";
|
import { BrowserView, MobileView } from "react-device-detect";
|
||||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||||
import { isMobile } from "react-device-detect";
|
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||||
import theme from '../theme.jsx';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Grid,
|
Grid,
|
||||||
@@ -30,6 +32,7 @@ import {
|
|||||||
Edit as EditIcon,
|
Edit as EditIcon,
|
||||||
KeyboardArrowRight as KeyboardArrowRightIcon,
|
KeyboardArrowRight as KeyboardArrowRightIcon,
|
||||||
ExpandMore as ExpandMoreIcon,
|
ExpandMore as ExpandMoreIcon,
|
||||||
|
FileCopy as FileCopyIcon
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
const Body = {
|
const Body = {
|
||||||
@@ -60,6 +63,120 @@ const innerHrefStyle = {
|
|||||||
textDecoration: "none",
|
textDecoration: "none",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const CopyToClipboard = (props) => {
|
||||||
|
const {text, style, onCopy} = props;
|
||||||
|
const parsedstyle = style !== undefined ? style : {
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: -10,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={parsedstyle}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
toast("Copied to clipboard")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileCopyIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OuterLink = (props) => {
|
||||||
|
if (props.href.includes("http") || props.href.includes("mailto")) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={props.href}
|
||||||
|
style={{ color: "#f85a3e", textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={props.href}
|
||||||
|
style={{ color: "#f85a3e", textDecoration: "none" }}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const Img = (props) => {
|
||||||
|
return <img style={{ borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CodeHandler = (props) => {
|
||||||
|
const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const validate = validateJson(propvalue)
|
||||||
|
|
||||||
|
var newprop = propvalue
|
||||||
|
if (validate.valid === false) {
|
||||||
|
// Check if https://shuffler.io in the url
|
||||||
|
// if so, then we change it for the current url
|
||||||
|
if (propvalue.includes("https://shuffler.io")) {
|
||||||
|
newprop = propvalue.replace("https://shuffler.io", window.location.origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it contains Bearer APIKEY
|
||||||
|
// If so, replace apikey
|
||||||
|
//if (newprop.includes("Bearer APIKEY")) {
|
||||||
|
// newprop = newprop.replace("Bearer APIKEY", "Bearer API
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: 15,
|
||||||
|
minWidth: "50%",
|
||||||
|
maxWidth: "100%",
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
overflowY: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{validate.valid === true ?
|
||||||
|
<ReactJson
|
||||||
|
src={validate.result}
|
||||||
|
theme={theme.palette.jsonTheme}
|
||||||
|
style={theme.palette.reactJsonStyle}
|
||||||
|
collapsed={false}
|
||||||
|
displayDataTypes={false}
|
||||||
|
name={""}
|
||||||
|
/>
|
||||||
|
:
|
||||||
|
<div style={{display: "flex", position: "relative", }}>
|
||||||
|
<code
|
||||||
|
style={{
|
||||||
|
// Wrap if larger than X
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
overflow: "auto",
|
||||||
|
marginRight: 40,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{newprop}
|
||||||
|
</code>
|
||||||
|
<CopyToClipboard
|
||||||
|
text={newprop}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const Docs = (defaultprops) => {
|
const Docs = (defaultprops) => {
|
||||||
const { globalUrl, selectedDoc, serverside, serverMobile } = defaultprops;
|
const { globalUrl, selectedDoc, serverside, serverMobile } = defaultprops;
|
||||||
|
|
||||||
@@ -121,6 +238,123 @@ const Docs = (defaultprops) => {
|
|||||||
//height: "50vh",
|
//height: "50vh",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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", position: "sticky", top: 50, }}>
|
||||||
|
{isMobile ? null : (
|
||||||
|
<Typography style={{ display: "inline", marginTop: 6 }}>
|
||||||
|
<a
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
href={selectedMeta.link}
|
||||||
|
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||||
|
>
|
||||||
|
<Button style={{ color: "white", }} variant="outlined" color="secondary">
|
||||||
|
<EditIcon /> Edit
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{isMobile ? 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 }}>
|
||||||
|
{isMobile ||
|
||||||
|
selectedMeta.contributors === undefined ||
|
||||||
|
selectedMeta.contributors === null ? (
|
||||||
|
""
|
||||||
|
) : (
|
||||||
|
<div style={{ margin: 10, height: "100%", display: "inline" }}>
|
||||||
|
{selectedMeta.contributors.slice(0, 7).map((data, index) => {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={index}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraInfo !== "" && props.level === 1 && props.children !== undefined && props.children !== null && props.children.length > 0) {
|
||||||
|
if (props.children[0].toLowerCase().includes("privacy") || props.children[0].toLowerCase().includes("terms")) {
|
||||||
|
extraInfo = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
onMouseOver={() => {
|
||||||
|
setHover(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.level !== 1 ? (
|
||||||
|
<Divider
|
||||||
|
style={{
|
||||||
|
width: "90%",
|
||||||
|
marginTop: 40,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{element}
|
||||||
|
{extraInfo}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const SideBar = {
|
const SideBar = {
|
||||||
minWidth: 300,
|
minWidth: 300,
|
||||||
width: "20%",
|
width: "20%",
|
||||||
@@ -382,190 +616,7 @@ const Docs = (defaultprops) => {
|
|||||||
fontSize: isMobile ? "1.3rem" : "1.1rem",
|
fontSize: isMobile ? "1.3rem" : "1.1rem",
|
||||||
};
|
};
|
||||||
|
|
||||||
function OuterLink(props) {
|
|
||||||
if (props.href.includes("http") || props.href.includes("mailto")) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
href={props.href}
|
|
||||||
style={{ color: "#f85a3e", textDecoration: "none" }}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to={props.href}
|
|
||||||
style={{ color: "#f85a3e", textDecoration: "none" }}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Img(props) {
|
|
||||||
return <img style={{ borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function CodeHandler(props) {
|
|
||||||
//console.log("Codehandler PROPS: ", props)
|
|
||||||
|
|
||||||
const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : ""
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: 15,
|
|
||||||
minWidth: "50%",
|
|
||||||
maxWidth: "100%",
|
|
||||||
backgroundColor: theme.palette.inputColor,
|
|
||||||
overflowY: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<code
|
|
||||||
style={{
|
|
||||||
// Wrap if larger than X
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
overflow: "auto",
|
|
||||||
}}
|
|
||||||
>{propvalue}</code>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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", position: "sticky", top: 50, }}>
|
|
||||||
{mobile ? null : (
|
|
||||||
<Typography style={{ display: "inline", marginTop: 6 }}>
|
|
||||||
<a
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
href={selectedMeta.link}
|
|
||||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
|
||||||
>
|
|
||||||
<Button style={{ color: "white", }} variant="outlined" color="secondary">
|
|
||||||
<EditIcon /> Edit
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
{mobile ? null : (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
height: "100%",
|
|
||||||
width: 1,
|
|
||||||
backgroundColor: "white",
|
|
||||||
marginLeft: 50,
|
|
||||||
marginRight: 50,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Typography style={{ display: "inline", marginTop: 11 }}>
|
|
||||||
{selectedMeta.read_time} minute
|
|
||||||
{selectedMeta.read_time === 1 ? "" : "s"} to read
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
<div style={{ flex: 2 }}>
|
|
||||||
{mobile ||
|
|
||||||
selectedMeta.contributors === undefined ||
|
|
||||||
selectedMeta.contributors === null ? (
|
|
||||||
""
|
|
||||||
) : (
|
|
||||||
<div style={{ margin: 10, height: "100%", display: "inline" }}>
|
|
||||||
{selectedMeta.contributors.slice(0, 7).map((data, index) => {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
key={index}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extraInfo !== "" && props.level === 1 && props.children !== undefined && props.children !== null && props.children.length > 0) {
|
|
||||||
if (props.children[0].toLowerCase().includes("privacy") || props.children[0].toLowerCase().includes("terms")) {
|
|
||||||
extraInfo = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
//React.createElement("p", {style: {color: "red", backgroundColor: "blue"}}, this.props.paragraph)
|
|
||||||
|
|
||||||
//function unicodeToChar(text) {
|
|
||||||
// return text.replace(/\\u[\dA-F]{4}/gi,
|
|
||||||
// function (match) {
|
|
||||||
// return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16));
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
const CustomButton = (props) => {
|
const CustomButton = (props) => {
|
||||||
|
|||||||
@@ -484,7 +484,6 @@ export const validateJson = (showResult) => {
|
|||||||
// This is where we start recursing
|
// This is where we start recursing
|
||||||
if (jsonvalid) {
|
if (jsonvalid) {
|
||||||
// Check fields if they can be parsed too
|
// Check fields if they can be parsed too
|
||||||
//console.log("In this window for the data. Should look for list in result! Does recursion.")
|
|
||||||
try {
|
try {
|
||||||
for (const [key, value] of Object.entries(result)) {
|
for (const [key, value] of Object.entries(result)) {
|
||||||
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
||||||
@@ -1649,6 +1648,7 @@ const Workflows = (props) => {
|
|||||||
addFilter(e.target.innerHTML);
|
addFilter(e.target.innerHTML);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasWorkflows = workflows === undefined || workflows === null || workflows.length === 0
|
||||||
const NewWorkflowPaper = () => {
|
const NewWorkflowPaper = () => {
|
||||||
const [hover, setHover] = React.useState(false);
|
const [hover, setHover] = React.useState(false);
|
||||||
|
|
||||||
@@ -1659,17 +1659,17 @@ const Workflows = (props) => {
|
|||||||
minWidth: paperAppStyle.width,
|
minWidth: paperAppStyle.width,
|
||||||
color: innerColor,
|
color: innerColor,
|
||||||
padding: paperAppStyle.padding,
|
padding: paperAppStyle.padding,
|
||||||
borderRadius: paperAppStyle.borderRadius,
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
border: `2px solid ${innerColor}`,
|
border: hasWorkflows ? `2px solid #f85a3e` : `2px solid ${innerColor}`,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
backgroundColor: hover ? "rgba(39,41,45,0.5)" : "rgba(39,41,45,1)",
|
backgroundColor: hover ? "rgba(39,41,45,0.5)" : "rgba(39,41,45,1)",
|
||||||
|
borderRadius: paperAppStyle.borderRadius,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid item xs={isMobile ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
|
<Grid item xs={isMobile ? 12 : hasWorkflows ? 12 : 4} style={{ padding: "12px 10px 12px 10px" }}>
|
||||||
<Paper
|
<Paper
|
||||||
square
|
square
|
||||||
style={setupPaperStyle}
|
style={setupPaperStyle}
|
||||||
@@ -1685,8 +1685,11 @@ const Workflows = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title={`New Workflow`} placement="bottom">
|
<Tooltip title={`New Workflow`} placement="bottom">
|
||||||
<span style={{ textAlign: "center", minWidth: 300, margin: "auto" }}>
|
<span style={{ textAlign: "center", minWidth: 240, margin: "auto" }}>
|
||||||
<AddCircleIcon style={{ height: 65, width: 65 }} />
|
<AddCircleIcon style={{ height: 65, width: 65 }} />
|
||||||
|
<Typography variant="h6" style={{ color: innerColor, margin: "auto" }}>
|
||||||
|
New Workflow
|
||||||
|
</Typography>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -3328,8 +3331,8 @@ const Workflows = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{width: "100%", minHeight: isMobile ? 0 : 51, maxHeight: isMobile ? 0 : 51, marginTop: 10, }}>
|
<div style={{width: "100%", minHeight: isMobile ? 0 : hasWorkflows ? 0 : 51, maxHeight: isMobile ? 0 : 51, marginTop: 10, }}>
|
||||||
{!isMobile && usecases !== null && usecases !== undefined && usecases.length > 0 ?
|
{!isMobile && !hasWorkflows && usecases !== null && usecases !== undefined && usecases.length > 0 ?
|
||||||
<div style={{ display: "flex", }}>
|
<div style={{ display: "flex", }}>
|
||||||
{usecases.map((usecase, index) => {
|
{usecases.map((usecase, index) => {
|
||||||
//console.log(usecase)
|
//console.log(usecase)
|
||||||
|
|||||||
Reference in New Issue
Block a user