Merge branch 'nightly' of https://github.com/Shuffle/Shuffle into automation

This commit is contained in:
lalitdeore12@gmail.com
2025-02-26 12:41:23 +05:30
79 changed files with 8422 additions and 583 deletions
+16 -16
View File
@@ -116,7 +116,7 @@ const AppGrid = (props) => {
})
.then((response) => response.json())
.then((response) => {
if (response.success === true) {
if (response?.success === true) {
setFormMessage(response.reason);
//toast("Thanks for submitting!")
} else {
@@ -307,7 +307,7 @@ const AppGrid = (props) => {
})
.then(response => response.json())
.then(responseJson => {
if (responseJson.success) {
if (responseJson?.success) {
setUserdata(responseJson);
setAllActivatedAppIds(responseJson.active_apps)
setIsLoggedIn(true);
@@ -350,7 +350,7 @@ const AppGrid = (props) => {
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson?.success === false) {
toast.error(responseJson.reason);
} else {
//toast.success(`App ${type}d Successfully!`);
@@ -414,7 +414,7 @@ const AppGrid = (props) => {
scrollbarColor: "#494949 #2f2f2f",
}}
>
{hits.map((data, index) => {
{hits?.map((data, index) => {
const appUrl =
isCloud === true ?
`/apps/${data.objectID}`
@@ -556,7 +556,7 @@ const AppGrid = (props) => {
}}
>
<span>
{data.tags.slice(0, 1).map((tag, tagIndex) => (
{data?.tags?.slice(0, 1)?.map((tag, tagIndex) => (
<span key={tagIndex}>
{normalizedString(tag)}
{tagIndex < 1 ? ", " : ""}
@@ -569,7 +569,7 @@ const AppGrid = (props) => {
) : (
<div style={{ width: 230, textOverflow: "ellipsis", overflow: 'hidden', whiteSpace: 'nowrap', }}>
{data.tags &&
data.tags.map((tag, tagIndex) => (
data?.tags?.map((tag, tagIndex) => (
<span key={tagIndex}>
{normalizedString(tag)}
{tagIndex < data.tags.length - 1 ? ", " : ""}
@@ -760,7 +760,7 @@ const AppGrid = (props) => {
};
const transformRefinementListItems = items =>
items.map(item => ({
items?.map(item => ({
...item,
label: item.label === 'true' ? 'App Editor' : 'Python',
}));
@@ -1103,7 +1103,7 @@ const AppGrid = (props) => {
}
});
const categoryArray = Object.keys(categoryCountMap).map((category) => ({
const categoryArray = Object.keys(categoryCountMap)?.map((category) => ({
category,
count: categoryCountMap[category],
}));
@@ -1169,7 +1169,7 @@ const AppGrid = (props) => {
{!isLoading && (
<Collapse in={isCategoreListExpanded}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%' }}>
{topCategories.map((data, index) => (
{topCategories?.map((data, index) => (
<Button
key={data.category}
style={{
@@ -1247,7 +1247,7 @@ const AppGrid = (props) => {
});
}
const tagArray = Object.keys(tagCountMap).map((tag) => ({
const tagArray = Object.keys(tagCountMap)?.map((tag) => ({
tag,
count: tagCountMap[tag],
}));
@@ -1308,7 +1308,7 @@ const AppGrid = (props) => {
</Button>
<Collapse in={isActionLabelExpanded}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%', }}>
{topTags && topTags.length > 0 && topTags.map((data, index) => (
{topTags && topTags.length > 0 && topTags?.map((data, index) => (
<Button
key={index}
onClick={() => handleCheckboxChange(index)}
@@ -1419,7 +1419,7 @@ const AppGrid = (props) => {
<Collapse in={isCreatedWithExpanded}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', width: '100%' }}>
{AppCreatedWithOptions.map((data, index) => (
{AppCreatedWithOptions?.map((data, index) => (
<Button
style={{
display: "inline-flex",
@@ -1689,7 +1689,7 @@ const AppGrid = (props) => {
maxHeight: 570,
}}
>
{filteredUserAppdata.map((data, index) => {
{filteredUserAppdata?.map((data, index) => {
const isMouseOverOnCloudIcon = false;
const xs = 12;
const rowHandler = 12;
@@ -1847,8 +1847,8 @@ const AppGrid = (props) => {
<div style={{minWidth: 120, overflow: "hidden", }}>
{data.generated !== true ?
<div>
{data.tags &&
data.tags.slice(0,2).map((tag, tagIndex) => (
{data?.tags &&
data?.tags?.slice(0,2)?.map((tag, tagIndex) => (
<span key={tagIndex}>
{normalizedString(tag)}
{tagIndex < data.tags.length - 1 ? ", " : ""}
@@ -1888,7 +1888,7 @@ const AppGrid = (props) => {
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson?.success === false) {
toast.error(responseJson.reason);
} else {
toast.success("App Deactivated Successfully. Reload UI to see updated changes.")
+1 -1
View File
@@ -179,7 +179,7 @@ const AppSearchButtons = (props) => {
return (
<Grid item xs={xsValue} style={{ alignItems: "center", marginTop: 5, }}
<Grid item xs={xsValue} style={{ alignItems: "center", marginTop: 5, maxWidth: "50%", minWidth: "50%" }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
+289 -61
View File
@@ -24,11 +24,14 @@ import {
Checkbox,
MenuItem,
DialogContent,
FormControl,
Select,
} from "@mui/material";
import {
Link as LinkIcon,
AutoFixHigh as AutoFixHighIcon,
AutoFixNormal as AutoFixNormalIcon,
Edit as EditIcon,
FileCopy as FileCopyIcon,
SelectAll as SelectAllIcon,
@@ -50,8 +53,8 @@ import {
Business as BusinessIcon,
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon,
CheckBox,
Key,
Clear as ClearIcon,
Add as AddIcon,
} from "@mui/icons-material";
import { validateJson, } from "../views/Workflows.jsx";
import { Context } from "../context/ContextApi.jsx";
@@ -91,15 +94,40 @@ const CacheView = memo((props) => {
const [showDistributionPopup, setShowDistributionPopup] = useState(false);
const [selectedSubOrg, setSelectedSubOrg] = 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(() => {
if(orgId?.length >0){
listOrgCache(orgId);
if (orgId?.length > 0) {
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) => {
fetch(globalUrl + `/api/v1/orgs/${orgId}/list_cache`, {
const listOrgCache = (orgId, category) => {
const url = `${globalUrl}/api/v1/orgs/${orgId}/list_cache${category !== undefined ? `?category=${category.replaceAll(" ", "_")}` : ""}`
fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -119,6 +147,19 @@ const CacheView = memo((props) => {
if (responseJson.success === true) {
setListCache(responseJson.keys);
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 !== "") {
@@ -132,15 +173,12 @@ const CacheView = memo((props) => {
const deleteCache = (orgId, key) => {
//toast("Attempting to delete Cache");
// method: "DELETE",
const method = "POST"
//const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/${key}`
const url = `${globalUrl}/api/v1/orgs/${orgId}/delete_cache`
const parsed = {
"org_id": orgId,
"key": key,
"category": selectedCategory === "" || selectedCategory === "default" ? "" : selectedCategory,
}
fetch(url, {
@@ -155,7 +193,7 @@ const CacheView = memo((props) => {
if (response.status === 200) {
toast("Successfully deleted Cache");
setTimeout(() => {
listOrgCache(orgId);
listOrgCache(orgId, selectedCategory)
}, 1000);
} else {
toast("Failed deleting Cache. Does it still exist?");
@@ -167,7 +205,12 @@ const CacheView = memo((props) => {
};
const editOrgCache = (orgId) => {
const cache = { key: dataValue.key , value: value };
const cache = {
key: dataValue.key,
value: value,
category: selectedCategory,
}
setCacheInput([cache]);
fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, {
@@ -191,7 +234,7 @@ const CacheView = memo((props) => {
.then((responseJson) => {
setAddCache(responseJson);
toast("Cache Edited Successfully!");
listOrgCache(orgId);
listOrgCache(orgId, selectedCategory);
setModalOpen(false);
})
.catch((error) => {
@@ -200,9 +243,13 @@ const CacheView = memo((props) => {
};
const addOrgCache = (orgId) => {
const cache = { key: key, value: value };
const cache = {
key: key,
value: value,
category: selectedCategory,
}
setCacheInput([cache]);
console.log("cache input:", cacheInput)
fetch(globalUrl + `/api/v1/orgs/${orgId}/set_cache`, {
@@ -224,8 +271,8 @@ const CacheView = memo((props) => {
})
.then((responseJson) => {
setAddCache(responseJson);
toast("New key Added Successfully!");
listOrgCache(orgId);
toast("New key added Successfully!");
listOrgCache(orgId, selectedCategory);
setModalOpen(false);
})
.catch((error) => {
@@ -243,7 +290,9 @@ const CacheView = memo((props) => {
setValue(JSON.stringify(parsedjson, null, 2))
} catch (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>
<span style={{ color: "white" }}>
{ editCache ? "Edit Key" : "Add Key" }
{ editCache ? "Edit Key" : "Add Key"}{selectedCategory === "" || selectedCategory === "default" ? "" : ` in category '${selectedCategory}'`}
</span>
</DialogTitle>
<div style={{ paddingLeft: "30px", paddingRight: '30px', backgroundColor: "#212121", }}>
Key
<TextField
color="primary"
disabled={editCache}
style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor }}
autoFocus
InputProps={{
@@ -345,7 +395,7 @@ const CacheView = memo((props) => {
autoFixJson(value)
}}
>
<AutoFixHighIcon />
<AutoFixNormalIcon />
</IconButton>
</Tooltip>
</div>
@@ -436,46 +486,49 @@ const CacheView = memo((props) => {
const changeDistribution = (id, selectedSubOrg) => {
editFileConfig(id, [...new Set(selectedSubOrg)])
editFileConfig(id, [...new Set(selectedSubOrg)], selectedCategory)
}
const editFileConfig = (id, selectedSubOrg, cacheKey) => {
const data = {
Key: id,
action: "suborg_distribute",
selected_suborgs: selectedSubOrg,
}
console.log("data: ", data);
const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/config`;
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed overwriting datastore");
} else {
toast("Successfully updated datastore!");
setTimeout(() => {
listOrgCache(orgId);
setShowDistributionPopup(false);
}, 1000);
}
})
)
.catch((error) => {
toast("Err: " + error.toString());
});
const editFileConfig = (id, selectedSubOrg, category) => {
const data = {
Key: id,
action: "suborg_distribute",
selected_suborgs: selectedSubOrg,
category: category === undefined || category === "" || category === "default" ? "" : category,
}
console.log("data: ", data);
const url = `${globalUrl}/api/v1/orgs/${orgId}/cache/config`;
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed overwriting datastore");
} else {
toast("Successfully updated datastore!");
setTimeout(() => {
listOrgCache(orgId, selectedCategory);
setShowDistributionPopup(false);
}, 1000);
}
})
)
.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%", width: "calc(100% - 20px)", scrollbarColor: '#494949 transparent', scrollbarWidth: 'thin' }}>
<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}}>
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
@@ -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, }}
variant="contained"
color="primary"
onClick={() => listOrgCache(orgId)}
onClick={() => listOrgCache(orgId,selectedCategory)}
>
<CachedIcon />
</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
style={{
marginTop: 20,
@@ -704,6 +922,16 @@ const CacheView = memo((props) => {
</ListItem>
): 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";
if (index % 2 === 0) {
bgColor = isSelectedDataStore? "#1A1A1A":"#1f2023";
+41 -6
View File
@@ -191,6 +191,10 @@ const EditWorkflow = (props) => {
var upload = "";
var total_count = 0
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
return (
<Drawer
anchor={"right"}
@@ -624,6 +628,7 @@ const EditWorkflow = (props) => {
All
</MenuItem>
{userdata.orgs.map((data, index) => {
var skipOrg = false;
if (data.creator_org !== undefined && data.creator_org !== null && data.creator_org === userdata.active_org.id) {
// Finds the parent org
@@ -631,6 +636,8 @@ const EditWorkflow = (props) => {
return null
}
const correctRegion = data?.region_url === userdata?.region_url
const imagesize = 22
const imageStyle = {
width: imagesize,
@@ -664,7 +671,11 @@ const EditWorkflow = (props) => {
return (
<MenuItem key={index} value={data.id}>
<MenuItem
key={index}
value={data.id}
disabled={isCloud && !correctRegion}
>
<Checkbox checked={innerWorkflow.suborg_distribution !== undefined && innerWorkflow.suborg_distribution !== null && innerWorkflow.suborg_distribution.includes(data.id)} />
{image}{" "}
<span style={{ marginLeft: 8 }}>
@@ -1135,14 +1146,26 @@ const EditWorkflow = (props) => {
</FormControl>
</div>
<Divider style={{marginTop: 75, marginBottom: 75, }}/>
<Typography variant="h4" style={{ marginTop: 100, }}>
<Typography variant="h4" style={{ }}>
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 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>
<LocalizationProvider style={{marginLeft: 0, }} dateAdapter={AdapterDayjs}>
<DatePicker
sx={{
@@ -1172,9 +1195,21 @@ const EditWorkflow = (props) => {
setInnerWorkflow(innerWorkflow)
}}
>
<FormControlLabel value="trigger" control={<Radio />} label="Trigger" />
<FormControlLabel value="subflow" control={<Radio />} label="Subflow" />
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" />
<Tooltip title="Agentic workflows takes an input based on input questions (forms) and performs actions based on it by itself, using Large Action Models & Singul">
<FormControlLabel value="agentic" control={<Radio />} label="Agentic" />
</Tooltip>
<Tooltip title="Trigger workflows are typically running a schedule to get some data, doing some deduplication before sending it to a subflow or standalone workflow.">
<FormControlLabel value="trigger" control={<Radio />} label="Trigger" />
</Tooltip>
<Tooltip title="Subflow workflows are typically used to subprocess some data, and in some cases return the result to the parent workflow.">
<FormControlLabel value="subflow" control={<Radio />} label="Subflow" />
</Tooltip>
<Tooltip title="Standalone is default. This has no impact on Shuffle as a system.">
<FormControlLabel value="standalone" control={<Radio />} label="Standalone" />
</Tooltip>
</RadioGroup>
</FormControl>
+1 -1
View File
@@ -89,7 +89,6 @@ const Files = memo((props) => {
console.log('escape pressed')
setRenderTextBox(false);
}
}
const changeDistribution = (id, selectedSubOrg) => {
@@ -1051,6 +1050,7 @@ const Files = memo((props) => {
</Dialog>
</FormControl>
) : null}
<div style={{display: "inline-flex", position:"relative", top: 8}}>
{renderTextBox ?
<Tooltip title={"Close"} style={{}} aria-label={""}>
+1 -5
View File
@@ -721,11 +721,7 @@ useEffect(() => {
margin_left:
org.creator_org !== undefined &&
org.creator_org !== null &&
org.creator_org.length > 0
? org.id === userdata.active_org.id
? 0
: 20
: 0,
org.creator_org.length > 0 ? 20 : 0,
};
}) || []
);
+207 -202
View File
@@ -74,7 +74,7 @@ const OrgHeaderexpandedNew = (props) => {
getContentAnchorEl: () => null,
};
const [orgName, setOrgName] = useState(selectedOrganization?.name);
const [orgName, setOrgName] = useState(selectedOrganization?.name);
const [orgDescription, setOrgDescription] = React.useState(
selectedOrganization.description
);
@@ -180,41 +180,41 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
const [uploadUsername, setUploadUsername] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_username === undefined || selectedOrganization.defaults.workflow_upload_username.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_username)
const [uploadToken, setUploadToken] = React.useState(selectedOrganization.defaults === undefined ? "" : selectedOrganization.defaults.workflow_upload_token === undefined || selectedOrganization.defaults.workflow_upload_token.length === 0 ? "" : selectedOrganization.defaults.workflow_upload_token)
const [regionStatus, setRegionStatus] = useState();
useEffect(() => {
if (documentationReference !== selectedOrganization?.defaults?.documentation_reference) {
setDocumentationReference(selectedOrganization?.defaults?.documentation_reference)
}
if (uploadRepo !== selectedOrganization?.defaults?.workflow_upload_repo){
if (uploadRepo !== selectedOrganization?.defaults?.workflow_upload_repo) {
setUploadRepo(selectedOrganization?.defaults?.workflow_upload_repo)
}
if (uploadBranch !== selectedOrganization?.defaults?.workflow_upload_branch){
if (uploadBranch !== selectedOrganization?.defaults?.workflow_upload_branch) {
setUploadBranch(selectedOrganization?.defaults?.workflow_upload_branch)
}
if (uploadUsername !== selectedOrganization?.defaults?.workflow_upload_username){
if (uploadUsername !== selectedOrganization?.defaults?.workflow_upload_username) {
setUploadUsername(selectedOrganization?.defaults?.workflow_upload_username)
}
if (uploadToken !== selectedOrganization?.defaults?.workflow_upload_token){
if (uploadToken !== selectedOrganization?.defaults?.workflow_upload_token) {
setUploadToken(selectedOrganization?.defaults?.workflow_upload_token)
}
}, [selectedOrganization])
useEffect(() => {
if (selectedOrganization !== undefined && selectedOrganization !== null) {
if((orgName === undefined || orgName === null || orgName.length === 0) && selectedOrganization?.name !== orgName) {
if ((orgName === undefined || orgName === null || orgName.length === 0) && selectedOrganization?.name !== orgName) {
setOrgName(selectedOrganization?.name)
}
if((orgDescription === undefined || orgDescription === null || orgDescription.length === 0) && selectedOrganization?.description !== orgDescription) {
if ((orgDescription === undefined || orgDescription === null || orgDescription.length === 0) && selectedOrganization?.description !== orgDescription) {
setOrgDescription(selectedOrganization?.description)
}
}
}, [selectedOrganization])
const handleEditOrg = (
name,
description,
@@ -262,21 +262,21 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
});
}
const handleSendChangeRegionMail = (region)=> {
if(selectedOrganization === undefined || selectedOrganization === null){
const handleSendChangeRegionMail = (region) => {
if (selectedOrganization === undefined || selectedOrganization === null) {
toast.error("Failed to send request for changing region. Please contact support@shuffler.io.")
return
}
let destinationRegion = region;
if (region === "US") {
destinationRegion = "us-west2";
} else if (region === "EU") {
destinationRegion = "europe-west3";
} else if (region === "CA") {
destinationRegion = "northamerica-northeast1";
} else if (region === "UK") {
destinationRegion = "europe-west2";
}
const regionToCloudRegion = {
'US': 'us-west2',
'EU': 'europe-west3',
'CA': 'northamerica-northeast1',
'UK': 'europe-west2',
'EU-2': 'europe-west3'
};
const destinationRegion = regionToCloudRegion[region] || region;
var data = {
dst_region: destinationRegion
@@ -284,7 +284,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
toast.info("Sending request for changing region to " + region)
fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization?.id}/change/region/request`,{
fetch(`${globalUrl}/api/v1/orgs/${selectedOrganization?.id}/change/region/request`, {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -292,13 +292,13 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
credentials: "include",
body: JSON.stringify(data),
}).then((response)=>{
if(response.status !== 200){
}).then((response) => {
if (response.status !== 200) {
toast.error("Failed to send request for changing region. Please contact support@shuffler.io.")
}else{
toast.success("successfully send request for changing region. We will contact you shortly.")
} else {
toast.success("Successfully sent request for region change. We will process the move and contact you shortly.")
}
}).catch((err)=>{
}).catch((err) => {
console.log(err)
toast.error("Failed to send request for changing region. Please contact support@shuffler.io.")
})
@@ -307,13 +307,13 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
const setSelectedRegion = (region) => {
// send a POST request to /api/v1/orgs/{org_id}/region with the region as the body
if(region === "US") {
if (region === "US") {
region = "us-west2"
} else if(region === "EU") {
} else if (region === "EU") {
region = "europe-west2"
} else if(region === "CA") {
} else if (region === "CA") {
region = "northamerica-northeast1"
} else if(region === "UK") {
} else if (region === "UK") {
region = "europe-west2"
} else if (region === "EU-2") {
region = "europe-west3"
@@ -355,7 +355,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
const orgSaveButton = (
<Tooltip title="Save any unsaved data" placement="bottom">
<Button
style={{ width: 244, height: 51, display: 'flex', justifyContent:'center', textTransform: 'capitalize', padding: "16px, 24px, 16px, 24px", borderRadius: 4, backgroundColor: "#ff8544", color: "#1a1a1a", fontSize: 16, }}
style={{ width: 244, height: 51, display: 'flex', justifyContent: 'center', textTransform: 'capitalize', padding: "16px, 24px, 16px, 24px", borderRadius: 4, backgroundColor: "#ff8544", color: "#1a1a1a", fontSize: 16, }}
variant="contained"
color="primary"
disabled={
@@ -444,29 +444,29 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
<div style={{ flex: "3", color: "white" }}>
<div style={{ marginTop: 8, display: "flex" }} />
<div style={{ display: "flex" }}>
<div style={{width: "100%", maxWidth: 434, marginRight: 10}}>
<div style={{ width: "100%", maxWidth: 434, marginRight: 10 }}>
Name
<TextField
required
style={{
flex: "1",
display: "flex",
height: 35,
width: "100%",
maxWidth: 434,
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "#212121" : theme.palette.inputColor,
}}
fullWidth={true}
placeholder="Name"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={orgName}
onBlur={() => {
if((orgName !== selectedOrganization?.name) && (orgName !== "")) {
<TextField
required
style={{
flex: "1",
display: "flex",
height: 35,
width: "100%",
maxWidth: 434,
marginTop: "5px",
marginRight: "15px",
backgroundColor: isEditOrgTab ? "#212121" : theme.palette.inputColor,
}}
fullWidth={true}
placeholder="Name"
type="name"
id="standard-required"
margin="normal"
variant="outlined"
value={orgName}
onBlur={() => {
if ((orgName !== selectedOrganization?.name) && (orgName !== "")) {
handleEditOrg(
orgName,
orgDescription,
@@ -497,61 +497,62 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
auto_provision: selectedOrganization?.sso_config?.auto_provision,
}
)
}}}
onChange={(e) => {
if (e.target.value.length > 100) {
toast("Choose a shorter name.");
return;
}
}
}}
onChange={(e) => {
if (e.target.value.length > 100) {
toast("Choose a shorter name.");
return;
}
setOrgName(e.target.value);
}}
color="primary"
InputProps={{
style: {
color: "white",
height: "35px",
fontSize: "1em",
borderRadius: 4,
},
classes: {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
},
}}
/>
setOrgName(e.target.value);
}}
color="primary"
InputProps={{
style: {
color: "white",
height: "35px",
fontSize: "1em",
borderRadius: 4,
},
classes: {
notchedOutline: isEditOrgTab ? null : classes.notchedOutline,
},
}}
/>
</div>
{userdata?.support ? (
<div style={{ alignItems: 'center' }}>
<div style={{ marginRight: '12px', color: 'white' }}>Status</div>
<FormControl style={{ width: 220, height: 35 }}>
<Select
style={{ minWidth: 220, marginTop: 5, maxWidth: 220, height: 35, borderRadius: 4 }}
id="multiselect-status"
multiple
value={selectedStatus}
onChange={(event) => {handleStatusChange(event);setSelectedStatus(event.target.value)}}
input={<OutlinedInput />}
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps}
>
{["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "old customer", "old lead"].map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={selectedStatus.indexOf(name) > -1} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
): null}
<div style={{ marginRight: '12px', color: 'white' }}>Status</div>
<FormControl style={{ width: 220, height: 35 }}>
<Select
style={{ minWidth: 220, marginTop: 5, maxWidth: 220, height: 35, borderRadius: 4 }}
id="multiselect-status"
multiple
value={selectedStatus}
onChange={(event) => { handleStatusChange(event); setSelectedStatus(event.target.value) }}
input={<OutlinedInput />}
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps}
>
{["contacted", "lead", "demo done", "pov", "customer", "open source", "student", "internal", "creator", "tech partner", "old customer", "old lead"].map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={selectedStatus.indexOf(name) > -1} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
) : null}
{isCloud ? (
<div style={{ marginLeft: 13, fontSize: 16, color: "#9E9E9E" }} >
Region
<RegionChangeModal selectedOrganization={selectedOrganization} setSelectedRegion={setSelectedRegion} userdata={userdata} handleSendChangeRegionMail={handleSendChangeRegionMail}/>
</div>
): null}
Region
<RegionChangeModal selectedOrganization={selectedOrganization} setSelectedRegion={setSelectedRegion} userdata={userdata} handleSendChangeRegionMail={handleSendChangeRegionMail} />
</div>
) : null}
</div>
<div style={{ marginTop: "10px" }} />
About
@@ -575,38 +576,40 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
variant="outlined"
placeholder="A description for the organization"
value={orgDescription}
onBlur={() => {if((orgDescription !== selectedOrganization?.description) && (orgDescription !== "")) {
handleEditOrg(
orgName,
orgDescription,
selectedOrganization.id,
selectedOrganization.image,
{
app_download_repo: selectedOrganization?.defaults?.app_download_repo,
app_download_branch: selectedOrganization?.defaults?.app_download_branch,
workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo,
workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch,
notification_workflow: selectedOrganization?.defaults?.notification_workflow,
documentation_reference: selectedOrganization?.defaults?.documentation_reference,
workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo,
workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch,
workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username,
workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token,
newsletter: !newsletter,
weekly_recommendations: !weeklyRecommendations,
},
{
sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint,
sso_certificate: selectedOrganization?.sso_config?.sso_certificate,
client_id: selectedOrganization?.sso_config?.client_id,
client_secret: selectedOrganization?.sso_config?.client_secret,
openid_authorization: selectedOrganization?.sso_config?.openid_authorization,
openid_token: selectedOrganization?.sso_config?.openid_token,
SSORequired: selectedOrganization?.sso_config?.SSORequired,
auto_provision: selectedOrganization?.sso_config?.auto_provision,
}
)
}}}
onBlur={() => {
if ((orgDescription !== selectedOrganization?.description) && (orgDescription !== "")) {
handleEditOrg(
orgName,
orgDescription,
selectedOrganization.id,
selectedOrganization.image,
{
app_download_repo: selectedOrganization?.defaults?.app_download_repo,
app_download_branch: selectedOrganization?.defaults?.app_download_branch,
workflow_download_repo: selectedOrganization?.defaults?.workflow_download_repo,
workflow_download_branch: selectedOrganization?.defaults?.workflow_download_branch,
notification_workflow: selectedOrganization?.defaults?.notification_workflow,
documentation_reference: selectedOrganization?.defaults?.documentation_reference,
workflow_upload_repo: selectedOrganization?.defaults?.workflow_upload_repo,
workflow_upload_branch: selectedOrganization?.defaults?.workflow_upload_branch,
workflow_upload_username: selectedOrganization?.defaults?.workflow_upload_username,
workflow_upload_token: selectedOrganization?.defaults?.workflow_upload_token,
newsletter: !newsletter,
weekly_recommendations: !weeklyRecommendations,
},
{
sso_entrypoint: selectedOrganization?.sso_config?.sso_entrypoint,
sso_certificate: selectedOrganization?.sso_config?.sso_certificate,
client_id: selectedOrganization?.sso_config?.client_id,
client_secret: selectedOrganization?.sso_config?.client_secret,
openid_authorization: selectedOrganization?.sso_config?.openid_authorization,
openid_token: selectedOrganization?.sso_config?.openid_token,
SSORequired: selectedOrganization?.sso_config?.SSORequired,
auto_provision: selectedOrganization?.sso_config?.auto_provision,
}
)
}
}}
onChange={(e) => {
setOrgDescription(e.target.value);
}}
@@ -626,7 +629,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
</div>
</div>
<Typography variant="h5" style={{ color: "rgba(241, 241, 241, 1)", fontSize: 24, fontWeight: 600, marginTop: 40, textAlign: "left" }}>
<Typography variant="h5" style={{ color: "rgba(241, 241, 241, 1)", fontSize: 24, fontWeight: 600, marginTop: 40, textAlign: "left" }}>
Preferences
</Typography>
@@ -647,9 +650,9 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
</Grid>
<Grid item xs={12}>
<span>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Org Documentation reference</Typography>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Org Documentation reference</Typography>
<Typography variant="body2" color="textSecondary" style={{ fontWeight: 400, fontSize: 16, marginTop: 8 }}>
<Typography variant="body2" color="textSecondary" style={{ fontWeight: 400, fontSize: 16, marginTop: 8 }}>
Add a URL that is added as a link, pointing to any external documentation page you want.
</Typography>
@@ -672,7 +675,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
placeholder="Paste a URL to an external reference for this implementation"
value={documentationReference}
onBlur={() => {
if(documentationReference !== selectedOrganization?.defaults?.documentation_reference) {
if (documentationReference !== selectedOrganization?.defaults?.documentation_reference) {
handleEditOrg(
orgName,
orgDescription,
@@ -714,7 +717,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -728,16 +731,16 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
globalUrl={globalUrl}
userdata={userdata}
serverside={false}
/>
/>
<Grid item xs={12} style={{ marginTop: 20, }}>
<Typography variant="h4" style={{ textAlign: "left", color: "rgba(241, 241, 241, 1)", fontSize: 24, fontWeight: 600, }}>Workflow Backup Repository</Typography>
<Typography variant="body2" style={{ textAlign: "left", marginTop: 8, color: "#9E9E9E", fontSize: 16, fontWeight: 400 }}>
Decide where workflows are backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the repo root in the /orgId/workflow-status/workflowId.json format. <b>MSSP:</b> If suborg exists, this will automatically be applied for them as well (not retroactive). <a href="/docs/configuration#environment-variables" style={{textDecoration: "none", color: "#f86a3e"}} target="_blank">Credentials are encrypted.</a>
<Typography variant="h4" style={{ textAlign: "left", color: "rgba(241, 241, 241, 1)", fontSize: 24, fontWeight: 600, }}>Workflow Backup Repository</Typography>
<Typography variant="body2" style={{ textAlign: "left", marginTop: 8, color: "#9E9E9E", fontSize: 16, fontWeight: 400 }}>
Decide where workflows are backed up in a Git repository. Will create logs and notifications if upload fails. The repository and branch must already have been initialized. Files will show up in the repo root in the /orgId/workflow-status/workflowId.json format. <b>MSSP:</b> If suborg exists, this will automatically be applied for them as well (not retroactive). <a href="/docs/configuration#environment-variables" style={{ textDecoration: "none", color: "#f86a3e" }} target="_blank">Credentials are encrypted.</a>
</Typography>
<Grid container style={{ marginTop: 10, }} spacing={2}>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Repository for workflow backup</Typography>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Repository for workflow backup</Typography>
<TextField
required
style={{
@@ -765,7 +768,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -777,7 +780,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Branch</Typography>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Branch</Typography>
<TextField
required
style={{
@@ -805,7 +808,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -819,7 +822,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
<Grid container style={{ marginTop: 10, }} spacing={2}>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Username for backup of workflows</Typography>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Username for backup of workflows</Typography>
<TextField
required
style={{
@@ -847,7 +850,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -859,7 +862,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
</Grid>
<Grid item xs={6} style={{}}>
<span>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Git token/password</Typography>
<Typography style={{ fontWeight: 400, fontSize: 16 }}>Git token/password</Typography>
<TextField
required
style={{
@@ -886,7 +889,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -930,7 +933,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -970,7 +973,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -1010,7 +1013,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -1050,7 +1053,7 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
},
style: {
color: "white",
fontWeight: 400,
fontSize: 16,
borderRadius: 4,
@@ -1080,14 +1083,14 @@ const [orgName, setOrgName] = useState(selectedOrganization?.name);
export default OrgHeaderexpandedNew;
const RegionChangeModal = memo(({selectedOrganization, setSelectedRegion, userdata, handleSendChangeRegionMail}) => {
const RegionChangeModal = memo(({ selectedOrganization, setSelectedRegion, userdata, handleSendChangeRegionMail }) => {
// Show from options: "us-west2", "europe-west2", "europe-west3", "northamerica-northeast1"
// var regions = ["us-west2", "europe-west2", "europe-west3", "northamerica-northeast1"]
const regionMapping = {
"US": "us",
"EU-2": "eu",
"CA": "ca",
"UK": "gb"
"UK": "gb",
};
//let regiontag = "UK";
@@ -1095,6 +1098,7 @@ const RegionChangeModal = memo(({selectedOrganization, setSelectedRegion, userda
let regionCode = "gb";
const regionsplit = selectedOrganization?.region_url?.split(".");
if (regionsplit?.length > 2 && !regionsplit[0]?.includes("shuffler")) {
const namesplit = regionsplit[0]?.split("/");
regiontag = namesplit[namesplit.length - 1];
@@ -1103,58 +1107,59 @@ const RegionChangeModal = memo(({selectedOrganization, setSelectedRegion, userda
regiontag = "US";
regionCode = "us";
} else if (regiontag === "frankfurt") {
regiontag = "EU";
regiontag = "EU-2";
regionCode = "eu";
} else if (regiontag === "ca") {
regiontag = "CA";
regionCode = "ca";
}
}
return (
<FormControl style={{ display: "flex", flexDirection: "column", marginTop: 5, alignItems: "center" }} >
{/* <InputLabel id="demo-simple-select-label">Region</InputLabel> */}
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={regiontag}
style={{minWidth: 120, height: 35, borderRadius: 4 }}
onChange={(e) => {
if(userdata?.support){
setSelectedRegion(e.target.value)
}else{
handleSendChangeRegionMail(e.target.value)
}
}}
>
{Object.keys(regionMapping).map((region, index) => {
const regionImageCode = regionMapping[region];
// Set the default region if selectedOrganization.region is not set
if (selectedOrganization.region === undefined) {
selectedOrganization.region = "europe-west2";
}
// Check if the current region matches the selected region
if (region === selectedOrganization.region) {
// If the region matches, set the MenuItem as selected
return (
<MenuItem value={region} key={index} disabled>
{/* show region image through cdn */}
<img src={`https://flagcdn.com/48x36/${regionImageCode}.png`} alt={region} style={{ marginRight: 10 }} />
return (
<FormControl style={{ display: "flex", flexDirection: "column", marginTop: 5, alignItems: "center" }} >
{/* <InputLabel id="demo-simple-select-label">Region</InputLabel> */}
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={regiontag}
style={{ minWidth: 120, height: 35, borderRadius: 4 }}
onChange={(e) => {
if (userdata?.support) {
setSelectedRegion(e.target.value)
} else {
handleSendChangeRegionMail(e.target.value)
}
}}
>
{Object.keys(regionMapping).map((region, index) => {
const regionImageCode = regionMapping[region];
// Set the default region if selectedOrganization.region is not set
if (selectedOrganization.region === undefined) {
selectedOrganization.region = "europe-west2";
}
// Check if the current region matches the selected region
if (region === selectedOrganization.region) {
// If the region matches, set the MenuItem as selected
return (
<MenuItem value={region} key={index} disabled>
{/* show region image through cdn */}
<img src={`https://flagcdn.com/48x36/${regionImageCode}.png`} alt={region} style={{ marginRight: 10 }} />
{region}
</MenuItem>
);
} else {
return <MenuItem sx={{ display: 'flex' }} key={index} value={region}>
<img
src={`https://flagcdn.com/48x36/${regionImageCode}.png`}
alt={region}
style={{ marginRight: 10, width: 20, height: 18, }}
/>
{region}
</MenuItem>
);
} else {
return <MenuItem sx={{display: 'flex'}} key={index} value={region}>
<img
src={`https://flagcdn.com/48x36/${regionImageCode}.png`}
alt={region}
style={{ marginRight: 10, width: 20, height: 18,}}
/>
{region}
</MenuItem>;
}
})}
</Select>
</FormControl>
);
}
})}
</Select>
</FormControl>
);
})
+29 -5
View File
@@ -214,6 +214,20 @@ const ParsedAction = (props) => {
}
}, [expansionModalOpen])
/*
useEffect(() => {
// This will have the OLD selectedAction, not the new one huh?
// How do we map the fields correctly?
if (selectedAction === undefined || selectedAction === null) {
console.log("Selected action is undefined")
return
}
console.log("Selected action: ", selectedAction?.name, selectedAction)
}, [selectedAction])
*/
useEffect(() => {
// Changes the order of params to show in order:
// auth, required, optional
@@ -347,7 +361,7 @@ const ParsedAction = (props) => {
}
if (keyorder.join(",") !== newkeyorder.join(",")) {
//toast("KEYORDER CHANGED!")
console.log("KEYORDER CHANGED! DID ACTION AS WELL?", keyorder, newkeyorder)
setSelectedActionParameters(newparams)
selectedAction.parameters = newparams
@@ -872,7 +886,7 @@ const ParsedAction = (props) => {
}
}
return { ...param, value: paramvalue, error: message }
});
})
setSelectedActionParameters(newParameters)
setActionlist(newActionList)
@@ -3645,6 +3659,14 @@ const ParsedAction = (props) => {
);
if ((multiline === undefined || multiline === false) && ((data?.autocompleted === true || data?.field_active === true) || data.name.startsWith("${") && data.name.endsWith("}"))) {
multiline = true
}
if (data?.autocompleted === true || data?.field_active === true) {
rows = "1"
}
var datafield = (
<Tooltip
title={tooltipDescription}
@@ -3725,7 +3747,8 @@ const ParsedAction = (props) => {
setScrollConfig(scrollConfig)
}
}}
rows={data.name.startsWith("${") && data.name.endsWith("}") ? 2 : rows}
minRows={rows}
maxRows={6}
color="primary"
// defaultValue={data.value}
value={
@@ -4034,7 +4057,8 @@ const ParsedAction = (props) => {
helperText={returnHelperText(data.name, data.value)}
fullWidth
multiline={multiline}
rows={"3"}
minRows={3}
maxRows={6}
color="primary"
defaultValue={data.value}
type={"text"}
@@ -4580,7 +4604,7 @@ const ParsedAction = (props) => {
data.field_active === true ?
<Tooltip
color="primary"
title={"This is an Simplified field to make the body easier to use. NOT required according to the API documentation. If this is filled, it will be overridden in the Advanced Body"}
title={"This is a Simplified Field to make the body easier to use. NOT required according to the API documentation. If this is filled, it will be overridden in the Advanced Body"}
placement="top"
>
<PriorityHighIcon
+4 -19
View File
@@ -354,13 +354,13 @@ const RuntimeDebugger = (props) => {
var imageSource = "";
if (params?.row?.org?.id?.length > 0) {
if (params?.row?.org?.image?.length > 0){
imageSource = params.row.org.image
imageSource = params?.row.org?.image
}else {
imageSource = "/images/no_image.png"
}
}else {
if (userdata.active_org.image?.length > 0){
imageSource = userdata.active_org.image
imageSource = userdata?.active_org?.image
}else {
imageSource = "/images/no_image.png"
}
@@ -370,7 +370,7 @@ const RuntimeDebugger = (props) => {
<span style={{}} onClick={() => {
//setStatus(params.row.status)
}}>
{userdata?.active_org?.creator_org?.length === 0 ? (
{userdata?.active_org?.creator_org?.length === 0 && suborgWorkflowRuns ? (
<img src={imageSource} alt={source} style={{borderRadius: theme.palette?.borderRadius, height: imageSize, width: imageSize, }} />
) : null}
<Tooltip title={source} placement="top">
@@ -924,28 +924,13 @@ const RuntimeDebugger = (props) => {
onClick={() => setSearchQuery('')}
/>
)}
<button
type="button"
style={{
color: "#1A1A1A",
border: "none",
padding: "10px 20px",
width: 100,
height: 35,
borderRadius: 4,
backgroundColor: "#FF8544",
cursor: "pointer",
}}
>
Search
</button>
</InputAdornment>
),
}}
onChange={(e)=>{handleQueryChange(e)}}
color="primary"
placeholder="Filter by Workflow Name, Status, Execution Argument, Results.."
placeholder="Filter by Workflow Name, Status, Execution Argument, Results"
id="shuffle_search_field"
/>
</div>
+6
View File
@@ -109,6 +109,12 @@ const SearchData = props => {
}
}, [searchOpen]);
useEffect(() => {
if (currentRefinement !== inputValue) {
refine(inputValue);
}
}, [currentRefinement]);
return (
<form id="search_form" noValidate type="searchbox" action="" role="search" onClick={() => {
@@ -27,6 +27,7 @@ import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx";
import Draggable from "react-draggable";
import {
Storage as StorageIcon,
FullscreenExit as FullscreenExitIcon,
Extension as ExtensionIcon,
Apps as AppsIcon,
@@ -90,7 +91,7 @@ const pythonFilters = [
{ "name": "Using Shuffle variables", "value": `import json\nnodevalue = r\"\"\"$exec\"\"\"\nif not nodevalue:\n nodevalue = r\"\"\"{\"sample\": \"string\", \"int\": 1}\"\"\"\n \njsondata = json.loads(nodevalue)\nprint(jsondata)`, "example": `` },
{ "name": "Print Execution ID", "value": `print(self.current_execution_id)`, "example": `` },
{ "name": "Get full execution details", "value": `print(self.full_execution)`, "example": `` },
{ "name": "Use files", "value": `# Create a sample file\nfiles = [{\n \"name\": \"test.txt\",\n \"data\": \"Testdata\"\n}]\nret = self.set_files(files)\n\n# Get the content of the file from Shuffle storage\n# Originally a byte string in the \"data\" key\nfile_content = (self.get_file(ret[0])[\"data\"]).decode()\nprint(file_content)`, "example": `` },
{ "name": "Use files", "value": `# Create a sample file\nfiles = [{\n \"filename\": \"test.txt\",\n \"data\": \"Testdata\"\n}]\nret = self.set_files(files)\n\n# Get the content of the file from Shuffle storage\n# Originally a byte string in the \"data\" key\nfile_content = (self.get_file(ret[0])[\"data\"]).decode()\nprint(file_content)`, "example": `` },
{ "name": "Use datastore", "value": `key = \"testkey\"\nvalue = \"The value of the testkey\"\n\nself.set_cache(key, value)\n\n# Print the details of the key after it's been updated\n# To get the value, use self.get_cache(key)[\"value\"]\nprint(self.get_cache(key))`, "example": `` },
{ "name": "Run an App Action", "value": `response = shuffle.run_app(app_id="app", action="action_name", auth="authentication_id", params={})\nprint(response)`, "example": ``, "disabled": true, },
@@ -1805,9 +1806,11 @@ const CodeEditor = (props) => {
) : innerdata.type === "workflow_variable" ||
innerdata.type === "execution_variable" ? (
<FavoriteBorderIcon style={{ marginRight: 10 }} />
) : (
) :
innerdata.type === "Shuffle DB" ?
<StorageIcon style={{ marginRight: 10, }} />
:
<ScheduleIcon style={{ marginRight: 10 }} />
);
const handleExecArgumentHover = (inside) => {
var exec_text_field = document.getElementById(
@@ -1877,8 +1880,6 @@ const CodeEditor = (props) => {
menuPosition1.left = 0
}
//console.log("POS1: ", menuPosition1)
return parsedPaths.length > 0 ? (
<NestedMenuItem
key={innerdata.name}
+11 -1
View File
@@ -1335,7 +1335,7 @@ const UserManagmentTab = memo((props) => {
}}
>
<ListItem style={{ width: "100%", padding: "10px 10px 10px 0px", verticalAlign: 'middle', borderBottom: "1px solid #494949", display: "table-row" }}>
{["Username", "API Key", "Role", "Active", "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => (
{["Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => (
<ListItemText
key={index}
primary={header}
@@ -1458,6 +1458,8 @@ const UserManagmentTab = memo((props) => {
}}
style={{display:'table-cell', verticalAlign: 'middle' }}
/>
{/*
<ListItemText
style={{
textAlign: "center",
@@ -1488,6 +1490,8 @@ const UserManagmentTab = memo((props) => {
)
}
/>
*/}
<ListItemText
primary={
<Select
@@ -1552,10 +1556,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={
data.login_type === undefined ||
@@ -1566,6 +1572,8 @@ const UserManagmentTab = memo((props) => {
}
style={{ display:'table-cell',verticalAlign: 'middle', padding: "8px", }}
/>
{/*
<ListItemText
primary={
data?.mfa_info !== undefined &&
@@ -1576,6 +1584,8 @@ 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 &&
selectedOrganization?.child_orgs?.length > 0 ? (
@@ -28,7 +28,16 @@ import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import theme from "../theme.jsx";
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) {
return []
}
@@ -81,8 +90,7 @@ export const getParentNodes = (workflow, action) => {
continue;
}
// FIXME: This part is only handling first level,
// but needs to recurse
// FIXME: recursion
var incomingEdges = []
for (var branchkey in workflow.branches) {
const branch = workflow.branches[branchkey]
@@ -90,13 +98,19 @@ export const getParentNodes = (workflow, action) => {
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, {
id: branch.source_id,
})
}, count+1)
if (parents.length > 0) {
incomingEdges = incomingEdges.concat(parents)
}
*/
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) {
const parentfound = parents.find((element) => element.id === startnodeId)
if (parentfound !== undefined) {
+291 -199
View File
@@ -574,7 +574,7 @@ const AngularWorkflow = (defaultprops) => {
}, [editWorkflowModalOpen])
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
setSelectedTriggerValue(selectedTrigger?.parameters[1]?.value)
}
@@ -1203,18 +1203,15 @@ const AngularWorkflow = (defaultprops) => {
if (workflowExecutions.length > 0) {
// Look for the ID
for (let execkey in workflowExecutions) {
if (
workflowExecutions[execkey].results === undefined ||
workflowExecutions[execkey].results === null
) {
continue;
if (workflowExecutions[execkey].results === undefined || workflowExecutions[execkey].results === null) {
continue
}
var foundResult = workflowExecutions[execkey].results.find(
(result) => result.action.id === item.id
);
)
if (foundResult === undefined) {
continue;
continue
}
const validated = validateJson(foundResult.result)
@@ -4863,6 +4860,177 @@ const AngularWorkflow = (defaultprops) => {
})
};
// Check if it already has any non-decorator branches attached to it
const findClosestNode = (event, nodedata) => {
if (cy === undefined || cy === null) {
console.log("Cy is undefined or null")
return
}
if (event === undefined || event === null) {
console.log("Event is undefined or null")
return
}
if (event.target === undefined || event.target === null) {
console.log("Event target is undefined or null")
return
}
if (!((nodedata?.trigger_type === "SUBFLOW" || nodedata?.trigger_type === "USERINPUT" || nodedata?.type === "ACTION") && !nodedata?.isStartNode)) {
//console.log("Not a valid node to find closest node for")
return
}
if (nodedata.finished === false) {
//console.log("Node is not finished")
return
}
const branches = cy.elements('edge').jsons()
var branchFound = false
var decoratorNodeIds = []
var decoratorIds = []
for (var branchkey in branches) {
if (branches[branchkey].data.source === nodedata.id || branches[branchkey].data.target === nodedata.id) {
decoratorIds.push(branches[branchkey].data.id)
if (branches[branchkey].data.decorator === true) {
// Add the source/destination
if (branches[branchkey].data.source === nodedata.id) {
decoratorNodeIds.push(branches[branchkey].data.target)
} else {
decoratorNodeIds.push(branches[branchkey].data.source)
}
continue
}
//branchFound = true
//break
}
}
if (!branchFound) {
var relevantNodes = []
const minDistance = 185
const draggedNode = event.target
const allnodes = cy.nodes().jsons()
for (var nodekey in allnodes) {
const node = allnodes[nodekey]
if (node.data.id === nodedata.id) {
continue
}
// Decorators
if (node.data.attachedTo !== undefined) {
continue
}
if (node.position === undefined || node.position === null || node.position.x === undefined || node.position.y === undefined) {
continue
}
if (node.data.type !== "ACTION" && node.data.type !== "TRIGGER") {
continue
}
const distance = Math.sqrt(
Math.pow(draggedNode.position('x') - node.position.x, 2) +
Math.pow(draggedNode.position('y') - node.position.y, 2)
)
if (decoratorNodeIds.includes(node.data.id)) {
// Drag a little farther to remove it
if (distance > minDistance + 75) {
// Remove the branch? Why?
const edgeToRemove = cy.getElementById(branches[branchkey].data.id)
if (edgeToRemove !== null && edgeToRemove !== undefined) {
//console.log("Removing edge: ", edgeToRemove)
edgeToRemove.remove()
break
}
}
}
if (distance < minDistance) {
relevantNodes.push(node)
//minDistance = distance
//closestNode = node
}
}
for (var key in relevantNodes) {
const closestNode = relevantNodes[key]
if (closestNode.data.app_name === "Webhook" || closestNode.data.app_name === "Schedule") {
return
}
// Checks if the branch already exists between the nodes
if (decoratorIds.length > 0) {
var foundBranch = false
for (var decoratorkey in decoratorIds) {
const decoratorEdge = cy.getElementById(decoratorIds[decoratorkey])
if (decoratorEdge === null || decoratorEdge === undefined) {
continue
}
// Check if source and destination exists with a branch
const sourceId = decoratorEdge.data("source")
const targetId = decoratorEdge.data("target")
if ((sourceId === closestNode.data.id && targetId === nodedata.id) || (sourceId === nodedata.id && targetId === closestNode.data.id)) {
foundBranch = true
break
}
}
if (foundBranch) {
continue
}
}
const newId = uuidv4()
cy.add({
group: "edges",
data: {
decorator: true,
id: newId,
_id: newId,
source: closestNode.data.id,
target: nodedata.id,
label: releaseToConnectLabel,
conditions: [],
}
})
}
}
/*
// FIXME: This is the start of a highlighter for the node
// to better match it up with other elements
// 1. Get current node's position in X/Y on the screen
// 2. Draw a red line on the X and Y axis for positioning
// Draw a red div line in the HTML
const position = event.target.position()
const redline = document.getElementById("redline")
if (redline !== null && redline !== undefined) {
redline.style.display = "block"
redline.style.position = "absolute"
redline.style.left = position.x + "px"
redline.style.top = position.y + "px"
redline.style.height = "10000px"
redline.style.width = 1
console.log("REDLINE!")
}
*/
}
const onNodeDrag = (event, selectedAction) => {
const nodedata = event.target.data();
@@ -4906,161 +5074,9 @@ const AngularWorkflow = (defaultprops) => {
return;
}
// Finds closest partner to show edge to connect to
if ((nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT" || nodedata.type === "ACTION") && !nodedata.isStartNode) {
// Check if it already has any non-decorator branches attached to it
const branches = cy.elements('edge').jsons()
var branchFound = false
var decoratorIds = []
for (var branchkey in branches) {
if (branches[branchkey].data.source === nodedata.id || branches[branchkey].data.target === nodedata.id) {
if (branches[branchkey].data.decorator === true) {
// Add the source/destination
if (branches[branchkey].data.source === nodedata.id) {
decoratorIds.push(branches[branchkey].data.target)
} else {
decoratorIds.push(branches[branchkey].data.source)
}
continue
}
branchFound = true
break
}
}
if (!branchFound) {
//console.log("Found action during drag. Checking closest nodes as it doesn't have a valid branch")
var closestNode = null
var minDistance = 300
const draggedNode = event.target
const allnodes = cy.nodes().jsons()
for (var nodekey in allnodes) {
const node = allnodes[nodekey]
if (node.data.id === nodedata.id) {
continue
}
// Decorators
if (node.data.attachedTo !== undefined) {
continue
}
if (node.position === undefined || node.position === null || node.position.x === undefined || node.position.y === undefined) {
continue
}
if (node.data.type !== "ACTION" && node.data.type !== "TRIGGER") {
continue
}
const distance = Math.sqrt(
Math.pow(draggedNode.position('x') - node.position.x, 2) +
Math.pow(draggedNode.position('y') - node.position.y, 2)
)
if (decoratorIds.includes(node.data.id)) {
//console.log("Found existing decorator for: ", node.data.app_name, "Distance: ", distance)
if (distance > 300) {
// Remove the branch
const edgeToRemove = cy.getElementById(branches[branchkey].data.id)
if (edgeToRemove !== null && edgeToRemove !== undefined) {
//console.log("Removing edge: ", edgeToRemove)
edgeToRemove.remove()
//decoratorIds.splice(decoratorIds.indexOf(node.data.id), 1)
break
}
}
}
if (distance < minDistance) {
minDistance = distance
closestNode = node
}
}
if (closestNode !== null && closestNode !== undefined) {
if (closestNode.data.app_name === "Webhook" || closestNode.data.app_name === "Schedule") {
return
}
//console.log("Closest node app: ", closestNode.data.app_name, "Distance: ", minDistance)
/*
if (decoratorIds.length > 0) {
console.log("Decorators already exists. If within distance of 15 add to existing, otherwise remove old and add new: ", decoratorIds)
for (var decoratorkey in decoratorIds) {
const decoratorEdge = cy.getElementById(decoratorIds[decoratorkey])
if (decoratorEdge === null || decoratorEdge === undefined) {
continue
}
const sourceNode = cy.getElementById(decoratorEdge.data.source)
const targetNode = cy.getElementById(decoratorEdge.data.target)
const distance = Math.sqrt(
Math.pow(draggedNode.position('x') - sourceNode.position('x'), 2) +
Math.pow(draggedNode.position('y') - sourceNode.position('y'), 2)
)
// Check plus minus 15 in distance from mindistance
if (distance > minDistance - 15 && distance < minDistance + 15) {
console.log("Within distance of 15, add to existing edge")
} else {
console.log("Outside distance of 15, remove old edge and add new")
}
}
}
*/
if (decoratorIds.length === 0) {
//const edgeCurve = calculateEdgeCurve(draggedNode.position(), closestNode.position)
//currentedge.style('control-point-distance', edgeCurve.distance)
//currentedge.style('control-point-weight', edgeCurve.weight)
const newId = uuidv4()
cy.add({
group: "edges",
data: {
decorator: true,
id: newId,
_id: newId,
source: closestNode.data.id,
target: nodedata.id,
label: releaseToConnectLabel,
conditions: [],
}
})
}
}
}
/*
// FIXME: This is the start of a highlighter for the node
// to better match it up with other elements
// 1. Get current node's position in X/Y on the screen
// 2. Draw a red line on the X and Y axis for positioning
// Draw a red div line in the HTML
const position = event.target.position()
const redline = document.getElementById("redline")
if (redline !== null && redline !== undefined) {
redline.style.display = "block"
redline.style.position = "absolute"
redline.style.left = position.x + "px"
redline.style.top = position.y + "px"
redline.style.height = "10000px"
redline.style.width = 1
console.log("REDLINE!")
}
*/
findClosestNode(event, nodedata)
}
if (originalLocation.x === 0 && originalLocation.y === 0 && nodedata.position !== undefined) {
@@ -5222,7 +5238,9 @@ const AngularWorkflow = (defaultprops) => {
name: item.label,
autocomplete: itemlabelComplete,
example: exampledata,
};
}
console.log("VALUE: ", actionvalue)
actionlist.push(actionvalue);
}
@@ -5463,6 +5481,7 @@ const AngularWorkflow = (defaultprops) => {
// https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
// onNodeClick
const onNodeSelect = (event, newAppAuth) => {
// Forces all states to update at the same time,
// Otherwise everything is SUPER slow
@@ -5636,7 +5655,14 @@ const AngularWorkflow = (defaultprops) => {
}
ReactDOM.unstable_batchedUpdates(() => {
const selectedNodes = cy.$(':selected')
if (data.isButton) {
if (selectedNodes?.length > 1) {
event.target.unselect()
//console.log(": ", selectedNodes.length)
return
}
if (data.buttonType === "suggestion") {
if (cy === undefined) {
console.log("Cy not defined yet")
@@ -5736,7 +5762,6 @@ const AngularWorkflow = (defaultprops) => {
return
} else if (data.buttonType === "copy") {
console.log("COPY!");
// 1. Find parent
// 2. Find branches for parent
@@ -5877,6 +5902,14 @@ const AngularWorkflow = (defaultprops) => {
}
if (data.type === "ACTION") {
if (selectedNodes?.length > 1) {
console.log("Unselecting ACTION due to multiple nodes selected")
setSelectedAction({})
setSelectedApp({})
setSelectedComment({})
return
}
setSelectedComment({})
// FIXME: is this what is mapping it an actual action in the workflow? wtf?
@@ -6198,8 +6231,10 @@ const AngularWorkflow = (defaultprops) => {
}
}
setSelectedApp(curapp)
setSelectedAction(curaction)
setTimeout(() => {
setSelectedApp(curapp)
setSelectedAction(curaction)
}, 50)
cy.removeListener("drag");
cy.removeListener("free");
@@ -6222,6 +6257,14 @@ const AngularWorkflow = (defaultprops) => {
setSelectedActionEnvironment(env);
}
} else if (data.type === "TRIGGER") {
if (selectedNodes?.length > 1) {
console.log("Unselecting ACTION due to multiple nodes selected")
setSelectedAction({})
setSelectedApp({})
setSelectedComment({})
return
}
setSelectedComment({})
if (workflow.triggers === null) {
workflow.triggers = []
@@ -6423,6 +6466,14 @@ const AngularWorkflow = (defaultprops) => {
//setSelectedActionEnvironment(data.env)
}, 25)
} else if (data.type === "COMMENT") {
if (selectedNodes?.length > 1) {
console.log("Unselecting ACTION due to multiple nodes selected")
setSelectedAction({})
setSelectedApp({})
setSelectedComment({})
return
}
setSelectedComment(data);
} else {
toast("Can't handle node type " + data.type);
@@ -7431,26 +7482,21 @@ const AngularWorkflow = (defaultprops) => {
};
const handlePaste = (event) => {
//console.log("EV: ", event)
if (
event.path !== undefined &&
event.path !== null &&
event.path.length > 0
) {
//console.log("PATH: ", event.path[0])
if (event.path[0].localName !== "body") {
//console.log("Skipping because body is not targeted")
return;
}
}
//console.log("PATH2: ", event.target)
if (
event.target !== undefined &&
event.target !== null
) {
if (event.target.localName !== "body") {
//console.log("Skipping because body is not targeted")
return;
}
}
@@ -7460,12 +7506,10 @@ const AngularWorkflow = (defaultprops) => {
const clipboard = (event.originalEvent || event).clipboardData.getData(
"text/plain"
);
//console.log("Text: ", clipboard)
//window.document.execCommand('insertText', false, text);
//
try {
const allnodes = cy.nodes().jsons()
var parsedjson = JSON.parse(clipboard);
// Check if array
if (!Array.isArray(parsedjson)) {
console.log("Not array! Adding to array.")
parsedjson = [parsedjson]
@@ -7473,7 +7517,6 @@ const AngularWorkflow = (defaultprops) => {
for (let jsonkey in parsedjson) {
var item = parsedjson[jsonkey];
console.log("Adding: ", item);
if (item.data === undefined || item.data === null) {
console.log("Appending from here")
@@ -7493,13 +7536,31 @@ const AngularWorkflow = (defaultprops) => {
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()
cy.add({
group: item.group,
data: item.data,
position: item.position,
});
position: {
x: item.position.x+20,
y: item.position.y+20,
},
})
}
} catch (e) {
console.log("Error pasting: ", e);
@@ -9572,7 +9633,12 @@ const AngularWorkflow = (defaultprops) => {
console.log("Error fitting cytoscape (4): ", error)
}
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")) {
e.target.unselect();
}
@@ -9582,10 +9648,15 @@ const AngularWorkflow = (defaultprops) => {
cy.on("boxstart", (e) => {
console.log("START");
e.preventDefault()
e.stopPropagation()
});
cy.on("boxend", (e) => {
console.log("END: ", cy)
e.preventDefault()
e.stopPropagation()
console.log("END: ", e.target, cy)
var cydata = cy.$(":selected").jsons();
if (cydata !== undefined && cydata !== null && cydata.length > 0) {
// Unselect all nodes
@@ -10658,6 +10729,7 @@ const AngularWorkflow = (defaultprops) => {
return
}
// HTML -> Canvas overlap check
if (
e.pageX > cycontainer.offsetLeft
@@ -10670,6 +10742,9 @@ const AngularWorkflow = (defaultprops) => {
return
}
findClosestNode({
target: currentnode,
}, currentnode.data())
currentnode[0].renderedPosition("x", e.pageX - cycontainer.offsetLeft)
currentnode[0].renderedPosition("y", e.pageY - cycontainer.offsetTop)
@@ -17381,11 +17456,11 @@ const AngularWorkflow = (defaultprops) => {
selectedTrigger.status === "running"
}
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"
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) => {
setTriggerCronWrapper(e.target.value);
@@ -17734,10 +17809,24 @@ const AngularWorkflow = (defaultprops) => {
onChange={(e) => {
if (lastSaved === false && originalWorkflow.id === workflow.id) {
setSuborgWorkflows([])
saveWorkflow(workflow, undefined, undefined, e.target.value)
toast.warn(`Saving workflow first due to detected changes. If more than 10 auth`, {
/* Standard re-loads */
setAllTriggers(undefined)
setSelectedTriggerIndex(-1)
getEnvironments(e.target.value)
getAppAuthentication(undefined, undefined, undefined, e.target.value)
getFiles(e.target.value)
listOrgCache(e.target.value)
/* Standard re-loads */
toast.warn(`Saving workflow first due to detected changes.`, {
autoClose: 2000,
})
return
}
@@ -17761,12 +17850,14 @@ const AngularWorkflow = (defaultprops) => {
}
ReactDOM.unstable_batchedUpdates(() => {
/* Standard re-loads */
setAllTriggers(undefined)
setSelectedTriggerIndex(-1)
getEnvironments(e.target.value)
getAppAuthentication(undefined, undefined, undefined, e.target.value)
getFiles(e.target.value)
listOrgCache(e.target.value)
/* Standard re-loads */
// Reset the save button to ensure random saves don't occur during move
setLastSaved(true)
@@ -19035,7 +19126,7 @@ const AngularWorkflow = (defaultprops) => {
{workflow.public || userdata.support == true ?
<Tooltip
color="secondary"
title="Download public workflow"
title="Download workflow"
placement="top-start"
>
<span>
@@ -20038,7 +20129,8 @@ const AngularWorkflow = (defaultprops) => {
}
}
to_be_copied.replaceAll(" ", "_");
to_be_copied = to_be_copied.replaceAll(" ", "_");
console.log("COPY: ", to_be_copied);
const elementName = "copy_element_shuffle";
var copyText = document.getElementById(elementName);
if (copyText !== null && copyText !== undefined) {
@@ -21562,19 +21654,19 @@ const AngularWorkflow = (defaultprops) => {
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")) {
return (
<div style={{ maxWidth: 600, marginTop: 15, overflowX: "hidden", }}>
<Typography
variant="body1"
style={{}}
>
<b>Action Logs</b>
</Typography>
<Typography variant="body2" style={{ whiteSpace: 'pre-line', }}>
Logs for an 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>
</div>
)
return (
<div style={{ maxWidth: 600, marginTop: 75, overflowX: "hidden", }}>
<Typography
variant="body1"
style={{}}
>
<b>Action Logs</b>
</Typography>
<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>
</div>
)
}
var showlink = false
@@ -22139,9 +22231,9 @@ const AngularWorkflow = (defaultprops) => {
variant="h6"
style={{ marginBottom: 0, marginTop: 0 }}
>
Variables <span style={{ fontSize: 10 }}>(click to expand)</span>
Variable & Debug info <span style={{ fontSize: 10 }}>({selectedResult?.action?.parameters?.length})</span>
</Typography>
{selectedResult.action.parameters.map((data, index) => {
{selectedResult?.action?.parameters?.map((data, index) => {
if (data.value.length === 0) {
return null;
}
+7 -4
View File
@@ -176,16 +176,18 @@ export const GetParsedPaths = (inputdata, basekey) => {
}
if (typeof inputdata !== "object") {
return parsedValues;
return parsedValues
}
for (const [key, value] of Object.entries(inputdata)) {
for (var [key, value] of Object.entries(inputdata)) {
key = key.replaceAll(" ", "_")
// Check if loop or JSON
const extra = basekey.length > 0 ? splitkey : "";
const basekeyname = `${basekey
.slice(1, basekey.length)
.split(".")
.join(splitkey)}${extra}${key}`;
.join(splitkey)}${extra}${key}`
// Handle direct loop!
if (!isNaN(key) && basekey === "") {
@@ -205,7 +207,8 @@ export const GetParsedPaths = (inputdata, basekey) => {
type: "list",
name: `${splitkey}list`,
autocomplete: `${basekey.replaceAll(" ", "_")}.#`,
});
})
const returnValues = GetParsedPaths(value, `${basekey}.#`);
for (var subkey in returnValues) {
parsedValues.push(returnValues[subkey]);
+29 -8
View File
@@ -49,7 +49,7 @@ const searchClient = algoliasearch(
);
// AppCard Component
const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick, leftSideBarOpenByClick, userdata, fetchApps }) => {
const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl, deactivatedIndexes, currTab, handleAppClick, leftSideBarOpenByClick, userdata, fetchApps, appsToShow, setAppsToShow, setUserApps, }) => {
const navigate = useNavigate();
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" || window.location.host === "localhost:3000";
const appUrl = isCloud ? `/apps/${data.id}` : `https://shuffler.io/apps/${data.id}`;
@@ -223,6 +223,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
)
}
<Button
disabled={data?.reference_org === userdata?.active_org?.id}
className="deactivate-button"
sx={{
width: 110,
@@ -244,7 +245,7 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
event.preventDefault();
event.stopPropagation();
const url = `${globalUrl}/api/v1/apps/${data.id}/deactivate`;
toast("Deactivating app. Please wait...");
//toast("Deactivating app. Please wait...");
fetch(url, {
method: 'GET',
headers: {
@@ -256,11 +257,26 @@ const AppCard = ({ data, index, mouseHoverIndex, setMouseHoverIndex, globalUrl,
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.success === false) {
toast.error(responseJson.reason);
if (responseJson?.reason !== undefined && responseJson?.reason !== null && responseJson?.reason !== "") {
toast.error(responseJson.reason);
} else {
toast.error("Failed to deactivate app. Please try again later.")
}
} else {
toast.success("App Deactivated Successfully.");
fetchApps();
}
toast.success("App deactivated successfully. Will take effect on refresh..")
/*
// This somehow didn't work
if (appsToShow !== undefined && appsToShow !== null && appsToShow.length > 0) {
const newApps = appsToShow?.filter((app) => app.id !== data.id)
if (newApps !== undefined && newApps !== null && newApps.length > 0) {
setAppsToShow(newApps)
setUserApps(newApps)
}
}
}
*/
}
})
.catch(error => {
console.log("app error: ", error.toString());
@@ -1860,8 +1876,6 @@ const Apps2 = (props) => {
color: "#FF8544"
}
console.log("User", userdata)
return (
<div style={{ paddingTop: 70, paddingLeft: leftSideBarOpenByClick ? 200 : 0, transition: "padding-left 0.3s ease", backgroundColor: "#1A1A1A", fontFamily: theme?.typography?.fontFamily, zoom: 0.7, }}>
<InstantSearch searchClient={searchClient} indexName="appsearch">
@@ -2247,6 +2261,10 @@ const Apps2 = (props) => {
leftSideBarOpenByClick={leftSideBarOpenByClick}
userdata={userdata}
fetchApps={fetchApps}
setUserApps={setUserApps}
appsToShow={appsToShow}
setAppsToShow={setAppsToShow}
/>
))}
</div>
@@ -2296,6 +2314,9 @@ const Apps2 = (props) => {
<AppCard key={index} data={data} index={index} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} globalUrl={globalUrl} deactivatedIndexes={deactivatedIndexes} currTab={currTab} userdata={userdata}
handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick}
fetchApps={fetchApps}
setUserApps={setUserApps}
appsToShow={appsToShow}
setAppsToShow={setAppsToShow}
/>
))}
</div>
+18
View File
@@ -77,6 +77,24 @@ const RunWorkflow = (defaultprops) => {
const [boxWidth, setBoxWidth] = React.useState(500)
const [inputQuestions, setInputQuestions] = React.useState([])
useEffect(() => {
if (workflow === undefined || workflow === null || Object.keys(workflow).length === 0) {
return
}
if (workflow.input_questions === undefined || workflow.input_questions === null) {
return
}
// Checks if it's a user input-node based or not
if ((answer !== undefined && answer !== null) || (foundSourcenode !== undefined && foundSourcenode !== null)) {
} else {
setInputQuestions(workflow.input_questions)
setUpdate(Math.random())
}
}, [workflow])
const IframeWrapper = (props) => {
var propsCopy = JSON.parse(JSON.stringify(props))
propsCopy.width = 400
+1
View File
@@ -447,6 +447,7 @@ const Welcome = (props) => {
}
navigate("/welcome?tab=2")
setActiveStep(1)
setShowWelcome(true)
}}>
<CardActionArea style={actionObject}>
+14 -2
View File
@@ -553,6 +553,10 @@ export const validateJson = (showResult) => {
// Check fields if they can be parsed too
try {
for (const [key, value] of Object.entries(result)) {
if (typeof value === "string") {
value = value.replaceAll(" ", "_")
}
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
//console.log("CHECKING STRING: ", value)
@@ -573,6 +577,10 @@ export const validateJson = (showResult) => {
// Usually only reaches here if raw array > dict > value
if (typeof showResult !== "array") {
for (const [subkey, subvalue] of Object.entries(value)) {
if (typeof subvalue === "string") {
subvalue = subvalue.replaceAll(" ", "_")
}
if (typeof subvalue === "string" && (subvalue.startsWith("{") || subvalue.startsWith("["))) {
const inside_result = validateJson(subvalue)
if (inside_result.valid) {
@@ -1841,9 +1849,13 @@ const Workflows = (props) => {
var parsedworkflows = [];
for (var key in newSubflows) {
if (key === data.id) {
continue
}
const foundWorkflow = workflows.find(
(workflow) => workflow.id === newSubflows[key]
);
)
if (foundWorkflow !== undefined && foundWorkflow !== null) {
parsedworkflows.push(foundWorkflow);
}
@@ -1854,7 +1866,7 @@ const Workflows = (props) => {
"Appending subflows during export: ",
parsedworkflows.length
);
data.subflows = parsedworkflows;
data.subflows = parsedworkflows
}
}
+4 -1
View File
@@ -1935,6 +1935,10 @@ const Workflows2 = (props) => {
var parsedworkflows = [];
for (var key in newSubflows) {
if (key === data.id) {
continue
}
const foundWorkflow = workflows.find(
(workflow) => workflow.id === newSubflows[key]
);
@@ -2362,7 +2366,6 @@ const Workflows2 = (props) => {
<MenuItem
style={{ backgroundColor: theme.palette.inputColor, color: "white" }}
disabled={isDistributed}
onClick={() => {
setDeleteModalOpen(true);
setSelectedWorkflowId(data.id);