Merge branch 'nightly' into automation

This commit is contained in:
Frikky
2025-03-18 11:09:55 +01:00
committed by GitHub
56 changed files with 5121 additions and 903 deletions
+18 -10
View File
@@ -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")
});
};
+129 -103
View File
@@ -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([])
+4 -4
View File
@@ -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}`;
+4 -5
View File
@@ -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
+5 -4
View File
@@ -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
View File
@@ -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, '![]($1)').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}
File diff suppressed because it is too large Load Diff
+548
View File
@@ -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;
+4 -1
View File
@@ -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.`)
+17 -5
View File
@@ -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"