Merge branch 'nightly' into automation
This commit is contained in:
+105
-4
@@ -34,9 +34,10 @@ import RunWorkflow from "./views/RunWorkflow.jsx";
|
||||
import Admin2 from "./views/Admin2.jsx";
|
||||
|
||||
import LoginPage from "./views/LoginPage.jsx";
|
||||
import LoginPageOld from "./views/LoginPageOld.jsx";
|
||||
|
||||
import SettingsPage from "./views/SettingsPage.jsx";
|
||||
import KeepAlive from "./views/KeepAlive.jsx";
|
||||
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
|
||||
@@ -59,6 +60,7 @@ import 'react-toastify/dist/ReactToastify.css';
|
||||
import Drift from "react-driftjs";
|
||||
|
||||
import { AppContext } from './context/ContextApi.jsx';
|
||||
import Navbar from "./components/Navbar.jsx";
|
||||
import Workflows2 from "./views/Workflows2.jsx";
|
||||
import AppExplorer from "./views/AppExplorer.jsx";
|
||||
|
||||
@@ -213,7 +215,20 @@ const App = (message, props) => {
|
||||
|
||||
{ window?.location?.pathname === "/" || window?.location?.pathname === "/training" || !(isLoggedIn && isLoaded) ? (
|
||||
<div style={{ minHeight: 68, maxHeight: 68 }}>
|
||||
<Header
|
||||
{/* <Header
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
userdata={userdata}
|
||||
cookies={cookies}
|
||||
removeCookie={removeCookie}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
isLoggedIn={isLoggedIn}
|
||||
curpath={curpath}
|
||||
{...props}
|
||||
/> */}
|
||||
<Navbar
|
||||
notifications={notifications}
|
||||
setNotifications={setNotifications}
|
||||
userdata={userdata}
|
||||
@@ -237,6 +252,7 @@ const App = (message, props) => {
|
||||
<div style={{ height: 60 }} />
|
||||
*/}
|
||||
<Routes>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/login"
|
||||
@@ -244,7 +260,8 @@ const App = (message, props) => {
|
||||
<LoginPage
|
||||
isLoggedIn={isLoggedIn}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
register={true}
|
||||
register={false}
|
||||
inregister={false}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setCookie={setCookie}
|
||||
@@ -254,6 +271,63 @@ const App = (message, props) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/login2"
|
||||
element={
|
||||
<LoginPageOld
|
||||
isLoggedIn={isLoggedIn}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
register={false}
|
||||
inregister={false}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setCookie={setCookie}
|
||||
cookies={cookies}
|
||||
checkLogin={checkLogin}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/loginsetup"
|
||||
element={
|
||||
<LoginPageOld
|
||||
isLoggedIn={isLoggedIn}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
register={false}
|
||||
inregister={false}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setCookie={setCookie}
|
||||
cookies={cookies}
|
||||
checkLogin={checkLogin}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/register"
|
||||
element={
|
||||
<LoginPage
|
||||
isLoggedIn={isLoggedIn}
|
||||
setIsLoggedIn={setIsLoggedIn}
|
||||
inregister={true}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
setCookie={setCookie}
|
||||
cookies={cookies}
|
||||
checkLogin={checkLogin}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/admin2"
|
||||
@@ -356,6 +430,7 @@ const App = (message, props) => {
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/AdminSetup"
|
||||
@@ -368,6 +443,7 @@ const App = (message, props) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/detectionframework"
|
||||
@@ -560,6 +636,32 @@ const App = (message, props) => {
|
||||
<Route exact path="/forms/:key/run" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} />
|
||||
<Route exact path="/forms/:key" element={<RunWorkflow serverside={false} userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} surfaceColor={theme.palette.surfaceColor} inputColor={theme.palette.inputColor}{...props} />} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
path="/legal/:key"
|
||||
element={
|
||||
<Docs
|
||||
isMobile={isMobile}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
isLoggedIn={isLoggedIn}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/legal"
|
||||
element={
|
||||
<Docs
|
||||
isMobile={isMobile}
|
||||
isLoaded={isLoaded}
|
||||
globalUrl={globalUrl}
|
||||
isLoggedIn={isLoggedIn}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/docs/:key"
|
||||
@@ -577,7 +679,6 @@ const App = (message, props) => {
|
||||
exact
|
||||
path="/docs"
|
||||
element={
|
||||
//navigate(`/docs/about`)
|
||||
<Docs
|
||||
isMobile={isMobile}
|
||||
isLoaded={isLoaded}
|
||||
|
||||
@@ -874,15 +874,18 @@ const AppAuthTab = memo((props) => {
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
style={{ color: '#1a1a1a', textTransform: 'none', backgroundColor: "#FF8444", marginLeft:"auto", borderRadius: 4,fontSize: 16, minWidth: 162, height: 40, boxShadow:'none', }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!isCloud}
|
||||
onClick={() => setShowAppModal(true)}
|
||||
>
|
||||
Add App Auth
|
||||
</Button>
|
||||
|
||||
{isCloud ?
|
||||
<Button
|
||||
style={{ color: '#1a1a1a', textTransform: 'none', backgroundColor: "#FF8444", marginLeft:"auto", borderRadius: 4,fontSize: 16, minWidth: 162, height: 40, boxShadow:'none', }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!isCloud}
|
||||
onClick={() => setShowAppModal(true)}
|
||||
>
|
||||
Add App Auth
|
||||
</Button>
|
||||
: null}
|
||||
</div>
|
||||
{/* <Divider
|
||||
style={{
|
||||
@@ -1217,6 +1220,8 @@ const AppAuthTab = memo((props) => {
|
||||
{editAuthenticationModal}
|
||||
{authenticationView}
|
||||
<div style={{marginTop: 50, }}>
|
||||
|
||||
{/*
|
||||
<div style={{ marginTop: 150, marginBottom: 20 }}>
|
||||
<h2 style={{ color: "#FFFFFF" }}>App Authentication Groups</h2>
|
||||
<span style={{ marginLeft: 0 }}>
|
||||
@@ -1415,13 +1420,14 @@ const AppAuthTab = memo((props) => {
|
||||
);
|
||||
}
|
||||
)}
|
||||
</List>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</List>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import CreateIcon from '@mui/icons-material/Create'
|
||||
import { toast } from 'react-toastify'
|
||||
import YAML from "yaml";
|
||||
|
||||
|
||||
const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
|
||||
const [openApiModal, setOpenApiModal] = useState(false)
|
||||
const [generateAppModal, setGenerateAppModal] = useState(false)
|
||||
@@ -59,6 +58,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
|
||||
: "1px solid rgba(255,255,255,0.3)",
|
||||
borderImage: makeFancy ? "linear-gradient(to right, #ff8544 0%, #ec517c 50%, #9c5af2 100%) 1" : "none",
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
paddingBottom: isCloud ? 0 : 175,
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -256,26 +256,27 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
|
||||
body: openApidata,
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
.then((response) => {
|
||||
|
||||
setValidation(false);
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success) {
|
||||
setAppValidation(responseJson.id);
|
||||
} else {
|
||||
if (responseJson.reason !== undefined) {
|
||||
setOpenApiError(responseJson.reason);
|
||||
}
|
||||
toast("An error occurred in the response");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setValidation(false);
|
||||
toast(error.toString());
|
||||
setOpenApiError(error.toString());
|
||||
});
|
||||
setValidation(false);
|
||||
return response.json();
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson?.success === true) {
|
||||
setAppValidation(responseJson?.id)
|
||||
navigate(`/apps/new?id=${responseJson?.id}`)
|
||||
} else {
|
||||
if (responseJson.reason !== undefined) {
|
||||
setOpenApiError(responseJson.reason)
|
||||
}
|
||||
toast("An error occurred in the response");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setValidation(false);
|
||||
toast(error.toString());
|
||||
setOpenApiError(error.toString());
|
||||
});
|
||||
};
|
||||
|
||||
const redirectOpenApi = () => {
|
||||
@@ -416,7 +417,7 @@ const AppCreationModal = ({ open, onClose, theme, globalUrl, isCloud }) => {
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ p: 3 }}>
|
||||
<div style={{ display: "flex", gap: '16px' }}>
|
||||
<div style={{ display: "flex", gap: 16, }}>
|
||||
<AppCreateButton
|
||||
text="Upload OpenAPI or Swagger"
|
||||
func={() => {
|
||||
|
||||
@@ -77,6 +77,8 @@ const Billing = memo((props) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [deleteAlertIndex, setDeleteAlertIndex] = useState(-1);
|
||||
const [deleteAlertVerification, setDeleteAlertVerification] = useState(false);
|
||||
const [isScale, setIsScale] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) {
|
||||
const percentage = (userdata.app_execution_usage / userdata.app_execution_limit) * 100;
|
||||
@@ -176,11 +178,10 @@ const Billing = memo((props) => {
|
||||
padding: 20,
|
||||
// maxWidth: 400,
|
||||
width: 340,
|
||||
height: 480,
|
||||
height: 'auto',
|
||||
// width: "100%",
|
||||
backgroundColor: theme.palette.backgroundColor,
|
||||
borderRadius: theme.palette?.borderRadius * 2,
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
backgroundColor: "#1e1e1e",
|
||||
borderRadius: 10,
|
||||
marginRight: 10,
|
||||
marginTop: 15,
|
||||
}
|
||||
@@ -361,7 +362,7 @@ const Billing = memo((props) => {
|
||||
var showSupport = false
|
||||
if (subscription.name.includes("default")) {
|
||||
top_text = "Custom Contract"
|
||||
newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
|
||||
// newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
|
||||
showSupport = true
|
||||
}
|
||||
|
||||
@@ -377,16 +378,17 @@ const Billing = memo((props) => {
|
||||
|
||||
if (subscription.name.includes("Scale")) {
|
||||
top_text = "Scale access"
|
||||
setIsScale(true)
|
||||
}
|
||||
|
||||
if (highlight === true) {
|
||||
// Add an "Upgrade now" button
|
||||
newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
|
||||
// newPaperstyle.border = "1px solid rgba(255,255,255,0.3)"
|
||||
}
|
||||
|
||||
if (hovered) {
|
||||
newPaperstyle.backgroundColor = "#2b2b2b"
|
||||
}
|
||||
// if (hovered) {
|
||||
// newPaperstyle.backgroundColor = "#2b2b2b"
|
||||
// }
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpenChangeEmailBox(true);
|
||||
@@ -556,6 +558,7 @@ const Billing = memo((props) => {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button style={{ backgroundColor: '#2F2F2F', color: "white", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', width: 144, height: 40 }}>Current Plan</Button>
|
||||
<div style={{ display: "flex", width: 340 }}>
|
||||
{top_text === "Base Cloud Access" && userdata.has_card_available === true ?
|
||||
<Chip
|
||||
@@ -659,8 +662,8 @@ const Billing = memo((props) => {
|
||||
<TextField
|
||||
value={feature.split("Worker License: ")[1]}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
backgroundColor: theme.palette.textFieldStyle.backgroundColor,
|
||||
borderRadius: theme.palette?.textFieldStyle.borderRadius,
|
||||
}}
|
||||
id={fieldId}
|
||||
onClick={() => { }}
|
||||
@@ -2013,7 +2016,33 @@ const Billing = memo((props) => {
|
||||
</Typography>
|
||||
: null}
|
||||
|
||||
<div style={{ display: "flex", width: clickedFromOrgTab ? "100%" : "auto", overflowX: 'auto', overflowY: 'hidden', scrollbarWidth: 'thin', scrollbarColor: '#494949 #2f2f2f', height: isChildOrg ? 0 : "100%", maxWidth: 860, marginTop: 20}} >
|
||||
<div style={{ display: "flex", width: clickedFromOrgTab ? "100%" : "auto", overflowX: 'auto', overflowY: 'hidden', scrollbarWidth: 'thin', scrollbarColor: '#494949 #2f2f2f', height: isChildOrg ? 0 : "100%", marginTop: 20}} >
|
||||
<div style={{ display: "flex", flexDirection: "column", width: "100%", }}>
|
||||
{isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 &&
|
||||
!isChildOrg ?
|
||||
selectedOrganization.subscriptions
|
||||
.reverse()
|
||||
.map((sub, index) => {
|
||||
return (
|
||||
<SubscriptionObject
|
||||
index={index + 1}
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={serverside}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={sub}
|
||||
highlight={true}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "row", width: "100%", marginTop: 20, marginBottom: 20, maxWidth: 860, }}>
|
||||
{isCloud && billingInfo.subscription !== undefined && billingInfo.subscription !== null ? isChildOrg ? null :
|
||||
<LicencePopup
|
||||
serverside={serverside}
|
||||
@@ -2026,6 +2055,7 @@ const Billing = memo((props) => {
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
stripeKey={stripeKey}
|
||||
isScale={isScale}
|
||||
{...props}
|
||||
/>
|
||||
: !isCloud ?
|
||||
@@ -2061,6 +2091,7 @@ const Billing = memo((props) => {
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
stripeKey={stripeKey}
|
||||
isScale={isScale}
|
||||
/>
|
||||
|
||||
{/* <SubscriptionObject
|
||||
@@ -2084,30 +2115,9 @@ const Billing = memo((props) => {
|
||||
/> */}
|
||||
</span>
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 &&
|
||||
!isChildOrg ?
|
||||
selectedOrganization.subscriptions
|
||||
.reverse()
|
||||
.map((sub, index) => {
|
||||
return (
|
||||
<SubscriptionObject
|
||||
index={index + 1}
|
||||
globalUrl={globalUrl}
|
||||
userdata={userdata}
|
||||
serverside={serverside}
|
||||
billingInfo={billingInfo}
|
||||
stripeKey={stripeKey}
|
||||
selectedOrganization={selectedOrganization}
|
||||
subscription={sub}
|
||||
highlight={true}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
{/*
|
||||
<Grid item key={index} xs={12/selectedOrganization.subscriptions.length}>
|
||||
<Card
|
||||
@@ -2153,7 +2163,6 @@ const Billing = memo((props) => {
|
||||
*/}
|
||||
</div>
|
||||
|
||||
|
||||
{/*isCloud &&
|
||||
selectedOrganization.partner_info !== undefined &&
|
||||
selectedOrganization.partner_info.reseller === true ? (
|
||||
|
||||
@@ -579,12 +579,20 @@ const EditWorkflow = (props) => {
|
||||
<Typography variant="h4" style={{ marginTop: 50, }}>
|
||||
Multi-Tenancy, Backups & Security
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 30, marginBottom: 10, }}>
|
||||
Multi-Tenant Workflows. Make one workflow, and keep a separate, synced copy in all your tenants. Control distributed auth, runtime locations, files, datastore keys etc. (contact support@shuffler.io if you want a demo. Please try it!)
|
||||
Control mechanisms for multi-tenancy, backups, and security.
|
||||
</Typography>
|
||||
|
||||
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
|
||||
<Typography variant="h6" style={{ marginTop: 50, }}>
|
||||
Multi-Tenant Workflows
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, marginBottom: 10, }}>
|
||||
Make one workflow, and keep a separate, synced copy in your other tenants. Control distributed auth, runtime locations, files, datastore keys etc. Can only distribute from parent org to child org. Need help trying it? <a href="https://shuffler.io/contact" target="_blank" style={{color: "#f85a3e", textDecoration: "none",}}>Contact us for a demo</a>
|
||||
</Typography>
|
||||
|
||||
{userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ?
|
||||
|
||||
userdata.orgs.filter(org => org.creator_org === userdata.active_org.id).length === 0 ?
|
||||
userdata.active_org.creator_org === undefined || userdata.active_org.creator_org === null || userdata.active_org.creator_org === "" ?
|
||||
<Typography variant="body2" style={{ marginTop: 10, color: "rgba(255,255,255,0.7)" }}>
|
||||
@@ -602,6 +610,7 @@ const EditWorkflow = (props) => {
|
||||
multiple
|
||||
style={{ marginTop: 10, }}
|
||||
value={innerWorkflow.suborg_distribution === undefined || innerWorkflow.suborg_distribution === null ? ["none"] : innerWorkflow.suborg_distribution}
|
||||
disabled={workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0}
|
||||
onChange={(e) => {
|
||||
var newvalue = e.target.value
|
||||
if (newvalue.length > 1 && newvalue[0] === "none") {
|
||||
|
||||
@@ -1420,7 +1420,7 @@ const EnvironmentTab = memo((props) => {
|
||||
<Collapse in={listItemExpanded === index} timeout="auto" unmountOnExit>
|
||||
<Grid container justifyContent="center" style={{minWidth: 850, maxWidth: 850, }}>
|
||||
<Grid item xs={12} sm={8} md={6}>
|
||||
<div style={{minWidth: 700, maxWidth: 700, minHeight: 350, display: 'flex', justifyContent: "center", backgroundColor: "transparent", }}>
|
||||
<div style={{minWidth: 750, maxWidth: 750, minHeight: 350, display: 'flex', justifyContent: "center", backgroundColor: "transparent", }}>
|
||||
<div style={{ paddingTop: 50, paddingBottom: 100, }}>
|
||||
<Typography variant="h6">
|
||||
Self-Hosted Orborus instance
|
||||
|
||||
@@ -87,21 +87,21 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
|
||||
return orgOptions.find((option) => option.name === selectedOrg);
|
||||
}, [selectedOrg, orgOptions]);
|
||||
|
||||
//With this code it is opening search bar on google chrome search bar as well which is not required
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
||||
event.preventDefault();
|
||||
setSearchBarModalOpen((prev)=> !prev);
|
||||
}
|
||||
};
|
||||
//With this code it is opening search bar on google chrome search bar as well which is not required
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
||||
event.preventDefault();
|
||||
setSearchBarModalOpen((prev)=> !prev);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [setSearchBarModalOpen]);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, [setSearchBarModalOpen]);
|
||||
|
||||
const CustomPopper = (props) => {
|
||||
return (
|
||||
@@ -155,6 +155,7 @@ useEffect(() => {
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
|
||||
<Link to="/admin?tab=tenants" style={hrefStyle}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -1605,6 +1606,18 @@ useEffect(() => {
|
||||
marginLeft: 5,
|
||||
}}
|
||||
>
|
||||
|
||||
{expandLeftNav &&
|
||||
<Button
|
||||
variant="outlined"
|
||||
style={{marginBottom: 15, borderWidth: 2, }}
|
||||
onClick={() => {
|
||||
window.open("https://shuffler.io/contact?category=book_a_demo", "_blank")
|
||||
}}
|
||||
>
|
||||
Book a Demo
|
||||
</Button>
|
||||
}
|
||||
<Box ref={autocompleteRef}>
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
@@ -1898,8 +1911,6 @@ useEffect(() => {
|
||||
};
|
||||
|
||||
export default LeftSideBar;
|
||||
|
||||
|
||||
const ModalView = memo(({searchBarModalOpen, setSearchBarModalOpen, globalUrl, serverside, userdata}) => {
|
||||
return (
|
||||
(
|
||||
|
||||
@@ -46,7 +46,7 @@ import { handlePayasyougo } from "../views/HandlePaymentNew.jsx"
|
||||
import Billing from "./Billing.jsx";
|
||||
|
||||
const LicencePopup = (props) => {
|
||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, setModalOpen, isLoggedIn, isMobile, selectedOrganization, isCloud } = props;
|
||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, setModalOpen, isScale, isLoggedIn, isMobile, selectedOrganization, isCloud } = props;
|
||||
//const alert = useAlert();
|
||||
let navigate = useNavigate();
|
||||
const [selectedDealModalOpen, setSelectedDealModalOpen] = React.useState(false);
|
||||
@@ -165,11 +165,25 @@ const LicencePopup = (props) => {
|
||||
]
|
||||
}
|
||||
|
||||
if (userdata?.app_execution_limit >= 300000) {
|
||||
subscription.name = "Enterprise"
|
||||
subscription.currency_text = "$"
|
||||
subscription.price = typecost_single
|
||||
subscription.limit = userdata?.app_execution_limit
|
||||
subscription.interval = "app run / month"
|
||||
subscription.features = [
|
||||
"Includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. ",
|
||||
"Multi-Tenancy and Region-Selection",
|
||||
"And all other features from /pricing",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
var newPaperstyle = JSON.parse(JSON.stringify(paperStyle))
|
||||
if (subscription.name === "Enterprise" && subscription.active === true) {
|
||||
top_text = "Current Plan"
|
||||
top_text = "Enterprise Plan"
|
||||
|
||||
newPaperstyle.border = "1px solid #f85a3e"
|
||||
// newPaperstyle.border = "1px solid #f85a3e"
|
||||
}
|
||||
|
||||
var showSupport = false
|
||||
@@ -255,7 +269,7 @@ const LicencePopup = (props) => {
|
||||
style={{ borderRadius: theme.palette?.borderRadius, }}
|
||||
placement="bottom"
|
||||
>
|
||||
<div style={{}}>
|
||||
<div style={{ backgroundColor: "#1e1e1e"}}>
|
||||
<Paper
|
||||
style={newPaperstyle}
|
||||
// onMouseEnter={() => setHovered(true)}
|
||||
@@ -343,9 +357,9 @@ const LicencePopup = (props) => {
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button style={{ backgroundColor: '#2F2F2F', color: "white", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', width: 144, height: 40 }}>Current Plan</Button>
|
||||
{subscription.active === true && !isScale && <Button style={{ backgroundColor: '#2F2F2F', color: "white", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', width: 144, height: 40 }}>Current Plan</Button> }
|
||||
<div style={{ display: "flex" }}>
|
||||
{top_text === "Base Cloud Access" && userdata.has_card_available === true ?
|
||||
{top_text === "Base Cloud Access" && userdata.has_card_available === true && !isScale ?
|
||||
<Chip
|
||||
style={{
|
||||
backgroundColor: "#f86a3e",
|
||||
@@ -511,7 +525,10 @@ const LicencePopup = (props) => {
|
||||
"While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month."
|
||||
:
|
||||
isCloud ?
|
||||
`You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.`
|
||||
userdata?.app_execution_limit && userdata?.app_execution_limit >= 300000 ?
|
||||
"You have subscribed to the Enterprise plan, which includes " + (userdata?.app_execution_limit/1000) + "K app runs/month. You can increase the limit by upgrading current plan. Contact support@shuffler.io for more information." :
|
||||
`You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.`
|
||||
|
||||
:
|
||||
`You are not subscribed to any plan and are using the free, open source plan. This plan has no enforced limits, but scale issues may occur due to CPU congestion.`
|
||||
}
|
||||
@@ -699,8 +716,33 @@ const LicencePopup = (props) => {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(8)
|
||||
} else {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(300)
|
||||
if (userdata && userdata?.app_execution_limit) {
|
||||
if (userdata.app_execution_limit >= 300000 && userdata.app_execution_limit < 400000) {
|
||||
setSelectedValue(400)
|
||||
setCalculatedCost("$1280")
|
||||
}else if (userdata?.app_execution_limit >= 400000 && userdata?.app_execution_limit < 500000) {
|
||||
setSelectedValue(500)
|
||||
setCalculatedCost("$1600")
|
||||
} else if (userdata?.app_execution_limit >= 500000 && userdata?.app_execution_limit < 600000) {
|
||||
setSelectedValue(600)
|
||||
setCalculatedCost("$1920")
|
||||
} else if (userdata?.app_execution_limit >= 600000 && userdata?.app_execution_limit < 700000) {
|
||||
setSelectedValue(700)
|
||||
setCalculatedCost("$2240")
|
||||
} else if (userdata?.app_execution_limit >= 700000 && userdata?.app_execution_limit < 800000) {
|
||||
setSelectedValue(800)
|
||||
setCalculatedCost("$2560")
|
||||
} else if (userdata?.app_execution_limit >= 800000 && userdata?.app_execution_limit < 900000) {
|
||||
setSelectedValue(900)
|
||||
setCalculatedCost("$2880")
|
||||
}else {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(300)
|
||||
}
|
||||
}else {
|
||||
setCalculatedCost("$960")
|
||||
setSelectedValue(300)
|
||||
}
|
||||
}
|
||||
}, [shuffleVariant])
|
||||
|
||||
@@ -864,10 +906,14 @@ const LicencePopup = (props) => {
|
||||
return
|
||||
}
|
||||
|
||||
const priceItem = window.location.origin === "https://shuffler.io" ?
|
||||
shuffleVariant === 0 ? "app_executions" : "cores"
|
||||
:
|
||||
shuffleVariant === 0 ? "price_1PZPSSEJjT17t98NLJoTMYja" : "price_1PZPQuEJjT17t98N3yORUtd9"
|
||||
const priceItem =
|
||||
window.location.origin === "https://shuffler.io/" || "https://sandbox.shuffler.io/"
|
||||
? shuffleVariant === 0
|
||||
? "app_executions"
|
||||
: "cores"
|
||||
: shuffleVariant === 0
|
||||
? "price_1PZPSSEJjT17t98NLJoTMYja"
|
||||
: "price_1PZPQuEJjT17t98N3yORUtd9";
|
||||
|
||||
const successUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=success`
|
||||
const failUrl = `${window.location.origin}/admin?admin_tab=billingstats&payment=failure`
|
||||
@@ -973,7 +1019,7 @@ const LicencePopup = (props) => {
|
||||
</span>
|
||||
: null}
|
||||
|
||||
{isCloud &&
|
||||
{/* {isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 ?
|
||||
@@ -994,7 +1040,7 @@ const LicencePopup = (props) => {
|
||||
/>
|
||||
)
|
||||
})
|
||||
: null}
|
||||
: null} */}
|
||||
</Grid>
|
||||
<Grid item xs={8}>
|
||||
<Grid style={{}}>
|
||||
@@ -1002,12 +1048,12 @@ const LicencePopup = (props) => {
|
||||
<Card style={{
|
||||
padding: 20,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
border: "1px solid #f85a3e",
|
||||
border: !isScale ? "1px solid #f85a3e" : 'none',
|
||||
}}>
|
||||
<div>
|
||||
<Button style={{ backgroundColor: 'rgba(255, 132, 68, 0.2)', color: "#FF8444", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', }}
|
||||
{ !isScale && <Button style={{ backgroundColor: 'rgba(255, 132, 68, 0.2)', color: "#FF8444", textTransform: "capitalize", borderRadius: 200, boxShadow: 'none', }}
|
||||
variant="contained"
|
||||
color="primary">Recommended </Button>
|
||||
color="primary">Recommended </Button> }
|
||||
|
||||
</div>
|
||||
<Typography variant="h6" style={{ marginTop: 10, marginBottom: 10, flex: 5, }}>{shuffleVariant === 1 ? "Scale" : "Enterprise"}</Typography>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -359,7 +359,7 @@ const RuntimeDebugger = (props) => {
|
||||
imageSource = "/images/no_image.png"
|
||||
}
|
||||
}else {
|
||||
if (userdata.active_org.image?.length > 0){
|
||||
if (userdata?.active_org.image?.length > 0){
|
||||
imageSource = userdata?.active_org?.image
|
||||
}else {
|
||||
imageSource = "/images/no_image.png"
|
||||
|
||||
@@ -497,7 +497,9 @@ const TenantsTab = memo((props) => {
|
||||
response.json().then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
if (responseJson.reason !== undefined) {
|
||||
toast(responseJson.reason);
|
||||
toast.error(responseJson.reason, {
|
||||
autoClose: 5000,
|
||||
})
|
||||
} else {
|
||||
toast("Failed creating suborg. Please try again");
|
||||
}
|
||||
@@ -781,9 +783,9 @@ const TenantsTab = memo((props) => {
|
||||
<div style={{height: "100%", maxHeight: 1700, overflowY: "auto",overflowX: 'hidden', scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}>
|
||||
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Organizations</h2>
|
||||
<h2 style={{ marginBottom: 8, marginTop: 0, color: "#ffffff" }}>Tenants</h2>
|
||||
<span style={{ color: textColor }}>
|
||||
Control sub organizations (tenants)! {" "}
|
||||
Create, manage and change to sub-organizations (tenants)! {" "}
|
||||
{isCloud
|
||||
? "You can only make a sub organization if you are a customer of shuffle or running a POC of the platform. Please contact support@shuffler.io to try it out."
|
||||
: ''}
|
||||
|
||||
@@ -1557,10 +1557,12 @@ const UserManagmentTab = memo((props) => {
|
||||
style={{ display:'table-cell', verticalAlign: 'middle' }}
|
||||
/>
|
||||
|
||||
{/*
|
||||
<ListItemText
|
||||
primary={data.active ? "True" : "False"}
|
||||
style={{display:'table-cell',verticalAlign: 'middle' , padding: "8px", textAlign: "center", color: data.active ? "#02CB70" : "#F53434" }}
|
||||
/>
|
||||
*/}
|
||||
|
||||
<ListItemText
|
||||
primary={
|
||||
@@ -1573,7 +1575,6 @@ const UserManagmentTab = memo((props) => {
|
||||
style={{ display:'table-cell',verticalAlign: 'middle', padding: "8px", }}
|
||||
/>
|
||||
|
||||
{/*
|
||||
<ListItemText
|
||||
primary={
|
||||
data?.mfa_info !== undefined &&
|
||||
@@ -1584,7 +1585,6 @@ const UserManagmentTab = memo((props) => {
|
||||
}
|
||||
style={{ display:'table-cell', verticalAlign: 'middle',padding: "8px", color: data.mfa_info.active ? "#02CB70" : "#F53434" }}
|
||||
/>
|
||||
*/}
|
||||
|
||||
{selectedOrganization?.child_orgs !== undefined &&
|
||||
selectedOrganization?.child_orgs !== null &&
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { createContext, useState, useEffect } from 'react';
|
||||
import React, { createContext, useState, useEffect } from 'react';
|
||||
export const Context = createContext();
|
||||
|
||||
export const AppContext =(props) => {
|
||||
export const AppContext = (props) => {
|
||||
const { serverside } = props
|
||||
|
||||
const currentLocation = window?.location?.pathname;
|
||||
const currentLocation = serverside === true ? "" : window?.location?.pathname;
|
||||
|
||||
// Left side bar global states
|
||||
const [searchBarModalOpen, setSearchBarModalOpen] = useState(false);
|
||||
const [leftSideBarOpenByClick, setLeftSideBarOpenByClick] = useState(currentLocation?.includes('/workflows/') ? false : true)
|
||||
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
|
||||
const [windowWidth, setWindowWidth] = useState(serverside === true ? 100 : window.innerWidth);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentLocation?.includes('/workflows/') && leftSideBarOpenByClick === true) {
|
||||
@@ -18,6 +19,10 @@ export const AppContext =(props) => {
|
||||
|
||||
//Calculate window width
|
||||
useEffect(() => {
|
||||
if (serverside === true) {
|
||||
return
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
setWindowWidth(window?.innerWidth);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import React, { useState } from "react";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import theme from '../theme.jsx';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import {
|
||||
CircularProgress,
|
||||
@@ -20,11 +22,8 @@ const surfaceColor = "#27292D";
|
||||
const inputColor = "#383B40";
|
||||
|
||||
const boxStyle = {
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
backgroundColor: surfaceColor,
|
||||
padding: 40,
|
||||
backgroundColor: theme.palette.backgroundColor,
|
||||
};
|
||||
|
||||
const useStyles = makeStyles({
|
||||
@@ -41,8 +40,10 @@ const AdminAccount = (props) => {
|
||||
const [firstRequest, setFirstRequest] = useState(true);
|
||||
const [loginLoading, setLoginLoading] = useState(false);
|
||||
|
||||
|
||||
// Used to swap from login to register. True = login, false = register
|
||||
const register = true;
|
||||
let navigate = useNavigate();
|
||||
|
||||
const classes = useStyles();
|
||||
// Error messages etc
|
||||
@@ -70,13 +71,16 @@ const AdminAccount = (props) => {
|
||||
setLoginInfo(responseJson["reason"]);
|
||||
} else {
|
||||
if (responseJson.reason === "redirect") {
|
||||
window.location.pathname = "/login";
|
||||
setTimeout(() => {
|
||||
window.location.pathname = "/login";
|
||||
}, 2500)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
setLoginInfo("Error in userdata: ", error);
|
||||
setLoginInfo("Error in userdata (1): ", error);
|
||||
navigate("/loginsetup")
|
||||
});
|
||||
};
|
||||
|
||||
@@ -108,13 +112,17 @@ const AdminAccount = (props) => {
|
||||
setLoginInfo(responseJson["reason"]);
|
||||
} else {
|
||||
setLoginInfo("Successful register :)");
|
||||
window.location.pathname = "/login";
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.pathname = "/login";
|
||||
}, 2500)
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
setLoginLoading(false);
|
||||
setLoginInfo("Error in userdata: ", error);
|
||||
setLoginInfo("Error in userdata (2): ", error);
|
||||
setLoginLoading(false)
|
||||
navigate("/loginsetup")
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -575,11 +575,17 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}, [editWorkflowModalOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if(selectedTrigger?.trigger_type === "SUBFLOW"){
|
||||
// Check if selectedTriggerIndex is a number >= 0
|
||||
if (isNaN(selectedTriggerIndex) || selectedTriggerIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedTrigger?.trigger_type === "SUBFLOW" && selectedTriggerIndex !== undefined && selectedTriggerIndex !== null && workflow?.triggers[selectedTriggerIndex].parameters.length > 1) {
|
||||
setSelectedTriggerValue(workflow.triggers[selectedTriggerIndex].parameters[1].value || "")
|
||||
} else if(selectedTrigger?.trigger_type === "USERINPUT"){
|
||||
} else if (selectedTrigger?.trigger_type === "USERINPUT" && selectedTriggerIndex !== undefined && selectedTriggerIndex !== null && workflow?.triggers[selectedTriggerIndex].parameters.length > 0) {
|
||||
setSelectedTriggerValue(workflow.triggers[selectedTriggerIndex].parameters[0].value || "Do you want to continue the workflow? Start parameters: $exec")
|
||||
}
|
||||
|
||||
}, [selectedTriggerIndex, selectedTrigger, workflow])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -948,11 +954,15 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (workflow.actions?.length == 1) {
|
||||
if (workflow.actions[0].app_id == "3e320a20966d33c9b7e6790b2705f0bf") {
|
||||
if (workflow?.actions?.length == 1) {
|
||||
if (workflow?.actions[0].app_id == "3e320a20966d33c9b7e6790b2705f0bf") {
|
||||
setWorkflowAsCode(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (workflow?.suborg_distribution !== undefined && workflow?.suborg_distribution !== null && workflow?.suborg_distribution.length > 0) {
|
||||
getChildWorkflows(workflow.id)
|
||||
}
|
||||
}, [workflow]);
|
||||
|
||||
// Event for making sure app is correct
|
||||
@@ -1288,56 +1298,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
return
|
||||
}
|
||||
|
||||
//console.log("Failed in trigger selection: ", selectedTriggerIndex, "Trigger: ", selectedTrigger)
|
||||
|
||||
var found = null
|
||||
try {
|
||||
for (var key in workflows) {
|
||||
const curworkflow = workflows[key]
|
||||
const curtrigger = curworkflow?.triggers[selectedTriggerIndex]
|
||||
if (curtrigger === undefined || curtrigger === null) {
|
||||
console.log("Failed in trigger selection (1): ", curworkflow)
|
||||
continue
|
||||
}
|
||||
|
||||
if (curtrigger?.parameters === undefined || curtrigger?.parameters === null || curtrigger?.parameters.length === 0) {
|
||||
console.log("Failed in trigger selection (2): ", curworkflow)
|
||||
continue
|
||||
}
|
||||
|
||||
if (curtrigger?.parameters[0] === undefined || curtrigger?.parameters[0] === null || curtrigger?.parameters[0].value === undefined || curtrigger?.parameters[0].value === null) {
|
||||
console.log("Failed in trigger selection (3): ", curworkflow)
|
||||
continue
|
||||
}
|
||||
|
||||
if (curtrigger?.parameters[0].value === selectedTrigger?.parameters[0]?.value) {
|
||||
found = curworkflow
|
||||
setSubworkflow(curworkflow)
|
||||
}
|
||||
}
|
||||
|
||||
if (found !== null) {
|
||||
setSubworkflow(found)
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Failed in trigger selection (4): ", e)
|
||||
//return
|
||||
}
|
||||
|
||||
if (found) {
|
||||
const startNode = found.actions?.find((action) => action.id === workflow?.triggers[selectedTriggerIndex]?.parameters[3]?.value)
|
||||
setSubworkflowStartnode(startNode)
|
||||
}
|
||||
|
||||
/*
|
||||
// Multi-tenant sometimes gives us shit :(
|
||||
if (selectedTrigger === undefined || selectedTrigger === null || selectedTrigger.id === undefined || selectedTrigger.id === null && selectedTriggerIndex >= 0) {
|
||||
if (workflow.triggers !== undefined && workflow.triggers !== null && workflow.triggers.length > selectedTriggerIndex) {
|
||||
setSelectedTrigger(workflow.triggers[selectedTriggerIndex])
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Check if the running state is correct or not according to allTriggers
|
||||
if (allTriggers !== undefined && allTriggers !== null) {
|
||||
// Find the active trigger
|
||||
@@ -1545,6 +1505,8 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0) {
|
||||
headers["Org-Id"] = workflow.org_id
|
||||
}
|
||||
|
||||
setWorkflows([])
|
||||
|
||||
fetch(globalUrl + "/api/v1/workflows?subflow=true", {
|
||||
method: "GET",
|
||||
@@ -1562,7 +1524,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (responseJson !== undefined) {
|
||||
|
||||
// Sets up subflow trigger with the right info
|
||||
if (trigger_index > -1) {
|
||||
if (isNaN(trigger_index) === false && trigger_index > -1) {
|
||||
|
||||
var baseSubflow = {}
|
||||
const trigger = workflow.triggers[trigger_index];
|
||||
@@ -1592,10 +1554,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (Object.getOwnPropertyNames(baseSubflow).length > 0) {
|
||||
const foundAction = baseSubflow.actions.find(action => action?.id === param.value)
|
||||
if (foundAction !== null && foundAction !== undefined) {
|
||||
setSubworkflowStartnode(foundAction);
|
||||
setSubworkflowStartnode(foundAction)
|
||||
}
|
||||
} else {
|
||||
setSubworkflowStartnode(param.value);
|
||||
setSubworkflowStartnode(param.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2540,18 +2502,25 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
*/
|
||||
|
||||
// FIXME: What is this?
|
||||
if (cy !== undefined && cy !== null) {
|
||||
// scale: 0.3,
|
||||
// bg: "#27292d",
|
||||
const cyImageData = cy.png({
|
||||
output: "base64uri",
|
||||
maxWidth: 480,
|
||||
maxHeight: 270,
|
||||
})
|
||||
if (cy?.png !== undefined && cy?.png !== null) {
|
||||
try {
|
||||
const cyImageData = cy.png({
|
||||
output: "base64uri",
|
||||
maxWidth: 480,
|
||||
maxHeight: 270,
|
||||
})
|
||||
|
||||
if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) {
|
||||
useworkflow.image = cyImageData
|
||||
}
|
||||
if (cyImageData !== undefined && cyImageData !== null && cyImageData.length > 0) {
|
||||
useworkflow.image = cyImageData
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Failed to get image data: ", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useworkflow.id === undefined || useworkflow.id === null || useworkflow.id.length === 0) {
|
||||
@@ -3948,7 +3917,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
apps.push(responseJson.actions[index]);
|
||||
}
|
||||
|
||||
console.log("Setting used subflow apps: ", apps)
|
||||
//console.log("Setting used subflow apps: ", apps)
|
||||
setUsedSubflowApps(apps);
|
||||
|
||||
return apps
|
||||
@@ -3961,6 +3930,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
const findWorkflowDiff = (parentWorkflow, childWorkflow) => {
|
||||
// Ensures new memory is used
|
||||
parentWorkflow = JSON.parse(JSON.stringify(parentWorkflow))
|
||||
childWorkflow = JSON.parse(JSON.stringify(childWorkflow))
|
||||
|
||||
var diff = {
|
||||
"different": false,
|
||||
"environment": false,
|
||||
@@ -3978,9 +3951,13 @@ const AngularWorkflow = (defaultprops) => {
|
||||
return diff
|
||||
}
|
||||
|
||||
|
||||
var parentEnvironment = ""
|
||||
var childEnvironment = ""
|
||||
|
||||
if (parentWorkflow.triggers !== undefined && parentWorkflow.triggers !== null && parentWorkflow.triggers.length > 0) {
|
||||
parentWorkflow.actions = parentWorkflow.actions.concat(parentWorkflow.triggers)
|
||||
}
|
||||
|
||||
for (var parentKey in parentWorkflow.actions) {
|
||||
const parentAction = parentWorkflow.actions[parentKey]
|
||||
if (parentAction.environment !== undefined && parentAction.environment !== null && parentAction.environment !== "") {
|
||||
@@ -3992,6 +3969,19 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
var found = false
|
||||
if (childWorkflow.triggers !== undefined && childWorkflow.triggers !== null && childWorkflow.triggers.length > 0) {
|
||||
for (var triggerKey in childWorkflow.triggers) {
|
||||
if (childWorkflow.triggers[triggerKey].replacement_for_trigger !== parentAction.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Rewrapping the ID just in case
|
||||
childWorkflow.triggers[triggerKey].id = childWorkflow.triggers[triggerKey].replacement_for_trigger
|
||||
childWorkflow.actions.push(childWorkflow.triggers[triggerKey])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (var childKey in childWorkflow.actions) {
|
||||
const childAction = childWorkflow.actions[childKey]
|
||||
if (childAction.environment !== undefined && childAction.environment !== null && childAction.environment !== "") {
|
||||
@@ -4038,6 +4028,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
continue
|
||||
}
|
||||
|
||||
var parentworkflow = ""
|
||||
var parentstartnode = ""
|
||||
var childworkflow = ""
|
||||
var childstartnode = ""
|
||||
for (var parentParamIndex in parentAction.parameters) {
|
||||
const parentParam = parentAction.parameters[parentParamIndex]
|
||||
for (var childParamIndex in childAction.parameters) {
|
||||
@@ -4046,20 +4040,38 @@ const AngularWorkflow = (defaultprops) => {
|
||||
continue
|
||||
}
|
||||
|
||||
if (childParam.name === "workflow" || childParam.name === "subflow") {
|
||||
parentworkflow = parentParam.value
|
||||
childworkflow = childParam.value
|
||||
continue
|
||||
}
|
||||
|
||||
if (childParam.name == "startnode") {
|
||||
parentstartnode = parentParam.value
|
||||
childstartnode = childParam.value
|
||||
continue
|
||||
}
|
||||
|
||||
if (childParam.value !== parentParam.value) {
|
||||
actionDiff.parameters.push(childParam.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parentworkflow !== childworkflow && parentstartnode !== childstartnode) {
|
||||
actionDiff.parameters.push("subflow")
|
||||
}
|
||||
}
|
||||
|
||||
if (actionDiff.parameters.length > 0) {
|
||||
actionDiff.params = true
|
||||
}
|
||||
|
||||
/*
|
||||
if (!found) {
|
||||
actionDiff.new = true
|
||||
}
|
||||
*/
|
||||
|
||||
if (actionDiff !== undefined && actionDiff !== null && Object.keys(actionDiff).length > 1) {
|
||||
actionDiff.label = parentAction.label.replaceAll("_", " ")
|
||||
@@ -6352,11 +6364,15 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (workflow.org_id !== undefined && workflow.org_id !== null && workflow.org_id.length > 0 && originalWorkflow.org_id !== undefined && originalWorkflow.org_id !== null && originalWorkflow.org_id.length > 0 && workflow.org_id === originalWorkflow.org_id) {
|
||||
// Allows a parent workflow to control the schedule
|
||||
} else if (data.replacement_for_trigger !== undefined && data.replacement_for_trigger !== null && data.replacement_for_trigger.length > 0) {
|
||||
toast.warning("This schedule is controlled by the parent workflow. If you want additional schedule control, please add a custom schedule to this workflow.", {
|
||||
autoClose: 30000,
|
||||
})
|
||||
event.target.unselect()
|
||||
return
|
||||
|
||||
// No custom control on cloud
|
||||
if (isCloud) {
|
||||
toast.warning("This schedule is controlled by the parent workflow. If you want additional schedule control, please add a custom schedule to this workflow.", {
|
||||
autoClose: 30000,
|
||||
})
|
||||
event.target.unselect()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
} else if (data.app_name === "Webhook" && trigger_index >= 0) {
|
||||
@@ -6932,7 +6948,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
)
|
||||
if (targetnode !== -1) {
|
||||
if (workflow.triggers[targetnode].app_name === "User Input" || workflow.triggers[targetnode].app_name === "Shuffle Workflow" || workflow.triggers[targetnode].app_name === "Shuffle Subflow") {
|
||||
console.log("User Input or Shuffle Workflow")
|
||||
//console.log("User Input or Shuffle Workflow")
|
||||
} else {
|
||||
toast("Can't have triggers as target of branch")
|
||||
event.target.remove()
|
||||
@@ -9264,11 +9280,15 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
insertedNodes = insertedNodes.concat(newedges);
|
||||
setWorkflow(inputworkflow);
|
||||
setWorkflow(inputworkflow)
|
||||
|
||||
// Reset view for cytoscape
|
||||
if (cy !== undefined && cy !== null) {
|
||||
cy.add(insertedNodes)
|
||||
try {
|
||||
cy.add(insertedNodes)
|
||||
} catch (error) {
|
||||
console.log("Error adding nodes to cytoscape (6): ", error)
|
||||
}
|
||||
|
||||
try {
|
||||
cy.fit(null, 250)
|
||||
@@ -9347,12 +9367,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
}
|
||||
|
||||
//if (selectedNode.data("id") === selectedAction.id) {
|
||||
// setSelectedApp({});
|
||||
// setSelectedAction({});
|
||||
//setSelectedTrigger({});
|
||||
//setSelectedTriggerIndex({});
|
||||
//}
|
||||
const parsedSelection = cy.$(":selected");
|
||||
if (selectedNode.data().decorator === true && selectedNode.data("type") !== "COMMENT") {
|
||||
toast("This node can't be deleted.");
|
||||
@@ -9552,7 +9566,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
if (firstrequest) {
|
||||
setFirstrequest(false)
|
||||
getWorkflow(props.match.params.key, {})
|
||||
getChildWorkflows(props.match.params.key)
|
||||
getRevisionHistory(props.match.params.key)
|
||||
loadTriggers()
|
||||
getApps()
|
||||
@@ -9785,9 +9798,14 @@ const AngularWorkflow = (defaultprops) => {
|
||||
toast("Successfully stopped schedule");
|
||||
}
|
||||
|
||||
workflow.triggers[triggerindex].status = "stopped";
|
||||
trigger.status = "stopped";
|
||||
setSelectedTrigger(trigger);
|
||||
if (triggerindex !== undefined && triggerindex !== null && triggerindex >= 0) {
|
||||
workflow.triggers[triggerindex].status = "stopped";
|
||||
}
|
||||
|
||||
//trigger.status = "stopped";
|
||||
//console.log("TRIGGER: ", trigger)
|
||||
//setSelectedTrigger(trigger);
|
||||
|
||||
setWorkflow(workflow);
|
||||
saveWorkflow(workflow)
|
||||
|
||||
@@ -11446,7 +11464,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
var type = "app"
|
||||
const baseImage = <LibraryBooksIcon />
|
||||
return (
|
||||
<div style={{ position: "relative", marginTop: 15, marginLeft: 0, marginRight: 10, position: "absolute", color: "white", zIndex: 1001, backgroundColor: theme.palette.inputColor, minWidth: leftBarSize - 10, maxWidth: leftBarSize - 10, boxShadows: "none", overflowX: "hidden", }}>
|
||||
<div style={{ position: "relative", marginTop: 15, marginLeft: 0, marginRight: 10, position: "absolute", color: "white", zIndex: 1001, backgroundColor: theme.palette.inputColor, minWidth: leftBarSize+30, maxWidth: 340, boxShadows: "none", overflowX: "hidden", }}>
|
||||
<List style={{ backgroundColor: theme.palette.inputColor, }}>
|
||||
{hits.length === 0 ?
|
||||
<ListItem style={outerlistitemStyle}>
|
||||
@@ -11736,7 +11754,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
Click one of the relevant public apps below to Activate it for your organization.
|
||||
Click one of the public apps below to Activate it for your organization.
|
||||
</Typography>
|
||||
<InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
|
||||
console.log("CLICKED")
|
||||
@@ -14637,20 +14655,19 @@ const AngularWorkflow = (defaultprops) => {
|
||||
cy.add(cybranch);
|
||||
}
|
||||
|
||||
console.log("Value to be set: ", e.target.value);
|
||||
try {
|
||||
workflow.triggers[
|
||||
selectedTriggerIndex
|
||||
].parameters[3].value = e.target.value.id;
|
||||
} catch {
|
||||
workflow.triggers[selectedTriggerIndex].parameters[3] =
|
||||
{
|
||||
name: "startnode",
|
||||
value: e.target.value.id,
|
||||
};
|
||||
workflow.triggers[selectedTriggerIndex].parameters[3].value = e.target.value.id
|
||||
} catch(e) {
|
||||
console.log("Error: ", e)
|
||||
workflow.triggers[selectedTriggerIndex].parameters[3] =
|
||||
{
|
||||
name: "startnode",
|
||||
value: e.target.value.id,
|
||||
};
|
||||
}
|
||||
|
||||
setWorkflow(workflow);
|
||||
setWorkflow(workflow)
|
||||
setUpdate(Math.random())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14934,12 +14951,15 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: "10" }}>
|
||||
<b>Select a workflow to execute </b>
|
||||
<b>Select a workflow to run</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{workflow.triggers[selectedTriggerIndex].parameters[0].value
|
||||
.length === 0 ? null : workflow.triggers[selectedTriggerIndex]
|
||||
.parameters[0].value === props.match.params.key ? null : (
|
||||
.parameters[0].value === props.match.params.key ?
|
||||
null
|
||||
: (
|
||||
<div style={{ marginLeft: 5, flex: 1 }}>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
@@ -14960,8 +14980,9 @@ const AngularWorkflow = (defaultprops) => {
|
||||
|
||||
{workflows === undefined ||
|
||||
workflows === null ||
|
||||
workflows.length === 0 ? null : (
|
||||
|
||||
workflows.length === 0 ?
|
||||
<CircularProgress color="secondary" style={{ margin: "auto", marginTop: 10, width: 25, height: 25, }} />
|
||||
: (
|
||||
<Autocomplete
|
||||
id="subflow_search"
|
||||
autoHighlight
|
||||
@@ -15035,7 +15056,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
<Tooltip arrow placement="left" title={
|
||||
<span style={{}}>
|
||||
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
|
||||
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette?.borderRadius, }} />
|
||||
<img src={data.image} alt={data.name} style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, maxWidth: 285, borderRadius: theme.palette?.borderRadius, }} />
|
||||
: null}
|
||||
<Typography>
|
||||
Choose Subflow '{data.name}'
|
||||
@@ -15095,6 +15116,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
<b>Select the Startnode</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Autocomplete
|
||||
id="subflow_node_search"
|
||||
autoHighlight
|
||||
@@ -18217,6 +18239,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
value={workflow.org_id}
|
||||
disabled={savingState !== 0 || suborgWorkflows?.length === 0 || allTriggers === undefined}
|
||||
onChange={(e) => {
|
||||
if (cy !== undefined && cy !== null) {
|
||||
cy.nodes().unselect()
|
||||
}
|
||||
|
||||
if (lastSaved === false && originalWorkflow.id === workflow.id) {
|
||||
setSuborgWorkflows([])
|
||||
|
||||
|
||||
@@ -2657,12 +2657,12 @@ const AppCreator = (defaultprops) => {
|
||||
if (responseJson.reason !== undefined) {
|
||||
setErrorCode(responseJson.reason);
|
||||
|
||||
if (responseJson.extra === undefined && responseJson.extra === null) {
|
||||
toast("Failed to verify: " + responseJson.reason);
|
||||
}
|
||||
toast.error("Failed to build: " + responseJson.reason, {
|
||||
autoClose: 10000
|
||||
})
|
||||
}
|
||||
} else {
|
||||
toast("Successfully uploaded openapi");
|
||||
toast.success("Successfully built openapi app! Added job to rebuild it in your hybrid runtime locations (Orborus).");
|
||||
if (window.location.pathname.includes("/new")) {
|
||||
if (responseJson.id !== undefined && responseJson.id !== null) {
|
||||
window.location = `/apps/edit/${responseJson.id}`;
|
||||
|
||||
@@ -3866,13 +3866,13 @@ const AppExplorer = (props) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTitle style={{ marginBottom: 0 }}>
|
||||
<DialogTitle style={{ marginBottom: 0, padding: 50, paddingBottom: 0, }}>
|
||||
<div style={{ textAlign: "center", color: "rgba(255,255,255,0.9)" }}>
|
||||
Are you sure you want to PUBLISH this app?
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent
|
||||
style={{ color: "rgba(255,255,255,0.65)", textAlign: "center" }}
|
||||
style={{ color: "rgba(255,255,255,0.65)", textAlign: "center", padding: 50, }}
|
||||
>
|
||||
<div>
|
||||
<Typography variant="body1" style={{ marginBottom: 20 }}>
|
||||
@@ -4120,7 +4120,7 @@ const AppExplorer = (props) => {
|
||||
</Button>
|
||||
)}
|
||||
{appType === 1 ? null :
|
||||
creatorProfile.self === true || userdata.support === true ?
|
||||
userdata?.support || userdata?.id === app?.owner || (userdata?.admin === "true" && userdata?.active_org?.id === app?.reference_org) || app?.contributors?.includes(userdata?.id) || creatorProfile?.self === true ?
|
||||
<Tooltip title="Edit App" placement="top">
|
||||
<Button
|
||||
variant="outlined"
|
||||
@@ -4167,9 +4167,9 @@ const AppExplorer = (props) => {
|
||||
Explore API
|
||||
</Button>
|
||||
</a>
|
||||
{(appdata?.owner === userdata?.id) ? (
|
||||
<Select
|
||||
value={sharingConfiguration}
|
||||
disabled={!isCloud}
|
||||
onChange={(event) => {
|
||||
if ((appdata.owner !== userdata.id) && (!userdata.support)) {
|
||||
toast("You're not the owner of this app. You can't update it.");
|
||||
@@ -4222,7 +4222,6 @@ const AppExplorer = (props) => {
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
): null }
|
||||
</div>
|
||||
) : (
|
||||
<a
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect, useContext, useCallback, memo, useMemo, useRef } from "react";
|
||||
import theme from "../theme.jsx";
|
||||
import { isMobile } from "react-device-detect";
|
||||
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
TextField, Button, Typography, MenuItem, Select, Tabs, Tab, Zoom,
|
||||
@@ -590,7 +591,7 @@ const Hits = ({
|
||||
<div style={{
|
||||
flex: 1
|
||||
}}>
|
||||
{hoverEffect === index && isCloud ? (
|
||||
{hoverEffect === index ? (
|
||||
<div>
|
||||
{data.tags && (
|
||||
<Tooltip
|
||||
@@ -764,7 +765,7 @@ const SearchBox = ({ refine, searchQuery, setSearchQuery }) => {
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Search for apps"
|
||||
placeholder="Search from 2500+ public apps"
|
||||
value={localQuery}
|
||||
id="shuffle_search_field"
|
||||
inputRef={inputRef}
|
||||
@@ -2009,7 +2010,7 @@ const Apps2 = (props) => {
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Discover Apps"
|
||||
label="Discover Public Apps"
|
||||
style={{
|
||||
...tabStyle,
|
||||
marginRight: 0,
|
||||
@@ -2028,7 +2029,7 @@ const Apps2 = (props) => {
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Search for apps"
|
||||
placeholder={currTab === 1 ? "Search your apps" : "Search org apps"}
|
||||
disabled={!isLoggedIn}
|
||||
value={searchQuery}
|
||||
id="shuffle_search_field"
|
||||
|
||||
+324
-63
@@ -5,7 +5,7 @@ import theme from '../theme.jsx';
|
||||
import ReactJson from "react-json-view-ssr";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { BrowserView, MobileView } from "react-device-detect";
|
||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||
import { useParams, useNavigate, Link, useLocation } from "react-router-dom";
|
||||
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { Context } from "../context/ContextApi.jsx";
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
Edit as EditIcon,
|
||||
KeyboardArrowRight as KeyboardArrowRightIcon,
|
||||
ExpandMore as ExpandMoreIcon,
|
||||
FileCopy as FileCopyIcon
|
||||
FileCopy as FileCopyIcon,
|
||||
ChevronLeft,
|
||||
ChevronRight
|
||||
} from "@mui/icons-material";
|
||||
import { fontGrid } from "@mui/material/styles/cssUtils.js";
|
||||
|
||||
@@ -51,6 +53,7 @@ const dividerColor = "rgb(225, 228, 232)";
|
||||
const hrefStyle = {
|
||||
color: "rgba(255, 255, 255, 0.8)",
|
||||
textDecoration: "none",
|
||||
marginRight: window.location.pathname.includes("/articles/") ? "0.8em" : undefined,
|
||||
};
|
||||
|
||||
|
||||
@@ -152,11 +155,33 @@ export const OuterLink = (props) => {
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const Img = (props) => {
|
||||
// Find parent container and check width
|
||||
|
||||
const isArticlePage = window.location.pathname.includes("/articles/");
|
||||
var height = "auto"
|
||||
var width = 750
|
||||
var width = isArticlePage ? 1000 : 750
|
||||
|
||||
const docsImageStyle = {
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
width: width,
|
||||
maxWidth: width,
|
||||
margin: "auto",
|
||||
marginTop: 10,
|
||||
marginBottom: 10
|
||||
}
|
||||
|
||||
const articleImageStyle = {
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
minWidth: 350,
|
||||
maxWidth: "100%",
|
||||
textAlign: "center",
|
||||
margin: "auto",
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
}
|
||||
|
||||
if (props.height !== undefined && props.height !== null) {
|
||||
height = props.height
|
||||
}
|
||||
@@ -166,11 +191,13 @@ export const Img = (props) => {
|
||||
}
|
||||
|
||||
return(
|
||||
<img
|
||||
style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette?.borderRadius, width: width, maxWidth: width, margin: "auto", marginTop: 10, marginBottom: 10, }}
|
||||
alt={props.alt}
|
||||
src={props.src}
|
||||
/>
|
||||
<div style={{width: "100%", textAlign: "center", }}>
|
||||
<img
|
||||
style={isArticlePage ? articleImageStyle : docsImageStyle}
|
||||
alt={props.alt}
|
||||
src={props.src}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -251,8 +278,10 @@ export const CodeHandler = (props) => {
|
||||
}
|
||||
|
||||
const Docs = (defaultprops) => {
|
||||
const { globalUrl, selectedDoc, serverside, serverMobile, isLoggedIn, isLoaded } = defaultprops;
|
||||
const { globalUrl, selectedDoc, serverside, serverMobile, isLoggedIn, isLoaded, userdata } = defaultprops;
|
||||
|
||||
let navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
// Quickfix for react router 5 -> 6
|
||||
const params = useParams();
|
||||
//var props = JSON.parse(JSON.stringify(defaultprops))
|
||||
@@ -283,15 +312,31 @@ const Docs = (defaultprops) => {
|
||||
serverside === true ? "" : window.location.href
|
||||
);
|
||||
const [hashRendered, setHashRendered] = React.useState(false)
|
||||
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [activeSubItem, setActiveSubItem] = useState(false);
|
||||
const headingElementsRef = useRef({})
|
||||
var isArticlePage = window.location.pathname.includes("/articles/") || window.location.pathname === "/articles" ? true : false;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
//if (params["key"] === undefined) {
|
||||
// navigate("/docs/about")
|
||||
// return
|
||||
//}
|
||||
}, [])
|
||||
fetchDocList();
|
||||
setSidebarOpen(false);
|
||||
|
||||
if (props.match.params === undefined || props.match.params === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const propkey = props.match.params.key
|
||||
if (propkey === undefined || propkey === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (location.pathname.includes("/docs/")) {
|
||||
if (propkey === "cookie_policy" || propkey === "compliance" || propkey === "privacy_policy" || propkey === "terms_of_service") {
|
||||
navigate(`/legal/${propkey}`)
|
||||
}
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
@@ -460,8 +505,13 @@ const Docs = (defaultprops) => {
|
||||
}
|
||||
|
||||
const SidebarPaperStyle = {
|
||||
backgroundColor: "rgb(26,26,26)",
|
||||
backgroundColor: isArticlePage ? "transparent" : "rgb(26,26,26)",
|
||||
border: isArticlePage ? "none" : undefined,
|
||||
borderRadius: isArticlePage ? "none" : undefined,
|
||||
boxShadow: isArticlePage ? "none" : undefined,
|
||||
backgroundImage: "none",
|
||||
width: isArticlePage ? "100%" : undefined,
|
||||
height: isArticlePage ? "100%" : undefined,
|
||||
overflowX: "hidden",
|
||||
position: "relative",
|
||||
paddingLeft: 15,
|
||||
@@ -527,7 +577,7 @@ const Docs = (defaultprops) => {
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
padding: 15,
|
||||
borderRadius: theme.palette?.borderRadius,
|
||||
marginBottom: 25,
|
||||
marginBottom: isArticlePage ? 25 : 30,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
@@ -620,7 +670,7 @@ const Docs = (defaultprops) => {
|
||||
style={{
|
||||
width: "90%",
|
||||
marginTop: 60,
|
||||
marginBottom: 20,
|
||||
marginBottom: isArticlePage ? 40 : 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
@@ -630,7 +680,21 @@ const Docs = (defaultprops) => {
|
||||
marginBlock: "0.85em", alignItems: "center"
|
||||
}}>
|
||||
{element}
|
||||
<Link to={`#${id}`} style={{
|
||||
<Link to={`#${id}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault(); // Prevent default navigation
|
||||
document.getElementById(id)?.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
|
||||
const url = `${window.location.origin}${window.location.pathname}#${id}`;
|
||||
navigator.clipboard.writeText(url);
|
||||
toast("Link copied to clipboard", {
|
||||
position: "bottom-center",
|
||||
autoClose: 2000,
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
textDecoration: "none", color: "white",
|
||||
paddingLeft: "0.3em", rotate: "-30deg",
|
||||
paddingTop: "0.9em", display: props.level === 1 ? "none" : "block",
|
||||
@@ -638,8 +702,7 @@ const Docs = (defaultprops) => {
|
||||
<LinkIcon />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{extraInfo}
|
||||
{isArticlePage ? (userdata?.support ? extraInfo : "") : extraInfo}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
@@ -647,21 +710,56 @@ const Docs = (defaultprops) => {
|
||||
|
||||
const SideBar = {
|
||||
width: "17%",
|
||||
borderRight: !isArticlePage ? "1px solid rgba(255,255,255,0.3)" : undefined,
|
||||
minWidth: isArticlePage ? "250px" : "17%",
|
||||
maxWidth: isArticlePage ? "250px" : "17%",
|
||||
marginLeft: isArticlePage ? sidebarOpen ? 40 : 50 : undefined,
|
||||
position: "sticky",
|
||||
top: 50,
|
||||
paddingTop: "0.25em",
|
||||
minHeight: "95vh",
|
||||
maxHeight: "95vh",
|
||||
top: isArticlePage ? 120 : 50,
|
||||
paddingTop: isArticlePage ? "0.65em" : "0.25em",
|
||||
paddingRight: isArticlePage ? "1em" : undefined,
|
||||
minHeight: isArticlePage ? "80vh" : "95vh",
|
||||
maxHeight: isArticlePage ? "80vh" : "95vh",
|
||||
overflowX: "hidden",
|
||||
overflowY: "auto",
|
||||
zIndex: 1000,
|
||||
//borderRight: "1px solid rgba(255,255,255,0.3)",
|
||||
backgroundColor: isArticlePage ? "#212121" : undefined,
|
||||
borderRadius: isArticlePage ? theme.palette?.borderRadius : undefined,
|
||||
transition: isArticlePage ? "width 0.3s ease, min-width 0.3s ease" : undefined,
|
||||
};
|
||||
|
||||
const sideBarToggleButton = {
|
||||
position: "absolute",
|
||||
bottom: sidebarOpen ? 20 : "50%",
|
||||
right: sidebarOpen ? 30 : 21,
|
||||
zIndex: 1000,
|
||||
backgroundColor: "#212121",
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
borderRadius: "50%",
|
||||
width: 40,
|
||||
height: 40,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
cursor: "pointer",
|
||||
transition: "transform 0.3s ease",
|
||||
"&:hover": {
|
||||
backgroundColor: "#2c2c2c",
|
||||
}
|
||||
};
|
||||
|
||||
const collapsedSideBar = {
|
||||
...SideBar,
|
||||
width: "40px",
|
||||
minWidth: "40px",
|
||||
maxWidth: "40px",
|
||||
overflow: "hidden",
|
||||
};
|
||||
|
||||
const IndexBar = {
|
||||
alignSelf: "flex-start",
|
||||
position: "sticky",
|
||||
top: 80,
|
||||
top: 120,
|
||||
overflowY: "auto",
|
||||
minHeight: "93vh",
|
||||
maxHeight: "93vh",
|
||||
@@ -670,8 +768,15 @@ const Docs = (defaultprops) => {
|
||||
overflow: "hidden",
|
||||
}
|
||||
|
||||
const fetchDocList = () => {
|
||||
fetch(`${globalUrl}/api/v1/docs`, {
|
||||
const fetchDocList = (resetCache = false) => {
|
||||
var url = `${globalUrl}/api/v1/docs`
|
||||
if (location.pathname.includes("/legal")) {
|
||||
url = `${globalUrl}/api/v1/docs?folder=legal&resetCache=${resetCache}`
|
||||
} else if (location.pathname.includes("/articles")) {
|
||||
url = `${globalUrl}/api/v1/docs?folder=articles&resetCache=${resetCache}`
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -691,8 +796,19 @@ const Docs = (defaultprops) => {
|
||||
.catch((error) => { });
|
||||
};
|
||||
|
||||
const fetchDocs = (docId) => {
|
||||
fetch(`${globalUrl}/api/v1/docs/${docId}`, {
|
||||
const fetchDoc = (docId) => {
|
||||
if (docId === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
var url = `${globalUrl}/api/v1/docs/${docId}`
|
||||
if (location.pathname.includes("/legal")) {
|
||||
url = `${globalUrl}/api/v1/docs/${docId}?folder=legal`
|
||||
} else if (location.pathname.includes("/articles")) {
|
||||
url = `${globalUrl}/api/v1/docs/${docId}?folder=articles`
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -706,18 +822,32 @@ const Docs = (defaultprops) => {
|
||||
}
|
||||
|
||||
if (responseJson.success && responseJson.reason !== undefined) {
|
||||
|
||||
if(isArticlePage && window.location.pathname.includes("/articles/")) {
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found")) {
|
||||
setData("# Error\nThis page doesn't exist.");
|
||||
toast("This page doesn't exist. Redirecting to the latest article...", {
|
||||
position: "bottom-center",
|
||||
autoClose: 2000,
|
||||
});
|
||||
setTimeout(() => {
|
||||
navigate(`/articles/2.0_release`)
|
||||
}, 2000)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Find <img> tags and translate them into ![]() format
|
||||
const imgRegex = /<img.*?src="(.*?)"/g;
|
||||
const tocRegex = /^## Table of contents[\s\S]*?(?=^## )|^## Table of contents[\s\S]*$/gm;
|
||||
const newdata = responseJson.reason.replace(imgRegex, '').replace(tocRegex, "");
|
||||
setData(newdata);
|
||||
if (docId === undefined) {
|
||||
document.title = "Shuffle automation documentation";
|
||||
document.title = isArticlePage ? "Shuffle Articles introduction" : "Shuffle documentation";
|
||||
} else {
|
||||
document.title = "Shuffle " + docId.replace("_", " ") + " documentation";
|
||||
document.title = "Shuffle " + docId.replace("_", " ") + (isArticlePage ? " articles" : " documentation");
|
||||
}
|
||||
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found")) {
|
||||
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.includes("404: Not Found") && !isArticlePage) {
|
||||
navigate("/docs")
|
||||
return
|
||||
}
|
||||
@@ -747,6 +877,11 @@ const Docs = (defaultprops) => {
|
||||
.catch((error) => { });
|
||||
};
|
||||
|
||||
const handleResetCache = () => {
|
||||
fetchDocList(true);
|
||||
toast("Cache has been reset");
|
||||
}
|
||||
|
||||
if (firstrequest) {
|
||||
setFirstrequest(false);
|
||||
if (!serverside) {
|
||||
@@ -769,11 +904,29 @@ const Docs = (defaultprops) => {
|
||||
// return null
|
||||
//}
|
||||
//
|
||||
if (props.match.params.key === undefined) {
|
||||
|
||||
if (props.match.params.key === undefined && isArticlePage) {
|
||||
fetch(`${globalUrl}/api/v1/articles`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success && responseJson.list && responseJson.list.length > 0) {
|
||||
// Navigate to the latest article
|
||||
if (responseJson?.list[0]?.name) {
|
||||
navigate(`/articles/${responseJson?.list[0].name}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error fetching articles:", error);
|
||||
});
|
||||
} else {
|
||||
console.log("DOCID: ", props.match.params.key)
|
||||
fetchDocs(props.match.params.key)
|
||||
fetchDoc(props.match.params.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -782,7 +935,7 @@ const Docs = (defaultprops) => {
|
||||
// Handles search-based changes that origin from outside this file
|
||||
if (serverside !== true && window.location.href !== baseUrl) {
|
||||
setBaseUrl(window.location.href);
|
||||
fetchDocs(props.match.params.key);
|
||||
fetchDoc(props.match.params.key);
|
||||
}
|
||||
|
||||
const markdownStyle = {
|
||||
@@ -793,7 +946,7 @@ const Docs = (defaultprops) => {
|
||||
maxWidth: "100%",
|
||||
minWidth: "100%",
|
||||
overflow: "hidden",
|
||||
fontSize: isMobile ? "1.3rem" : "1rem",
|
||||
fontSize: isMobile ? "1.3rem" : "1.1rem",
|
||||
};
|
||||
|
||||
const alertNote = {
|
||||
@@ -940,20 +1093,51 @@ const Docs = (defaultprops) => {
|
||||
}
|
||||
|
||||
|
||||
const activeHrefStyleToc2 = {
|
||||
...hrefStyleToc2,
|
||||
color: "#f86a3e",
|
||||
};
|
||||
|
||||
const activeListItemStyle = {
|
||||
backgroundColor: "rgba(248, 106, 62, 0.08)", // Slight orange tint
|
||||
marginRight: window.location.pathname.includes("/articles/") ? "0.8em" : undefined,
|
||||
borderLeft: "3px solid #f86a3e",
|
||||
paddingLeft: "13px", // Compensate for the border
|
||||
};
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setSidebarOpen(!sidebarOpen);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// PostDataBrowser Section
|
||||
const postDataBrowser =
|
||||
list === undefined || list === null ? null : (
|
||||
<div style={Body}>
|
||||
<div style={SideBar}>
|
||||
<div style={!isArticlePage ? SideBar : (sidebarOpen ? SideBar : collapsedSideBar)}>
|
||||
<Paper style={SidebarPaperStyle}>
|
||||
<List style={{ listStyle: "none", paddingLeft: "0" }}>
|
||||
<List style={{
|
||||
listStyle: "none",
|
||||
paddingLeft: "0",
|
||||
display: isArticlePage ? sidebarOpen ? "block" : "none" : undefined,
|
||||
opacity: isArticlePage ? sidebarOpen ? 1 : 0 : undefined,
|
||||
transition: isArticlePage ? "opacity 0.3s ease" : undefined,
|
||||
height: isArticlePage ? "90%" : undefined,
|
||||
}}>
|
||||
{list.map((data, index) => {
|
||||
const item = data.name;
|
||||
if (item === undefined) {
|
||||
return null;
|
||||
}
|
||||
const path = "/docs/" + item;
|
||||
|
||||
var path = "/docs/" + item;
|
||||
if (location.pathname.includes("/legal")) {
|
||||
path = "/legal/" + item
|
||||
} else if (location.pathname.includes("/articles")) {
|
||||
path = "/articles/" + item
|
||||
}
|
||||
|
||||
const newname =
|
||||
item.charAt(0).toUpperCase() +
|
||||
item.substring(1).split("_").join(" ").split("-").join(" ");
|
||||
@@ -965,23 +1149,62 @@ const Docs = (defaultprops) => {
|
||||
<ListItemButton
|
||||
component={Link}
|
||||
key={index}
|
||||
style={hrefStyle}
|
||||
style={{
|
||||
...hrefStyle,
|
||||
...(itemMatching ? activeListItemStyle : {}),
|
||||
}}
|
||||
to={path}
|
||||
>
|
||||
<ListItemText
|
||||
style={{ color: itemMatching ? "#f86a3e" : "inherit" }}
|
||||
variant="body1"
|
||||
>
|
||||
{newname}
|
||||
</ListItemText>
|
||||
style={{
|
||||
color: itemMatching ? "#f86a3e" : "inherit",
|
||||
flex: 1,
|
||||
}}
|
||||
primary={
|
||||
<Typography
|
||||
variant="body1"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{newname}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
<KeyboardArrowRightIcon
|
||||
style={{
|
||||
color: itemMatching ? "#f86a3e" : "inherit",
|
||||
fontSize: "1.1rem",
|
||||
marginRight: "-6px",
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
{
|
||||
isArticlePage && (
|
||||
<IconButton
|
||||
onClick={toggleSidebar}
|
||||
style={sideBarToggleButton}
|
||||
>
|
||||
{sidebarOpen ? <ChevronLeft /> : <ChevronRight />}
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
</Paper>
|
||||
</div>
|
||||
<div style={{ width: "60%", margin: "30px 0px 30px 30px", overflow: "hidden", paddingRight: 50, paddingLeft: 40 }}>
|
||||
<div style={{
|
||||
width: isArticlePage ? sidebarOpen ? "60%" : "70%" : "60%",
|
||||
margin: isArticlePage ? sidebarOpen ? "0 0px 30px 30px" : "0 0px 30px 60px" : "30px 0px 30px 30px",
|
||||
overflow: "hidden",
|
||||
paddingRight: 50,
|
||||
paddingLeft: 40,
|
||||
transition: "width 0.3s ease, margin 0.3s ease",
|
||||
}}>
|
||||
{props.match.params.key === undefined ?
|
||||
mainpageInfo
|
||||
:
|
||||
@@ -1003,12 +1226,26 @@ const Docs = (defaultprops) => {
|
||||
}
|
||||
</div>
|
||||
<div style={IndexBar}>
|
||||
{userdata?.support && isArticlePage && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={handleResetCache}
|
||||
style={{marginBottom: "15px"}}
|
||||
>
|
||||
Reset Cache
|
||||
</Button>
|
||||
)}
|
||||
{tocLines.length > 0 ?
|
||||
(
|
||||
<h4 style={{ fontWeight: 600, margin: 0, fontSize: "16px", marginBottom: "8px" }}>Table of Content</h4>
|
||||
<h4 style={{ fontWeight: 600, margin: 0, fontSize: "16px", marginBottom: "8px" }}>Table Of Content</h4>
|
||||
|
||||
) : null}
|
||||
<nav>
|
||||
<div
|
||||
style={{
|
||||
overflowY: "auto",
|
||||
maxHeight: "90vh",
|
||||
}}>
|
||||
{tocLines.map((data, index) => {
|
||||
return (
|
||||
<div className="toc"
|
||||
@@ -1019,12 +1256,15 @@ const Docs = (defaultprops) => {
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
padding: "4px 8px 4px 8px",
|
||||
padding: isArticlePage ? "4px 0" : "4px 8px 4px 8px",
|
||||
paddingLeft: isArticlePage ? "0" : "8px",
|
||||
paddingRight: isArticlePage ? "0" : "8px",
|
||||
lineHeight: "20px",
|
||||
color: activeId === data.id ? "#f86a3e" : "rgba(255,255,255,0.6)",
|
||||
color: activeId === data.id ? "#f86a3e" : "inherit",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
handleCollapse(index)
|
||||
setActiveId(data.id)
|
||||
}}
|
||||
>
|
||||
{data.title}
|
||||
@@ -1041,8 +1281,12 @@ const Docs = (defaultprops) => {
|
||||
return (
|
||||
<ListItemButton
|
||||
key={i}
|
||||
style={hrefStyleToc2}
|
||||
style={activeSubItem === d.id ? activeHrefStyleToc2 : hrefStyleToc2}
|
||||
href={`#${d.id}`}
|
||||
onClick={(e) => {
|
||||
// e.preventDefault()
|
||||
setActiveSubItem(d.id)
|
||||
}}
|
||||
>
|
||||
{d.title}
|
||||
</ListItemButton>
|
||||
@@ -1055,7 +1299,7 @@ const Docs = (defaultprops) => {
|
||||
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</div >
|
||||
|
||||
</div >
|
||||
@@ -1083,7 +1327,7 @@ const Docs = (defaultprops) => {
|
||||
color="primary"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div style={{ color: "white" }}>More docs</div>
|
||||
<div style={{ color: "white" }}>More {isArticlePage ? "articles" : "docs"}</div>
|
||||
</Button>
|
||||
<Menu
|
||||
id="simple-menu"
|
||||
@@ -1099,7 +1343,7 @@ const Docs = (defaultprops) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const path = "/docs/" + item;
|
||||
const path = isArticlePage ? "/articles/" + item : "/docs/" + item;
|
||||
const newname =
|
||||
item.charAt(0).toUpperCase() +
|
||||
item.substring(1).split("_").join(" ").split("-").join(" ");
|
||||
@@ -1108,7 +1352,8 @@ const Docs = (defaultprops) => {
|
||||
key={index}
|
||||
style={{ color: "white" }}
|
||||
onClick={() => {
|
||||
window.location.pathname = path;
|
||||
navigate(path)
|
||||
handleClose()
|
||||
}}
|
||||
>
|
||||
{newname}
|
||||
@@ -1140,8 +1385,22 @@ const Docs = (defaultprops) => {
|
||||
<MenuItem
|
||||
key={index}
|
||||
style={{ color: "white" }}
|
||||
component={Link}
|
||||
to={`#${data.id}`}
|
||||
onClick={() => {
|
||||
console.log(`Scrolling to: ${data.id}`);
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(data?.id);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
} else {
|
||||
console.error(`Element with id ${data.id} not found.`);
|
||||
}
|
||||
}, 100); // Delay to ensure the content is loaded
|
||||
handleCloseToc();
|
||||
}}
|
||||
>
|
||||
<a href={`#${data.id}`} style={hrefStyle}>{data.title}</a>
|
||||
{data.title}
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
@@ -1178,7 +1437,7 @@ const Docs = (defaultprops) => {
|
||||
color="primary"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div style={{ color: "white" }}>More docs</div>
|
||||
<div style={{ color: "white" }}>More {isArticlePage ? "articles" : "docs"}</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -1198,7 +1457,7 @@ export default Docs;
|
||||
|
||||
const DocsContent = memo(({postDataBrowser, postDataMobile}) => {
|
||||
return(
|
||||
<div>
|
||||
<div style={{fontFamily: theme?.palette?.fontFamily}}>
|
||||
<BrowserView>{postDataBrowser}</BrowserView>
|
||||
<MobileView>{postDataMobile}</MobileView>
|
||||
</div>
|
||||
@@ -1210,12 +1469,14 @@ const DocsWrapper = memo(({isLoggedIn, isLoaded, children })=>{
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
marginTop: window.location.pathname.includes("/articles/") ? 50 : undefined,
|
||||
paddingBottom: 50,
|
||||
minHeight: 1000, zIndex: 1,
|
||||
maxWidth: Math.min(!(isLoggedIn && isLoaded) ? 1920 : leftSideBarOpenByClick ? windowWidth - 300 : windowWidth - 200, 1920),
|
||||
minWidth: isMobile ? null : (isLoggedIn && isLoaded) ? leftSideBarOpenByClick ? 800 : 900 : null, margin: "auto",
|
||||
position: (isLoggedIn && isLoaded) && leftSideBarOpenByClick ? "relative" : "static",
|
||||
left: (isLoggedIn && isLoaded) && leftSideBarOpenByClick ? 120 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0,
|
||||
marginLeft: windowWidth < 1920 ? leftSideBarOpenByClick && (isLoggedIn && isLoaded) ? 90 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0 : "auto", width: "100%",
|
||||
marginLeft: windowWidth < 1920 ? leftSideBarOpenByClick && (isLoggedIn && isLoaded) ? window.location.pathname.includes("/articles/") ? 0 : 90 : (isLoggedIn && isLoaded) && !leftSideBarOpenByClick ? 80 : 0 : "auto", width: "100%",
|
||||
transition: "left 0.3s ease-in-out, min-width 0.3s ease-in-out, max-width 0.3s ease-in-out, position 0.3s ease-in-out, margin 0.3s ease-in-out, margin-left 0.3s ease"
|
||||
}}>
|
||||
{children}
|
||||
|
||||
+915
-502
File diff suppressed because it is too large
Load Diff
Executable
+548
@@ -0,0 +1,548 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useInterval } from "react-powerhooks";
|
||||
import theme from '../theme.jsx';
|
||||
|
||||
import {
|
||||
CircularProgress,
|
||||
TextField,
|
||||
Button,
|
||||
Paper,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const hrefStyle = {
|
||||
color: "white",
|
||||
textDecoration: "none",
|
||||
};
|
||||
|
||||
const bodyDivStyle = {
|
||||
margin: "auto",
|
||||
marginTop: 150,
|
||||
width: "500px",
|
||||
};
|
||||
|
||||
const useStyles = makeStyles({
|
||||
notchedOutline: {
|
||||
borderColor: "#f85a3e !important",
|
||||
},
|
||||
});
|
||||
|
||||
const LoginDialog = (props) => {
|
||||
const {
|
||||
globalUrl,
|
||||
isLoaded,
|
||||
isLoggedIn,
|
||||
setIsLoggedIn,
|
||||
setCookie,
|
||||
register,
|
||||
checkLogin,
|
||||
} = props;
|
||||
|
||||
let navigate = useNavigate();
|
||||
const classes = useStyles();
|
||||
|
||||
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("");
|
||||
|
||||
const [MFAField, setMFAField] = useState(false);
|
||||
const [MFAValue, setMFAValue] = useState("");
|
||||
|
||||
|
||||
// Used to swap from login to register. True = login, false = register
|
||||
|
||||
useEffect(() => {
|
||||
checkAdmin()
|
||||
}, [loginViewLoading])
|
||||
|
||||
// Error messages etc
|
||||
const [loginInfo, setLoginInfo] = useState("");
|
||||
|
||||
const handleValidateForm = () => {
|
||||
return username.length > 1 && password.length > 1;
|
||||
};
|
||||
|
||||
if (isLoggedIn === true) {
|
||||
//window.location.pathname = "/workflows";
|
||||
navigate("/workflows")
|
||||
}
|
||||
|
||||
const checkAdmin = () => {
|
||||
const url = globalUrl + "/api/v1/checkusers";
|
||||
fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) =>
|
||||
response.json().then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"]);
|
||||
} else {
|
||||
|
||||
if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) {
|
||||
setSSOUrl(responseJson.sso_url);
|
||||
}
|
||||
|
||||
navigate("/login")
|
||||
|
||||
if (loginViewLoading) {
|
||||
setLoginViewLoading(false);
|
||||
checkLogin();
|
||||
stop();
|
||||
|
||||
if (
|
||||
responseJson.reason !== undefined &&
|
||||
responseJson.reason !== null
|
||||
) {
|
||||
setLoginInfo(responseJson.reason);
|
||||
}
|
||||
}
|
||||
|
||||
if (responseJson.reason === "stay") {
|
||||
navigate("/adminsetup")
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
if (!loginViewLoading) {
|
||||
setLoginViewLoading(true);
|
||||
start();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const { start, stop } = useInterval({
|
||||
duration: 3000,
|
||||
startImmediate: false,
|
||||
callback: () => {
|
||||
checkAdmin();
|
||||
},
|
||||
})
|
||||
|
||||
if (firstRequest) {
|
||||
setFirstRequest(false);
|
||||
checkAdmin();
|
||||
}
|
||||
|
||||
const onSubmit = (e) => {
|
||||
setLoginLoading(true);
|
||||
e.preventDefault();
|
||||
setLoginInfo("");
|
||||
// FIXME - add some check here ROFL
|
||||
|
||||
// Just use this one?
|
||||
var data = { username: username, password: password };
|
||||
if (MFAValue !== undefined && MFAValue !== null && MFAValue.length > 0) {
|
||||
data["mfa_code"] = MFAValue;
|
||||
}
|
||||
|
||||
var baseurl = globalUrl;
|
||||
if (register) {
|
||||
var url = baseurl + "/api/v1/login";
|
||||
fetch(url, {
|
||||
mode: "cors",
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
credentials: "include",
|
||||
crossDomain: true,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
})
|
||||
.then((response) =>
|
||||
response.json().then((responseJson) => {
|
||||
setLoginLoading(false);
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"]);
|
||||
} else {
|
||||
if (responseJson["reason"] === "MFA_REDIRECT") {
|
||||
setLoginInfo(
|
||||
"MFA required. Please enter the 6-digit code from your authenticator"
|
||||
);
|
||||
setMFAField(true);
|
||||
return;
|
||||
} else if (responseJson["reason"] === "MFA_SETUP") {
|
||||
window.location.href = `/login/${responseJson.url}/mfa-setup`;
|
||||
return;
|
||||
}
|
||||
|
||||
setLoginInfo("Successful login, rerouting");
|
||||
for (var key in responseJson["cookies"]) {
|
||||
setCookie(
|
||||
responseJson["cookies"][key].key,
|
||||
responseJson["cookies"][key].value,
|
||||
{ path: "/" }
|
||||
);
|
||||
}
|
||||
|
||||
if (responseJson.tutorials === undefined || responseJson.tutorials === null || !responseJson.tutorials.includes("welcome")) {
|
||||
console.log("RUN Welcome!!")
|
||||
setTimeout(() => {
|
||||
navigate("/welcome?tab=2")
|
||||
},200)
|
||||
// window.location.pathname = ""
|
||||
return
|
||||
}
|
||||
|
||||
const tmpView = new URLSearchParams(window.location.search).get("view")
|
||||
if (tmpView !== undefined && tmpView !== null) {
|
||||
//const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}`
|
||||
const newUrl = `/${tmpView}`
|
||||
window.location.pathname = newUrl
|
||||
} else {
|
||||
window.location.pathname = "/workflows"
|
||||
}
|
||||
|
||||
setIsLoggedIn(true);
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
setLoginLoading(false);
|
||||
setLoginInfo("Error logging in: " + error);
|
||||
});
|
||||
} else {
|
||||
url = baseurl + "/api/v1/users/register";
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) =>
|
||||
response.json().then((responseJson) => {
|
||||
if (responseJson["success"] === false) {
|
||||
setLoginInfo(responseJson["reason"]);
|
||||
} else {
|
||||
setLoginInfo("Successful register!");
|
||||
}
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
setLoginInfo("Error in from backend: ", error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeUser = (e) => {
|
||||
setUsername(e.target.value);
|
||||
};
|
||||
|
||||
const onChangePass = (e) => {
|
||||
setPassword(e.target.value);
|
||||
};
|
||||
|
||||
//const onClickRegister = () => {
|
||||
// if (props.location.pathname === "/login") {
|
||||
// window.location.pathname = "/register"
|
||||
// } else {
|
||||
// window.location.pathname = "/login"
|
||||
// }
|
||||
|
||||
// setLoginCheck(!register)
|
||||
//}
|
||||
|
||||
//var loginChange = register ? (<div><p onClick={setLoginCheck(false)}>Want to register? Click here.</p></div>) : (<div><p onClick={setLoginCheck(true)}>Go back to login? Click here.</p></div>);
|
||||
var formtitle = register ? <div>Login</div> : <div>Register</div>;
|
||||
const imgsize = 100;
|
||||
const basedata = (
|
||||
<div style={bodyDivStyle}>
|
||||
<Paper
|
||||
style={{
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
position: "relative",
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
}}
|
||||
>
|
||||
<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 two minutes.
|
||||
</Typography>
|
||||
|
||||
{loginInfo === undefined ||
|
||||
loginInfo === null ||
|
||||
loginInfo.length === 0 ? null : (
|
||||
<div style={{ marginTop: "10px" }}>Database Response: {loginInfo}</div>
|
||||
)}
|
||||
<CircularProgress color="secondary" style={{ color: "white" }} />
|
||||
|
||||
<Paper
|
||||
style={{
|
||||
paddingLeft: "30px",
|
||||
paddingRight: "30px",
|
||||
paddingBottom: "30px",
|
||||
paddingTop: "30px",
|
||||
position: "relative",
|
||||
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, and that you have a minimum of <b>2Gb of RAM available</b>:{" "}
|
||||
<br />
|
||||
<br />
|
||||
sudo chown -R 1000:1000 shuffle-database
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
style={{ marginBottom: 20, color: "white" }}
|
||||
>
|
||||
<b>2.</b> Disable memory swap on the host:
|
||||
<br />
|
||||
<br />
|
||||
sudo swapoff -a
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
style={{ marginBottom: 20, color: "white" }}
|
||||
>
|
||||
<b>3</b>. Restart the database:
|
||||
<br />
|
||||
<br />
|
||||
sudo docker restart shuffle-opensearch
|
||||
</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
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginTop: 5,
|
||||
}}
|
||||
autoFocus
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
autoComplete="username"
|
||||
placeholder="username@example.com"
|
||||
id="emailfield"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={onChangeUser}
|
||||
/>
|
||||
</div>
|
||||
Password
|
||||
<div>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginTop: 5,
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
id="outlined-password-input"
|
||||
fullWidth={true}
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder="**********"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={onChangePass}
|
||||
/>
|
||||
</div>
|
||||
{MFAField === true ? (
|
||||
<div style={{ marginTop: 15 }}>
|
||||
2-factor code
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginTop: 5,
|
||||
}}
|
||||
InputProps={{
|
||||
classes: {
|
||||
notchedOutline: classes.notchedOutline,
|
||||
},
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
id="outlined-password-input"
|
||||
fullWidth={true}
|
||||
type="text"
|
||||
placeholder="6-digit code"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setMFAValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<div style={{ display: "flex", marginTop: "15px" }}>
|
||||
<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
|
||||
id="sso_button"
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
type="button"
|
||||
style={{ flex: "1", marginTop: 5 }}
|
||||
onClick={() => {
|
||||
//console.log("CLICK SSO");
|
||||
window.location.href = ssoUrl
|
||||
//navigate(ssoUrl)
|
||||
}}
|
||||
>
|
||||
Use SSO
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</form>
|
||||
)}
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
|
||||
const loadedCheck = isLoaded ? <div>{basedata}</div> : <div></div>;
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0) {
|
||||
//id="sso_button"
|
||||
const ssoBtn = document.getElementById("sso_button");
|
||||
if (ssoBtn !== undefined && ssoBtn !== null) {
|
||||
//console.log("SSO BTN: ", ssoBtn)
|
||||
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
|
||||
var tmpView = new URLSearchParams(cursearch).get("autologin");
|
||||
if (tmpView !== undefined && tmpView !== null) {
|
||||
if (tmpView === "true") {
|
||||
console.log("Tmp: ", tmpView)
|
||||
ssoBtn.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}, [ssoUrl])
|
||||
|
||||
return <div style={{paddingBottom: 150, }}>{loadedCheck}</div>;
|
||||
};
|
||||
|
||||
export default LoginDialog;
|
||||
@@ -1946,7 +1946,10 @@ const Workflows = (props) => {
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for setting workflows :O!");
|
||||
toast("Failed deleting workflow. Do you have access?");
|
||||
|
||||
if (bulk !== true) {
|
||||
toast(`Failed deleting workflow ${id}. Do you have access?`);
|
||||
}
|
||||
} else {
|
||||
if (bulk !== true) {
|
||||
toast(`Deleted workflow ${id}. Child Workflows in Suborgs were also removed.`)
|
||||
|
||||
@@ -1758,9 +1758,17 @@ const Workflows2 = (props) => {
|
||||
let exportFileDefaultName = data.name + ".json";
|
||||
|
||||
data["owner"] = "";
|
||||
data["updated_by"] = "";
|
||||
data["org"] = [];
|
||||
data["org_id"] = "";
|
||||
data["execution_org"] = {};
|
||||
data["created"] = 0
|
||||
data["due_date"] = 0
|
||||
data["edited"] = 0
|
||||
|
||||
data["validation"] = {};
|
||||
data["suborg_distribution"] = []
|
||||
data["parentorg_workflow"] = ""
|
||||
|
||||
// These are backwards.. True = saved before. Very confuse.
|
||||
data["previously_saved"] = false;
|
||||
@@ -2035,7 +2043,10 @@ const Workflows2 = (props) => {
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for setting workflows :O!");
|
||||
toast("Failed deleting workflow. Do you have access?");
|
||||
|
||||
if (bulk !== true) {
|
||||
toast("Failed deleting workflow. Do you have access?");
|
||||
}
|
||||
} else {
|
||||
if (bulk !== true) {
|
||||
toast(`Deleted workflow ${id}. Child Workflows in Suborgs were also removed.`)
|
||||
@@ -2813,7 +2824,7 @@ const Workflows2 = (props) => {
|
||||
|
||||
{(data.sharing !== undefined && data.sharing !== null && data.sharing === "form") || (data?.form_control?.input_markdown !== undefined && data?.form_control?.input_markdown !== null && data?.form_control?.input_markdown !== "") && type !== "public" ?
|
||||
<Tooltip title="Edit Form" placement="top">
|
||||
<div style={{ position: "absolute", top: 45, right: 8, }}>
|
||||
<div style={{ position: "absolute", top: 50, right: 8, }}>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
aria-controls="long-menu"
|
||||
@@ -2830,8 +2841,8 @@ const Workflows2 = (props) => {
|
||||
: null}
|
||||
|
||||
{(data?.validation?.validation_ran === true && data?.validation?.valid === false && data?.validation?.errors?.length > 0 ) ?
|
||||
<Tooltip title={`Explore more than ${data?.validation?.errors?.length} errors. When the last execution finishes without errors AND notifications stop occuring, this icon disappears.`} placement="top">
|
||||
<div style={{ position: "absolute", top: 45, right: 8, }}>
|
||||
<Tooltip title={`Explore more than ${data?.validation?.errors?.length} notifications. When the last execution finishes without errors AND notifications stop occuring, this icon disappears.`} placement="top">
|
||||
<div style={{ position: "absolute", top: 85, right: 8, }}>
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
aria-controls="long-menu"
|
||||
@@ -2850,8 +2861,8 @@ const Workflows2 = (props) => {
|
||||
</IconButton>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
: null}
|
||||
|
||||
</Grid>
|
||||
</Paper>
|
||||
</div>
|
||||
@@ -3227,6 +3238,7 @@ const Workflows2 = (props) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title="Amount of triggers"
|
||||
|
||||
Reference in New Issue
Block a user