Updated minor multi tenant issues

This commit is contained in:
Frikky
2025-02-26 00:34:54 +01:00
parent 1ffae87420
commit a406c8eaa7
5 changed files with 394 additions and 92 deletions
+289 -61
View File
@@ -24,11 +24,14 @@ import {
Checkbox, Checkbox,
MenuItem, MenuItem,
DialogContent, DialogContent,
FormControl,
Select,
} from "@mui/material"; } from "@mui/material";
import { import {
Link as LinkIcon, Link as LinkIcon,
AutoFixHigh as AutoFixHighIcon, AutoFixHigh as AutoFixHighIcon,
AutoFixNormal as AutoFixNormalIcon,
Edit as EditIcon, Edit as EditIcon,
FileCopy as FileCopyIcon, FileCopy as FileCopyIcon,
SelectAll as SelectAllIcon, SelectAll as SelectAllIcon,
@@ -50,8 +53,8 @@ import {
Business as BusinessIcon, Business as BusinessIcon,
Visibility as VisibilityIcon, Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon, VisibilityOff as VisibilityOffIcon,
CheckBox, Clear as ClearIcon,
Key, Add as AddIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { validateJson, } from "../views/Workflows.jsx"; import { validateJson, } from "../views/Workflows.jsx";
import { Context } from "../context/ContextApi.jsx"; import { Context } from "../context/ContextApi.jsx";
@@ -91,15 +94,40 @@ const CacheView = memo((props) => {
const [showDistributionPopup, setShowDistributionPopup] = useState(false); const [showDistributionPopup, setShowDistributionPopup] = useState(false);
const [selectedSubOrg, setSelectedSubOrg] = useState([]); const [selectedSubOrg, setSelectedSubOrg] = useState([]);
const [selectedCacheKey, setSelectedCacheKey] = useState(""); const [selectedCacheKey, setSelectedCacheKey] = useState("");
// Direct category migration from ../components/Files.jsx
const [selectAllChecked, setSelectAllChecked] = React.useState(false)
const [renderTextBox, setRenderTextBox] = React.useState(false);
const [fileCategories, setFileCategories] = React.useState(["default"]);
const [selectedCategory, setSelectedCategory] = React.useState("default");
const [selectedFileId, setSelectedFileId] = React.useState("");
const [updateToThisCategory, setUpdateToThisCategory] = useState("")
const [showFileCategoryPopup, setShowFileCategoryPopup] = React.useState(false);
const [selectedFiles, setSelectedFiles] = useState([]);
useEffect(() => { useEffect(() => {
if(orgId?.length >0){ if (orgId?.length > 0) {
listOrgCache(orgId); listOrgCache(orgId, selectedCategory)
} }
}, [orgId]); }, [orgId])
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
fileCategories.push(event.target.value);
setSelectedCategory(event.target.value);
setRenderTextBox(false);
}
if (event.key === 'Escape'){ // not working for some reasons
console.log('escape pressed')
setRenderTextBox(false);
}
}
const listOrgCache = (orgId) => { const listOrgCache = (orgId, category) => {
fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, { const url = `${globalUrl}/api/v1/orgs/${orgId}/list_cache${category !== undefined ? `?category=${category.replaceAll(" ", "_")}` : ""}`
fetch(url, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -119,6 +147,19 @@ const CacheView = memo((props) => {
if (responseJson.success === true) { if (responseJson.success === true) {
setListCache(responseJson.keys); setListCache(responseJson.keys);
setCachedLoaded(true); setCachedLoaded(true);
if (fileCategories.length === 1 && fileCategories[0] === "default") {
var newcategories = ["default"]
for (var key in responseJson.keys) {
if (responseJson.keys[key].category !== undefined && responseJson.keys[key].category !== null && responseJson.keys[key].category !== "" && !fileCategories.includes(responseJson.keys[key].category)) {
newcategories.push(responseJson.keys[key].category);
}
}
console.log("CATEGORIES: ", newcategories)
setFileCategories(newcategories)
}
} }
if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") { if (responseJson.cursor !== undefined && responseJson.cursor !== null && responseJson.cursor !== "") {
@@ -132,15 +173,12 @@ const CacheView = memo((props) => {
const deleteCache = (orgId, key) => { const deleteCache = (orgId, key) => {
//toast("Attempting to delete Cache");
// method: "DELETE",
const method = "POST" const method = "POST"
//const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/${key}`
const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache` const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache`
const parsed = { const parsed = {
"org_id": orgId, "org_id": orgId,
"key": key, "key": key,
"category": selectedCategory === "" || selectedCategory === "default" ? "" : selectedCategory,
} }
fetch(url, { fetch(url, {
@@ -155,7 +193,7 @@ const CacheView = memo((props) => {
if (response.status === 200) { if (response.status === 200) {
toast("Successfully deleted Cache"); toast("Successfully deleted Cache");
setTimeout(() => { setTimeout(() => {
listOrgCache(orgId); listOrgCache(orgId, selectedCategory)
}, 1000); }, 1000);
} else { } else {
toast("Failed deleting Cache. Does it still exist?"); toast("Failed deleting Cache. Does it still exist?");
@@ -167,7 +205,12 @@ const CacheView = memo((props) => {
}; };
const editOrgCache = (orgId) => { const editOrgCache = (orgId) => {
const cache = { key: dataValue.key , value: value }; const cache = {
key: dataValue.key,
value: value,
category: selectedCategory,
}
setCacheInput([cache]); setCacheInput([cache]);
fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, { fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, {
@@ -191,7 +234,7 @@ const CacheView = memo((props) => {
.then((responseJson) => { .then((responseJson) => {
setAddCache(responseJson); setAddCache(responseJson);
toast("Cache Edited Successfully!"); toast("Cache Edited Successfully!");
listOrgCache(orgId); listOrgCache(orgId, selectedCategory);
setModalOpen(false); setModalOpen(false);
}) })
.catch((error) => { .catch((error) => {
@@ -200,9 +243,13 @@ const CacheView = memo((props) => {
}; };
const addOrgCache = (orgId) => { const addOrgCache = (orgId) => {
const cache = { key: key, value: value }; const cache = {
key: key,
value: value,
category: selectedCategory,
}
setCacheInput([cache]); setCacheInput([cache]);
console.log("cache input:", cacheInput)
fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, { fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, {
@@ -224,8 +271,8 @@ const CacheView = memo((props) => {
}) })
.then((responseJson) => { .then((responseJson) => {
setAddCache(responseJson); setAddCache(responseJson);
toast("New key Added Successfully!"); toast("New key added Successfully!");
listOrgCache(orgId); listOrgCache(orgId, selectedCategory);
setModalOpen(false); setModalOpen(false);
}) })
.catch((error) => { .catch((error) => {
@@ -243,7 +290,9 @@ const CacheView = memo((props) => {
setValue(JSON.stringify(parsedjson, null, 2)) setValue(JSON.stringify(parsedjson, null, 2))
} catch (e) { } catch (e) {
console.log("Error parsing JSON: ", e) console.log("Error parsing JSON: ", e)
//return JSON.stringify(inputvalue); toast.info("Invalid JSON.", {
autoClose: 1500,
})
} }
} }
@@ -306,13 +355,14 @@ const CacheView = memo((props) => {
> >
<DialogTitle> <DialogTitle>
<span style={{ color: "white" }}> <span style={{ color: "white" }}>
{ editCache ? "Edit Key" : "Add Key" } { editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`}
</span> </span>
</DialogTitle> </DialogTitle>
<div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}> <div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}>
Key Key
<TextField <TextField
color="primary" color="primary"
disabled={editCache}
style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor }} style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor }}
autoFocus autoFocus
InputProps={{ InputProps={{
@@ -345,7 +395,7 @@ const CacheView = memo((props) => {
autoFixJson(value) autoFixJson(value)
}} }}
> >
<AutoFixHighIcon /> <AutoFixNormalIcon />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</div> </div>
@@ -436,46 +486,49 @@ const CacheView = memo((props) => {
const changeDistribution = (id, selectedSubOrg) => { const changeDistribution = (id, selectedSubOrg) => {
editFileConfig(id, [...new Set(selectedSubOrg)]) editFileConfig(id, [...new Set(selectedSubOrg)], selectedCategory)
} }
const editFileConfig = (id, selectedSubOrg, cacheKey) => {
const data = { const editFileConfig = (id, selectedSubOrg, category) => {
Key: id, const data = {
action: "suborg_distribute", Key: id,
selected_suborgs: selectedSubOrg, action: "suborg_distribute",
} selected_suborgs: selectedSubOrg,
console.log("data: ", data); category: category === undefined || category === "" || category === "default" ? "" : category,
}
const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/config`;
console.log("data: ", data);
fetch(url, {
mode: "cors", const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/config`;
method: "POST",
body: JSON.stringify(data), fetch(url, {
credentials: "include", mode: "cors",
crossDomain: true, method: "POST",
withCredentials: true, body: JSON.stringify(data),
headers: { credentials: "include",
"Content-Type": "application/json; charset=utf-8", crossDomain: true,
}, withCredentials: true,
}) headers: {
.then((response) => "Content-Type": "application/json; charset=utf-8",
response.json().then((responseJson) => { },
if (responseJson["success"] === false) { })
toast("Failed overwriting datastore"); .then((response) =>
} else { response.json().then((responseJson) => {
toast("Successfully updated datastore!"); if (responseJson["success"] === false) {
setTimeout(() => { toast("Failed overwriting datastore");
listOrgCache(orgId); } else {
setShowDistributionPopup(false); toast("Successfully updated datastore!");
}, 1000); setTimeout(() => {
} listOrgCache(orgId, selectedCategory);
}) setShowDistributionPopup(false);
) }, 1000);
.catch((error) => { }
toast("Err: " + error.toString()); })
}); )
.catch((error) => {
toast("Err: " + error.toString());
});
}; };
@@ -579,7 +632,7 @@ const CacheView = memo((props) => {
<div style={{height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}> <div style={{height: "100%", maxHeight: 1700, overflowY: "auto", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin'}}>
<div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}> <div style={{ height: "100%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}>
<div style={{ marginTop: isSelectedDataStore?null:20, marginBottom: 20 }}> <div style={{ marginTop: isSelectedDataStore?null:20, marginBottom: 20 }}>
<h2 style={{ display: isSelectedDataStore?null: "inline" }}>Shuffle Datastore</h2> <h2 style={{ display: isSelectedDataStore?null: "inline" }}>Shuffle Datastore {selectedCategory === "" || selectedCategory === "default" ? "" : `- Category '${selectedCategory}'`}</h2>
<span style={{ marginLeft: isSelectedDataStore?null:25, color:isSelectedDataStore?"#9E9E9E":null}}> <span style={{ marginLeft: isSelectedDataStore?null:25, color:isSelectedDataStore?"#9E9E9E":null}}>
Datastore is a permanent key-value database for storing data that can be used cross-workflow. <br/>You can store anything from lists of IPs to complex configurations.&nbsp; Datastore is a permanent key-value database for storing data that can be used cross-workflow. <br/>You can store anything from lists of IPs to complex configurations.&nbsp;
<a <a
@@ -609,10 +662,175 @@ const CacheView = memo((props) => {
style={{ marginLeft: 16, marginRight: 15, backgroundColor: isSelectedDataStore?"#2F2F2F":null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null,borderRadius:isSelectedDataStore?4:null, width:isSelectedDataStore?81:null, height:isSelectedDataStore?40:null, }} style={{ marginLeft: 16, marginRight: 15, backgroundColor: isSelectedDataStore?"#2F2F2F":null, boxShadow: isSelectedDataStore ? "none":null,textTransform: isSelectedDataStore ? 'capitalize':null,borderRadius:isSelectedDataStore?4:null, width:isSelectedDataStore?81:null, height:isSelectedDataStore?40:null, }}
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => listOrgCache(orgId)} onClick={() => listOrgCache(orgId,selectedCategory)}
> >
<CachedIcon /> <CachedIcon />
</Button> </Button>
{fileCategories !== undefined &&
fileCategories !== null &&
fileCategories.length > 1 ? (
<FormControl style={{ minWidth: 150, maxWidth: 150 }}>
<Select
labelId="input-namespace-select-label"
id="input-namespace-select-id"
style={{
color: "white",
minWidth: 122,
maxWidth: 122,
height: 35,
float: "right",
position: 'relative',
top: 8
}}
value={selectedCategory}
onChange={(event) => {
//if (selectAllChecked || listCache.length > 0) {
if (selectAllChecked || selectedFiles.length > 0) {
setUpdateToThisCategory(event.target.value)
setShowFileCategoryPopup(true)
return
}
setSelectedCategory(event.target.value)
if (event.target.value === "all" || event.target.value === "default") {
listOrgCache(orgId)
} else {
listOrgCache(orgId, event.target.value)
}
// Add it to the url as a query
if (window.location.search.includes("category=")) {
const newurl = window.location.href.replace(/category=[^&]+/, `category=${event.target.value}`)
window.history.pushState({ path: newurl }, "", newurl)
} else {
window.history.pushState({ path: window.location.href }, "", `${window.location.href}&category=${event.target.value}`)
}
}}
>
{fileCategories.map((data, index) => {
return (
<MenuItem
key={index}
value={data}
style={{ color: "white" }}
>
{data.replaceAll("_", " ")}
</MenuItem>
);
})}
</Select>
<Dialog
PaperProps={{
sx: {
borderRadius: theme?.palette?.DialogStyle?.borderRadius,
border: theme?.palette?.DialogStyle?.border,
fontFamily: theme?.typography?.fontFamily,
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
zIndex: 1000,
'& .MuiDialogContent-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
'& .MuiDialogTitle-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
'& .MuiDialogActions-root': {
backgroundColor: theme?.palette?.DialogStyle?.backgroundColor,
},
},
}}
open={showFileCategoryPopup}
onClose={() => {
setShowFileCategoryPopup(false)
}}
>
<DialogTitle>File Categories</DialogTitle>
<DialogContent>
Please note that your selected files ({selectedFileId?.length}) will be moved to the <kbd>{updateToThisCategory}</kbd> category.
</DialogContent>
<DialogActions>
<Button
onClick={() => {
setShowFileCategoryPopup(false)
}}
style={{fontSize: 16, textTransform: 'none'}}
>
Close
</Button>
<Button
onClick={() => {
//handleUpdateFileCategory(updateToThisCategory)
toast.error("Not implemented.")
}}
style={{fontSize: 16, textTransform: 'none', color: "#1a1a1a", backgroundColor: "#ff8544"}}
>
Update
</Button>
</DialogActions>
</Dialog>
</FormControl>
) : null}
<div style={{display: "inline-flex", position:"relative", top: 8}}>
{renderTextBox ?
<Tooltip title={"Close"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15, height: 35, borderRadius: 4, backgroundColor: "#494949", textTransform: 'none', fontSize: 16, color: "#f1f1f1" }}
color="primary"
onClick={() => {
setRenderTextBox(false);
console.log(" close clicked")
}}
>
<ClearIcon/>
</Button>
</Tooltip>
:
<Tooltip title={"Add new file category"} style={{}} aria-label={""}>
<Button
style={{ marginLeft: 5, marginRight: 15, width: 169, height: 35, borderRadius: 4, backgroundColor: "#494949", textTransform: 'none', fontSize: 16, color: "#f1f1f1" }}
color="primary"
onClick={() => {
setRenderTextBox(true);
}}
>
<AddIcon/>
Category (beta)
</Button>
</Tooltip>
}
{renderTextBox && <TextField
onKeyPress={(event)=>{
handleKeyDown(event);
if(event.key === 'Enter' && selectedFileId.length > 0){
//setShowFileCategoryPopup(true)
setUpdateToThisCategory(event.target.value)
}
}}
style={{
height: 35,
width: 200,
marginTop: 0,
}}
InputProps={{
style: {
color: "white",
height: 35,
fontSize: 16,
borderRadius: 4,
paddingTop: 0,
},
}}
color="primary"
placeholder="Category name"
required
margin="dense"
defaultValue={""}
autoFocus
/>}</div>
{isSelectedDataStore? null :<Divider {isSelectedDataStore? null :<Divider
style={{ style={{
marginTop: 20, marginTop: 20,
@@ -704,6 +922,16 @@ const CacheView = memo((props) => {
</ListItem> </ListItem>
): listCache?.map((data, index) => { ): listCache?.map((data, index) => {
var category = selectedCategory
if (selectedCategory === "default") {
category = ""
}
if (data?.category === undefined && category === "") {
} else if (data?.category !== category) {
return null
}
var bgColor = isSelectedDataStore? "#212121":"#27292d"; var bgColor = isSelectedDataStore? "#212121":"#27292d";
if (index % 2 === 0) { if (index % 2 === 0) {
bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023"; bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023";
+14 -2
View File
@@ -1144,14 +1144,26 @@ const EditWorkflow = (props) => {
</FormControl> </FormControl>
</div> </div>
<Divider style={{marginTop: 75, marginBottom: 75, }}/>
<Typography variant="h4" style={{ marginTop: 100, }}>
<Typography variant="h4" style={{ }}>
Publishing Publishing
<Chip
style={{ marginLeft: 20, marginTop: 10, }}
color={workflow?.public === true ? "primary" : "secondary"}
variant={workflow?.public === true ? "default" : "outlined"}
label={workflow?.public === true ? "Public" : "NOT Public"}
/>
</Typography> </Typography>
<Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}> <Typography variant="body2" color="textSecondary" style={{ marginTop: 10, }}>
Publishing is related to making the workflow itself public. When publishing a workflow, all the details (except sensitive info) become available to everyone. The details below will help a user understand this better. When a workflow is published, you keep the original, and a copy enters the workflow search, and is associated with your <a href="/creators" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">creator account</a>, if you have one. You can always unpublish the workflow after. When ready to publish, click the three dots next to a workflow on the main workflow screen. After publishing, you can find it in the Shuffle search engine. Publishing is related to making this workflow itself public. When publishing a workflow, all the details (except sensitive info) become available to anyone. The fields below will help a user and Shuffle's system understand your workflow better. When a workflow is published, you keep the original, and a copy enters the Shuffle workflow search, and is associated with your <a href="/creators" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">creator</a> or partner account, if you have one. You can always unpublish the workflow after. When ready to publish, click the three dots next to a workflow on the main workflow page.
You can always unpublish a workflow after.
</Typography> </Typography>
<LocalizationProvider style={{marginLeft: 0, }} dateAdapter={AdapterDayjs}> <LocalizationProvider style={{marginLeft: 0, }} dateAdapter={AdapterDayjs}>
<DatePicker <DatePicker
sx={{ sx={{
+1 -1
View File
@@ -89,7 +89,6 @@ const Files = memo((props) => {
console.log('escape pressed') console.log('escape pressed')
setRenderTextBox(false); setRenderTextBox(false);
} }
} }
const changeDistribution = (id, selectedSubOrg) => { const changeDistribution = (id, selectedSubOrg) => {
@@ -1051,6 +1050,7 @@ const Files = memo((props) => {
</Dialog> </Dialog>
</FormControl> </FormControl>
) : null} ) : null}
<div style={{display: "inline-flex", position:"relative", top: 8}}> <div style={{display: "inline-flex", position:"relative", top: 8}}>
{renderTextBox ? {renderTextBox ?
<Tooltip title={"Close"} style={{}} aria-label={""}> <Tooltip title={"Close"} style={{}} aria-label={""}>
@@ -28,7 +28,16 @@ import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import theme from "../theme.jsx"; import theme from "../theme.jsx";
const itemHeight = 24 const itemHeight = 24
export const getParentNodes = (workflow, action) => { export const getParentNodes = (workflow, action, count) => {
if (count === undefined) {
count = 0
}
// 50 levels of parent nodes
if (count > 50) {
return []
}
if (action === undefined || action === null) { if (action === undefined || action === null) {
return [] return []
} }
@@ -81,8 +90,7 @@ export const getParentNodes = (workflow, action) => {
continue; continue;
} }
// FIXME: This part is only handling first level, // FIXME: recursion
// but needs to recurse
var incomingEdges = [] var incomingEdges = []
for (var branchkey in workflow.branches) { for (var branchkey in workflow.branches) {
const branch = workflow.branches[branchkey] const branch = workflow.branches[branchkey]
@@ -90,13 +98,19 @@ export const getParentNodes = (workflow, action) => {
continue continue
} }
// Go up in the levels // FIXME: Go up in the levels
// This somehow creates infinite recursion for now, so
// we are skipping it.
// This function is also not used for cytoscape recursion,
// so it doesn't matter much (yet)
/*
const parents = getParentNodes(workflow, { const parents = getParentNodes(workflow, {
id: branch.source_id, id: branch.source_id,
}) }, count+1)
if (parents.length > 0) { if (parents.length > 0) {
incomingEdges = incomingEdges.concat(parents) incomingEdges = incomingEdges.concat(parents)
} }
*/
incomingEdges.push(branch) incomingEdges.push(branch)
} }
@@ -215,8 +229,6 @@ const WorkflowValidationTimeline = (props) => {
} }
} }
//const parents = getParentNodes(workflow, action)
//console.log("PARENTS", key, parents)
if (parents !== undefined && parents !== null && parents.length > 0) { if (parents !== undefined && parents !== null && parents.length > 0) {
const parentfound = parents.find((element) => element.id === startnodeId) const parentfound = parents.find((element) => element.id === startnodeId)
if (parentfound !== undefined) { if (parentfound !== undefined) {
+71 -21
View File
@@ -574,7 +574,7 @@ const AngularWorkflow = (defaultprops) => {
}, [editWorkflowModalOpen]) }, [editWorkflowModalOpen])
useEffect(() => { useEffect(() => {
if (selectedTrigger !== undefined && selectedTrigger.parameters !== undefined && selectedTrigger.parameters !== undefined && selectedTrigger.parameters.length > 1) { if (selectedTrigger !== undefined && selectedTrigger?.parameters !== undefined && selectedTrigger?.parameters !== null && selectedTrigger?.parameters?.length > 1) {
// Right now just setting for the subflow // Right now just setting for the subflow
setSelectedTriggerValue(selectedTrigger?.parameters[1]?.value) setSelectedTriggerValue(selectedTrigger?.parameters[1]?.value)
} }
@@ -4916,7 +4916,7 @@ const AngularWorkflow = (defaultprops) => {
if (!branchFound) { if (!branchFound) {
var relevantNodes = [] var relevantNodes = []
const minDistance = 225 const minDistance = 185
const draggedNode = event.target const draggedNode = event.target
const allnodes = cy.nodes().jsons() const allnodes = cy.nodes().jsons()
for (var nodekey in allnodes) { for (var nodekey in allnodes) {
@@ -5481,6 +5481,7 @@ const AngularWorkflow = (defaultprops) => {
// https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
// onNodeClick // onNodeClick
const onNodeSelect = (event, newAppAuth) => { const onNodeSelect = (event, newAppAuth) => {
// Forces all states to update at the same time, // Forces all states to update at the same time,
// Otherwise everything is SUPER slow // Otherwise everything is SUPER slow
@@ -5654,7 +5655,14 @@ const AngularWorkflow = (defaultprops) => {
} }
ReactDOM.unstable_batchedUpdates(() => { ReactDOM.unstable_batchedUpdates(() => {
const selectedNodes = cy.$(':selected')
if (data.isButton) { if (data.isButton) {
if (selectedNodes?.length > 1) {
event.target.unselect()
//console.log(": ", selectedNodes.length)
return
}
if (data.buttonType === "suggestion") { if (data.buttonType === "suggestion") {
if (cy === undefined) { if (cy === undefined) {
console.log("Cy not defined yet") console.log("Cy not defined yet")
@@ -5894,6 +5902,14 @@ const AngularWorkflow = (defaultprops) => {
} }
if (data.type === "ACTION") { if (data.type === "ACTION") {
if (selectedNodes?.length > 1) {
console.log("Unselecting ACTION due to multiple nodes selected")
setSelectedAction({})
setSelectedApp({})
setSelectedComment({})
return
}
setSelectedComment({}) setSelectedComment({})
// FIXME: is this what is mapping it an actual action in the workflow? wtf? // FIXME: is this what is mapping it an actual action in the workflow? wtf?
@@ -6241,6 +6257,14 @@ const AngularWorkflow = (defaultprops) => {
setSelectedActionEnvironment(env); setSelectedActionEnvironment(env);
} }
} else if (data.type === "TRIGGER") { } else if (data.type === "TRIGGER") {
if (selectedNodes?.length > 1) {
console.log("Unselecting ACTION due to multiple nodes selected")
setSelectedAction({})
setSelectedApp({})
setSelectedComment({})
return
}
setSelectedComment({}) setSelectedComment({})
if (workflow.triggers === null) { if (workflow.triggers === null) {
workflow.triggers = [] workflow.triggers = []
@@ -6442,6 +6466,14 @@ const AngularWorkflow = (defaultprops) => {
//setSelectedActionEnvironment(data.env) //setSelectedActionEnvironment(data.env)
}, 25) }, 25)
} else if (data.type === "COMMENT") { } else if (data.type === "COMMENT") {
if (selectedNodes?.length > 1) {
console.log("Unselecting ACTION due to multiple nodes selected")
setSelectedAction({})
setSelectedApp({})
setSelectedComment({})
return
}
setSelectedComment(data); setSelectedComment(data);
} else { } else {
toast("Can't handle node type " + data.type); toast("Can't handle node type " + data.type);
@@ -7450,26 +7482,21 @@ const AngularWorkflow = (defaultprops) => {
}; };
const handlePaste = (event) => { const handlePaste = (event) => {
//console.log("EV: ", event)
if ( if (
event.path !== undefined && event.path !== undefined &&
event.path !== null && event.path !== null &&
event.path.length > 0 event.path.length > 0
) { ) {
//console.log("PATH: ", event.path[0])
if (event.path[0].localName !== "body") { if (event.path[0].localName !== "body") {
//console.log("Skipping because body is not targeted")
return; return;
} }
} }
//console.log("PATH2: ", event.target)
if ( if (
event.target !== undefined && event.target !== undefined &&
event.target !== null event.target !== null
) { ) {
if (event.target.localName !== "body") { if (event.target.localName !== "body") {
//console.log("Skipping because body is not targeted")
return; return;
} }
} }
@@ -7479,12 +7506,10 @@ const AngularWorkflow = (defaultprops) => {
const clipboard = (event.originalEvent || event).clipboardData.getData( const clipboard = (event.originalEvent || event).clipboardData.getData(
"text/plain" "text/plain"
); );
//console.log("Text: ", clipboard)
//window.document.execCommand('insertText', false, text);
//
try { try {
const allnodes = cy.nodes().jsons()
var parsedjson = JSON.parse(clipboard); var parsedjson = JSON.parse(clipboard);
// Check if array
if (!Array.isArray(parsedjson)) { if (!Array.isArray(parsedjson)) {
console.log("Not array! Adding to array.") console.log("Not array! Adding to array.")
parsedjson = [parsedjson] parsedjson = [parsedjson]
@@ -7492,7 +7517,6 @@ const AngularWorkflow = (defaultprops) => {
for (let jsonkey in parsedjson) { for (let jsonkey in parsedjson) {
var item = parsedjson[jsonkey]; var item = parsedjson[jsonkey];
console.log("Adding: ", item);
if (item.data === undefined || item.data === null) { if (item.data === undefined || item.data === null) {
console.log("Appending from here") console.log("Appending from here")
@@ -7512,13 +7536,31 @@ const AngularWorkflow = (defaultprops) => {
item.data.isStartNode = false item.data.isStartNode = false
} }
// Find a cy.data() label with the same name
const foundnodes = allnodes.filter((data) => {
//console.log("COMP: ", data.data.label, item.data.label)
if (data.data.label === undefined || data.data.label === null) {
return false
}
return data.data.label === item.data.label
})
if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) {
// Weird naming copy lol
item.data.label = item.data.label + "_copy_" + allnodes.length
}
item.data.id = uuidv4() item.data.id = uuidv4()
cy.add({ cy.add({
group: item.group, group: item.group,
data: item.data, data: item.data,
position: item.position, position: {
}); x: item.position.x+20,
y: item.position.y+20,
},
})
} }
} catch (e) { } catch (e) {
console.log("Error pasting: ", e); console.log("Error pasting: ", e);
@@ -9591,7 +9633,12 @@ const AngularWorkflow = (defaultprops) => {
console.log("Error fitting cytoscape (4): ", error) console.log("Error fitting cytoscape (4): ", error)
} }
cy.on("boxselect", "node", (e) => { cy.on("boxselect", "node", (e) => {
e.preventDefault()
e.stopPropagation()
console.log("BOXSELECT: ", e.boxSelectElements)
if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) { if (e.target.data("isButton") || e.target.data("isDescriptor") || e.target.data("isSuggestion")) {
e.target.unselect(); e.target.unselect();
} }
@@ -9601,10 +9648,15 @@ const AngularWorkflow = (defaultprops) => {
cy.on("boxstart", (e) => { cy.on("boxstart", (e) => {
console.log("START"); console.log("START");
e.preventDefault()
e.stopPropagation()
}); });
cy.on("boxend", (e) => { cy.on("boxend", (e) => {
console.log("END: ", cy) e.preventDefault()
e.stopPropagation()
console.log("END: ", e.target, cy)
var cydata = cy.$(":selected").jsons(); var cydata = cy.$(":selected").jsons();
if (cydata !== undefined && cydata !== null && cydata.length > 0) { if (cydata !== undefined && cydata !== null && cydata.length > 0) {
// Unselect all nodes // Unselect all nodes
@@ -17404,11 +17456,11 @@ const AngularWorkflow = (defaultprops) => {
selectedTrigger.status === "running" selectedTrigger.status === "running"
} }
defaultValue={ defaultValue={
selectedTrigger?.parameters === undefined || selectedTrigger?.parameters === null || selectedTrigger?.parameters?.length === 0 ? isCloud || selectedTrigger?.environment === "cloud" ? "*/25 * * * *" : "60" : selectedTrigger.parameters[0]?.value selectedTrigger?.parameters === undefined || selectedTrigger?.parameters === null || selectedTrigger?.parameters?.length === 0 ? isCloud || selectedTrigger?.environment === "cloud" ? "*/25 * * * *" : "60" : selectedTrigger?.parameters[0]?.value
} }
color="primary" color="primary"
placeholder={ placeholder={
selectedTrigger.parameters === undefined ? isCloud || selectedTrigger?.environment === "cloud" ? "*/25 * * * *" : "60" : selectedTrigger.parameters[0]?.value selectedTrigger.parameters === undefined || selectedTrigger?.parameters === null || selectedTrigger?.parameters?.length === 0 ? isCloud || selectedTrigger?.environment === "cloud" ? "*/25 * * * *" : "60" : selectedTrigger?.parameters[0]?.value
} }
onBlur={(e) => { onBlur={(e) => {
setTriggerCronWrapper(e.target.value); setTriggerCronWrapper(e.target.value);
@@ -21602,10 +21654,8 @@ const AngularWorkflow = (defaultprops) => {
const checked = validateJson(data.value.trim()) const checked = validateJson(data.value.trim())
if (data.name === "shuffle_action_logs" && data.value !== undefined && data.value !== null && data.value.length > 0 && data.value.includes("add env SHUFFLE_LOGS_DISABLED")) { if (data.name === "shuffle_action_logs" && data.value !== undefined && data.value !== null && data.value.length > 0 && data.value.includes("add env SHUFFLE_LOGS_DISABLED")) {
data.value = `Logs for this action are not available without <a style={{ color: "#FF8544", }} href="/admin?tab=locations" target="_blank" rel="noopener noreferrer">an onprem environment</a> with the <a style={{ color: "#FF8544", }} href="/docs/configuration#scaling-shuffle" target="_blank" rel="noopener noreferrer">SHUFFLE_LOGS_DISABLED</a> environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode.`
/*
return ( return (
<div style={{ maxWidth: 600, marginTop: 15, overflowX: "hidden", }}> <div style={{ maxWidth: 600, marginTop: 75, overflowX: "hidden", }}>
<Typography <Typography
variant="body1" variant="body1"
style={{}} style={{}}
@@ -21613,10 +21663,10 @@ const AngularWorkflow = (defaultprops) => {
<b>Action Logs</b> <b>Action Logs</b>
</Typography> </Typography>
<Typography variant="body2" style={{ whiteSpace: 'pre-line', }}> <Typography variant="body2" style={{ whiteSpace: 'pre-line', }}>
More log details for this action are not available without <a style={{ color: "#FF8544", }} href="/admin?tab=locations" target="_blank" rel="noopener noreferrer">an onprem environment</a> with the <a style={{ color: "#FF8544", }} href="/docs/configuration#scaling-shuffle" target="_blank" rel="noopener noreferrer">SHUFFLE_LOGS_DISABLED</a> environment variable set to false: SHUFFLE_LOGS_DISABLED=false. Logs are enabled by default, except in scale mode.
</Typography> </Typography>
</div> </div>
) )
*/
} }
var showlink = false var showlink = false