Merge branch 'Shuffle:1.4.0' into 1.4.0

This commit is contained in:
Hari Krishna
2024-02-23 02:01:56 -08:00
committed by GitHub
22 changed files with 1051 additions and 408 deletions
+141 -122
View File
@@ -40,12 +40,12 @@ cytoscape.use(edgehandles);
export const findSpecificApp = (framework, inputcategory) => {
// Get the frameworkinfo for the org and fill in
if (framework === undefined || framework === null) {
console.log("findSpecificApp: framework is null")
//console.log("findSpecificApp: framework is null")
return null
}
if (inputcategory === undefined || inputcategory === null) {
console.log("findSpecificApp: category is null")
//console.log("findSpecificApp: category is null")
return null
}
@@ -118,8 +118,8 @@ export const findSpecificApp = (framework, inputcategory) => {
}
return {
name: "EDR :default",
large_image: parsedDatatypeImages["EDR & AV"],
name: "IAM :default",
large_image: parsedDatatypeImages["IAM"],
count: 0,
description: "",
id: "",
@@ -654,7 +654,7 @@ const AppFramework = (props) => {
const handleLoadNextSuggestion = (frameworkData) => {
console.log("Should check for next apps to load from App suggestion model")
//console.log("Should check for next apps to load from App suggestion model")
//fetch(globalUrl + "/api/v1/workflows/usecases", {
//credentials: "include",
//cors: "no-cors",
@@ -720,7 +720,7 @@ const AppFramework = (props) => {
}
const showRecommendations = (changed, frameworkData) => {
console.log("Inside recommendation loader")
//console.log("Inside recommendation loader")
setChangedApp(changed)
handleLoadNextSuggestion(frameworkData)
@@ -761,7 +761,7 @@ const AppFramework = (props) => {
//console.log("APptype, changed, framework: ", apptype.toLowerCase(), alternativeChanged.toLowerCase(), changed.toLowerCase(), frameworkData)
if (changed.toLowerCase() === apptype.toLowerCase() || changed.toLowerCase().includes(apptype.toLowerCase()) || alternativeChanged.toLowerCase() === apptype.toLowerCase() || alternativeChanged.toLowerCase().includes(apptype.toLowerCase())) {
potential = true
console.log("Potential: !", apptype)
//console.log("Potential: !", apptype)
if (frameworkData[apptype] !== undefined && frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) {
usecase.items[itemtype].app = frameworkData[apptype]
@@ -819,10 +819,10 @@ const AppFramework = (props) => {
continue
}
} else {
console.log("No usecase to try to match it to (usecase.usecase_references in UsecaseSearch)")
//console.log("No usecase to try to match it to (usecase.usecase_references in UsecaseSearch)")
}
console.log("Usecase: ", usecase)
//console.log("Usecase: ", usecase)
if (matches.length === usecase.items.length) {
usecase.color = "#c51152"
usecase.type = usecaseTypes[key].name
@@ -833,8 +833,8 @@ const AppFramework = (props) => {
}
// FIXME: Check if a usecase has already been handled
console.log("")
console.log("GOT USECASES: ", showusecases)
//console.log("")
//console.log("GOT USECASES: ", showusecases)
// FIXME: Just showing one usecase at a time for now
if (showusecases.length > 0) {
@@ -894,38 +894,37 @@ const AppFramework = (props) => {
parsedUsecase.process.push(edge.data)
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(parsedUsecase),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(parsedUsecase),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed updating: " + responseJson.reason)
} else {
toast("Failed to update framework for your org.")
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed updating: " + responseJson.reason)
} else {
toast("Updated usecase.")
toast("Failed to update framework for your org.")
}
})
.catch((error) => {
toast(error.toString());
//setFrameworkLoaded(true)
})
}
} else {
toast("Updated usecase.")
}
})
.catch((error) => {
toast(error.toString());
})
}
const activateApp = (appid) => {
fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", {
@@ -957,45 +956,54 @@ const AppFramework = (props) => {
}
const setFrameworkItem = (data) => {
console.log("Setting framework item: ", data, isCloud)
if (!isCloud) {
activateApp(data.id)
}
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed updating: " + responseJson.reason)
} else {
toast("Failed to update framework for your org.")
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed getting appframework: " + responseJson.reason)
} else {
toast("Failed to update framework for your org.")
}
}
}
} else {
if (data.id === "remove") {
frameworkData[data.type] = {}
frameworkData[data.type.toLowerCase()] = {}
} else {
frameworkData[data.type] = data
frameworkData[data.type.toLowerCase()] = data
}
//setFrameworkLoaded(true)
//setFrameworkData(responseJson)
})
.catch((error) => {
toast(error.toString());
//setFrameworkLoaded(true)
})
}
setDiscoveryData({})
if (setFrameworkData !== undefined) {
setFrameworkData(frameworkData)
}
}
})
.catch((error) => {
toast(error.toString());
//setFrameworkLoaded(true)
})
}
useEffect(() => {
if (!window.location.pathname.includes("usecases")) {
@@ -1058,7 +1066,6 @@ const AppFramework = (props) => {
frameworkData[keys[key]] = submitValue
}
console.log("Frameworkdata: ", frameworkData)
setFrameworkData(frameworkData)
if (discoveryData.large_image !== undefined && discoveryData.large_image !== null && discoveryData.large_image.includes("storage.googleapis.com")) {
@@ -1076,8 +1083,6 @@ const AppFramework = (props) => {
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const imgSize = 50;
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
@@ -1263,7 +1268,7 @@ const AppFramework = (props) => {
var parsedStyle2 = {
"ghost": "yes",
"border-width": "7px",
"border-width": "7px",
}
/*
@@ -1399,7 +1404,6 @@ const AppFramework = (props) => {
const onNodeSelect = (event) => {
var data = event.target.data();
console.log("Node: ", data)
if (data.id === "SHUFFLE") {
event.target.unselect()
return
@@ -1465,6 +1469,15 @@ const AppFramework = (props) => {
const foundMiddleImage = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : '/images/Shuffle_logo.png'
const siemcheck = parsedFrameworkData.SIEM.large_image === undefined || (parsedFrameworkData.SIEM.name !== undefined && parsedFrameworkData.SIEM.name !== null && parsedFrameworkData.SIEM.name.includes(":default"))
const iamcheck = parsedFrameworkData.IAM.large_image === undefined || (parsedFrameworkData.IAM.name !== undefined && parsedFrameworkData.IAM.name !== null && parsedFrameworkData.IAM.name.includes(":default"))
const casescheck = parsedFrameworkData.Cases.large_image === undefined || (parsedFrameworkData.Cases.name !== undefined && parsedFrameworkData.Cases.name !== null && parsedFrameworkData.Cases.name.includes(":default"))
const assetscheck = parsedFrameworkData.Assets.large_image === undefined || (parsedFrameworkData.Assets.name !== undefined && parsedFrameworkData.Assets.name !== null && parsedFrameworkData.Assets.name.includes(":default"))
const intelcheck = parsedFrameworkData.Intel.large_image === undefined || (parsedFrameworkData.Intel.name !== undefined && parsedFrameworkData.Intel.name !== null && parsedFrameworkData.Intel.name.includes(":default"))
const commscheck = parsedFrameworkData.Comms.large_image === undefined || (parsedFrameworkData.Comms.name !== undefined && parsedFrameworkData.Comms.name !== null && parsedFrameworkData.Comms.name.includes(":default"))
const edrcheck = parsedFrameworkData["EDR & AV"].large_image === undefined || (parsedFrameworkData["EDR & AV"].name !== undefined && parsedFrameworkData["EDR & AV"].name !== null && parsedFrameworkData["EDR & AV"].name.includes(":default"))
const networkcheck = parsedFrameworkData.Network.large_image === undefined || (parsedFrameworkData.Network.name !== undefined && parsedFrameworkData.Network.name !== null && parsedFrameworkData.Network.name.includes(":default"))
const fontSize = `${12*scale}px`
const defaultSize = `${85*scale}px`
@@ -1484,11 +1497,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData.Cases.name === undefined ? "" : parsedFrameworkData.Cases.name,
description: parsedFrameworkData.Cases.description === undefined ? "" : parsedFrameworkData.Cases.description,
app_id: parsedFrameworkData.Cases.id === undefined ? "" : parsedFrameworkData.Cases.id,
text_margin_y: parsedFrameworkData.Cases.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Cases.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Cases.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
text_margin_y: casescheck ? textMarginDefault : textMarginImage,
margin_x: casescheck ? `${32*scale}px` : "0px",
margin_y: casescheck ? `${19*scale}px` : `0px`,
width: casescheck ? iconSize : defaultSize,
height: casescheck ? iconSize : defaultSize,
large_image: parsedFrameworkData.Cases.large_image === undefined ? parsedDatatypeImages["CASES"] : parsedFrameworkData.Cases.large_image,
label: securityFramework[0].text.toUpperCase(),
@@ -1501,6 +1514,7 @@ const AppFramework = (props) => {
}
},
{
group: "nodes",
data: {
font_size: fontSize,
@@ -1512,11 +1526,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData.IAM.name === undefined ? "" : parsedFrameworkData.IAM.name,
description: parsedFrameworkData.IAM.description === undefined ? "" : parsedFrameworkData.IAM.description,
app_id: parsedFrameworkData.IAM.id === undefined ? "" : parsedFrameworkData.IAM.id,
text_margin_y: parsedFrameworkData.IAM.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.IAM.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.IAM.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
text_margin_y: iamcheck ? textMarginDefault : textMarginImage,
margin_x: iamcheck ? `${32*scale}px` : "0px",
margin_y: iamcheck ? `${19*scale}px` : `0px`,
width: iamcheck ? iconSize : defaultSize,
height: iamcheck ? iconSize : defaultSize,
large_image: parsedFrameworkData.IAM.large_image === undefined ? parsedDatatypeImages["IAM"] : parsedFrameworkData.IAM.large_image,
label: securityFramework[3].text.toUpperCase(),
@@ -1540,11 +1554,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData.Assets.name === undefined ? "" : parsedFrameworkData.Assets.name,
description: parsedFrameworkData.Assets.description === undefined ? "" : parsedFrameworkData.Assets.description,
app_id: parsedFrameworkData.Assets.id === undefined ? "" : parsedFrameworkData.Assets.id,
text_margin_y: parsedFrameworkData.Assets.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Assets.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Assets.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
text_margin_y: assetscheck ? textMarginDefault : textMarginImage,
margin_x: assetscheck ? `${32*scale}px` : "0px",
margin_y: assetscheck ? `${19*scale}px` : `0px`,
width: assetscheck ? iconSize : defaultSize,
height: assetscheck ? iconSize : defaultSize,
large_image: parsedFrameworkData.Assets.large_image === undefined ? parsedDatatypeImages["ASSETS"] : parsedFrameworkData.Assets.large_image,
label: securityFramework[2].text.toUpperCase(),
@@ -1568,11 +1582,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData.Intel.name === undefined ? "" : parsedFrameworkData.Intel.name,
description: parsedFrameworkData.Intel.description === undefined ? "" : parsedFrameworkData.Intel.description,
app_id: parsedFrameworkData.Intel.id === undefined ? "" : parsedFrameworkData.Intel.id,
text_margin_y: parsedFrameworkData.Intel.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Intel.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Intel.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
text_margin_y: intelcheck ? textMarginDefault : textMarginImage,
margin_x: intelcheck ? `${32*scale}px` : "0px",
margin_y: intelcheck ? `${19*scale}px` : `0px`,
width: intelcheck ? iconSize : defaultSize,
height: intelcheck ? iconSize : defaultSize,
large_image: parsedFrameworkData.Intel.large_image === undefined ? parsedDatatypeImages["INTEL"] : parsedFrameworkData.Intel.large_image,
label: securityFramework[4].text.toUpperCase(),
@@ -1596,11 +1610,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData.Comms.name === undefined ? "" : parsedFrameworkData.Comms.name,
description: parsedFrameworkData.Comms.description === undefined ? "" : parsedFrameworkData.Comms.description,
app_id: parsedFrameworkData.Comms.id === undefined ? "" : parsedFrameworkData.Comms.id,
text_margin_y: parsedFrameworkData.Comms.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Comms.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Comms.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
text_margin_y: commscheck ? textMarginDefault : textMarginImage,
margin_x: commscheck ? `${32*scale}px` : "0px",
margin_y: commscheck ? `${19*scale}px` : `0px`,
width: commscheck ? iconSize : defaultSize,
height: commscheck ? iconSize : defaultSize,
large_image: parsedFrameworkData.Comms.large_image === undefined ? parsedDatatypeImages["COMMS"] : parsedFrameworkData.Comms.large_image,
label: securityFramework[5].text.toUpperCase(),
@@ -1624,11 +1638,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData["EDR & AV"].name === undefined ? "" : parsedFrameworkData["EDR & AV"].name,
description: parsedFrameworkData["EDR & AV"].description === undefined ? "" : parsedFrameworkData["EDR & AV"].description,
app_id: parsedFrameworkData["EDR & AV"].id === undefined ? "" : parsedFrameworkData["EDR & AV"].id,
text_margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
text_margin_y: edrcheck ? textMarginDefault : textMarginImage,
margin_x: edrcheck ? `${32*scale}px` : "0px",
margin_y: edrcheck ? `${19*scale}px` : `0px`,
width: edrcheck ? iconSize : defaultSize,
height: edrcheck ? iconSize : defaultSize,
large_image: parsedFrameworkData["EDR & AV"].large_image === undefined ? parsedDatatypeImages["EDR & AV"] : parsedFrameworkData["EDR & AV"].large_image,
label: securityFramework[7].text.toUpperCase(),
@@ -1652,11 +1666,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData.Network.name === undefined ? "" : parsedFrameworkData.Network.name,
description: parsedFrameworkData.Network.description === undefined ? "" : parsedFrameworkData.Network.description,
app_id: parsedFrameworkData.Network.id === undefined ? "" : parsedFrameworkData.Network.id,
text_margin_y: parsedFrameworkData.Network.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Network.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Network.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
text_margin_y: networkcheck ? textMarginDefault : textMarginImage,
margin_x: networkcheck ? `${32*scale}px` : "0px",
margin_y: networkcheck ? `${19*scale}px` : `0px`,
width: networkcheck ? iconSize : defaultSize,
height: networkcheck ? iconSize : defaultSize,
large_image: parsedFrameworkData.Network.large_image === undefined ? parsedDatatypeImages["NETWORK"] : parsedFrameworkData.Network.large_image,
label: securityFramework[6].text.toUpperCase(),
id: securityFramework[6].text.toUpperCase(),
@@ -1679,11 +1693,11 @@ const AppFramework = (props) => {
name: parsedFrameworkData.SIEM.name === undefined ? "" : parsedFrameworkData.SIEM.name,
description: parsedFrameworkData.SIEM.description === undefined ? "" : parsedFrameworkData.SIEM.description,
app_id: parsedFrameworkData.SIEM.id === undefined ? "" : parsedFrameworkData.SIEM.id,
text_margin_y: parsedFrameworkData.SIEM.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.SIEM.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.SIEM.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
text_margin_y: siemcheck ? textMarginDefault : textMarginImage,
margin_x: siemcheck ? `${32*scale}px` : "0px",
margin_y: siemcheck ? `${19*scale}px` : `0px`,
width: siemcheck ? iconSize : defaultSize,
height: siemcheck ? iconSize : defaultSize,
large_image: parsedFrameworkData.SIEM.large_image === undefined ? parsedDatatypeImages["SIEM"] : parsedFrameworkData.SIEM.large_image,
label: securityFramework[1].text.toUpperCase(),
id: securityFramework[1].text.toUpperCase(),
@@ -1988,6 +2002,7 @@ const AppFramework = (props) => {
const bgColor = color === undefined || color === null || color.length === 0 ? theme.palette.surfaceColor : color
return (
<div style={{margin: "auto", backgroundColor: bgColor, position: "relative", }}>
{/*
<div style={{position: "absolute"}}>
<SuggestedWorkflows
@@ -1999,9 +2014,11 @@ const AppFramework = (props) => {
inputSearch={changedApp}
apps={apps}
/>
</div>
{injectedApps.map((apps, appindex) => {
</div>
*/}
{/*injectedApps.map((apps, appindex) => {
var categoryTop = 100
var categoryLeft = 100
@@ -2028,8 +2045,11 @@ const AppFramework = (props) => {
return null
}
const scale = 0.9
const offsetTop = -70
const offsetLeft = 0
return (
<div key={appindex} style={{display: "flex", position: "absolute", top: categoryTop, left: categoryLeft, zIndex: 10010, }}>
<div key={appindex} style={{display: "flex", position: "absolute", top: categoryTop+offsetTop, left: categoryLeft+offsetLeft, zIndex: 10010, }}>
{apps.map((app, appIndex) => {
return (
<Chip
@@ -2059,7 +2079,6 @@ const AppFramework = (props) => {
}
if (setFrameworkData !== undefined) {
console.log("Setting frameworkdata")
// Find discoveryData.id
var keys = []
for (const [key, value] of Object.entries(frameworkData)) {
@@ -2088,7 +2107,7 @@ const AppFramework = (props) => {
})}
</div>
)
})}
})*/}
{showOptions === false ? null :
<div style={{textAlign: "center",}}>
+8 -4
View File
@@ -37,7 +37,7 @@ import {
} from '@mui/material';
const AppSearchButtons = (props) => {
const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, } = props
const { userdata, globalUrl, appFramework, moreButton, finishedApps, appType, totalApps, index, onNodeSelect, setDiscoveryData, appName, AppImage, setDefaultSearch, discoveryData, checkLogin, setMissing, getAppFramework, } = props
const ref = useRef()
let navigate = useNavigate();
@@ -48,8 +48,6 @@ const AppSearchButtons = (props) => {
useEffect(() => {
if (newSelectedApp !== undefined && setMissing != undefined) {
console.log("AppSearchButtons: setMissing is defined!")
const submitAppFramework = {
"description": newSelectedApp.description,
"id": newSelectedApp.objectID,
@@ -60,11 +58,17 @@ const AppSearchButtons = (props) => {
setFrameworkItem(submitAppFramework)
setMissing(newSelectedApp)
if (getAppFramework !== undefined) {
setTimeout(() => {
getAppFramework()
}, 1000)
}
}
}, [newSelectedApp])
if (appType === undefined) {
console.log("Apptype is required in AppSearchButtons")
//console.log("Apptype is required in AppSearchButtons")
return null;
}
+23 -6
View File
@@ -77,10 +77,7 @@ const ConfigureWorkflow = (props) => {
const [checkStarted, setCheckStarted] = React.useState(false);
useEffect(() => {
console.log("Required actions: ", requiredActions)
if (requiredActions.length === 0) {
console.log("No more actions? Set parent to done?")
if (setConfigurationFinished !== undefined) {
setConfigurationFinished(true)
}
@@ -705,9 +702,9 @@ const ConfigureWorkflow = (props) => {
})
.then((responseJson) => {
if (!responseJson.success) {
toast("Failed to set app auth: " + responseJson.reason);
toast("Failed to set app authentication: " + responseJson.reason);
} else {
toast("App auth set for app " + app.name.replace("_", " "));
toast("App authentication set for app " + app.name.replace("_", " "));
setFinalized(true)
setOpened(false)
}
@@ -731,6 +728,7 @@ const ConfigureWorkflow = (props) => {
parsedName = (parsedName.charAt(0).toUpperCase() + parsedName.slice(1)).replaceAll("_", " ");
console.log("AUTH Action: ", action)
return (
<ListItem
style={{padding: 0, display: "flex", flexDirection: "column", }}
@@ -744,6 +742,25 @@ const ConfigureWorkflow = (props) => {
<div style={{display: "flex", }}
onClick={() => {
setOpened(!opened);
// Scroll to it
const element = document.getElementById("app-config");
if (element) {
// Scroll down 100px
setTimeout(() => {
element.scrollIntoView({
behavior: "smooth",
top: 100,
})
//element.scrollIntoView({
// behavior: "smooth",
// block: "center",
// inline: "center"
//});
}, 250)
}
}}
>
<div style={{display: "flex", flex: 7, }}>
@@ -1318,7 +1335,7 @@ const ConfigureWorkflow = (props) => {
</Typography>
: null}
<List style={{paddingBottom: window.location.pathname.includes("/workflows/") ? 0 : 250, }}>
<List style={{paddingBottom: window.location.pathname.includes("/workflows/") ? 0 : 350, }}>
{requiredActions.map((data, index) => {
// AppWrapper = Default in a workflow, only shows with steps
+1 -3
View File
@@ -146,8 +146,6 @@ const EditWorkflow = (props) => {
const newWorkflow = isEditing === true ? false : true
const priority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true)
console.log("PRIO: ", priority)
var upload = "";
var total_count = 0
@@ -485,7 +483,7 @@ const EditWorkflow = (props) => {
</DialogContent>
<DialogActions style={{paddingRight: 100, }}>
<DialogActions style={{paddingRight: 100, marginBottom: 100, }}>
<Button
style={{}}
onClick={() => {
+291 -21
View File
@@ -16,6 +16,11 @@ import {
Divider,
Select,
MenuItem,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Typography,
} from "@mui/material";
import {
@@ -30,9 +35,8 @@ import {
Add as AddIcon,
} from "@mui/icons-material";
//import { useAlert
import Dropzone from "../components/Dropzone.jsx";
import CodeEditor from "../components/ShuffleCodeEditor.jsx";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
import theme from "../theme.jsx";
const Files = (props) => {
@@ -45,6 +49,13 @@ const Files = (props) => {
const [fileContent, setFileContent] = React.useState("");
const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false);
const [loadFileModalOpen, setLoadFileModalOpen] = React.useState(false);
const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState("");
const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards")
const [downloadBranch, setDownloadBranch] = React.useState("main");
const [downloadFolder, setDownloadFolder] = React.useState("translation_standards");
//const alert = useAlert();
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log"]
@@ -67,7 +78,7 @@ const Files = (props) => {
}
const runUpdateText = (text) =>{
const runUpdateText = (text) =>{
fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, {
method: "PUT",
headers: {
@@ -76,17 +87,34 @@ const Files = (props) => {
},
body:text,
credentials: "include",
}).then((response) => {
})
.then((response) => {
if (response.status !== 200) {
console.log("Can't update file");
}
return response.json();
})
//console.log(text);
})
.then((responseJson) => {
if (responseJson.success === true) {
toast("Successfully updated file");
}
})
.catch((error) => {
toast("Error updating file: " + error.toString());
})
}
const getFiles = () => {
fetch(globalUrl + "/api/v1/files", {
const getFiles = (namespace) => {
var parsedurl = `${globalUrl}/api/v1/files`
if (namespace === undefined || namespace === "default") {
} else if (namespace !== undefined && namespace !== null && namespace !== "") {
parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true`
} else if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace !== "default" && selectedNamespace !== "") {
parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedNamespace}?ids=true`
}
fetch(parsedurl, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -104,12 +132,23 @@ const Files = (props) => {
})
.then((responseJson) => {
if (responseJson.files !== undefined && responseJson.files !== null) {
setFiles(responseJson.files);
} else {
setFiles(responseJson.files);
} else if (responseJson.list !== undefined && responseJson.list !== null) {
// Set the "namespace" field in all items
if (namespace !== undefined && namespace !== null) {
responseJson.list.forEach((item) => {
item.namespace = namespace
item.filename = item.name
item.workflow_id = "global"
})
}
setFiles(responseJson.list);
} else {
setFiles([]);
}
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null) {
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileNamespaces.length === 0 || responseJson.namespaces.length > fileNamespaces.length)) {
setFileNamespaces(responseJson.namespaces);
}
})
@@ -119,9 +158,214 @@ const Files = (props) => {
};
useEffect(() => {
getFiles();
getFiles(selectedNamespace)
}, []);
const importStandardsFromUrl = (url, folder) => {
if (url === undefined || url === null || url.length < 5) {
toast("Please enter a valid URL");
return;
}
if (folder === undefined || folder === null || folder.length < 2) {
toast("Please enter a valid folder name")
return
}
const parsedData = {
url: url,
path: folder,
field_3: downloadBranch || "master",
};
if (field1.length > 0) {
parsedData["field_1"] = field1;
}
if (field2.length > 0) {
parsedData["field_2"] = field2;
}
toast(`Getting files from url ${url}. This may take a while if the repository is large. Please wait...`);
fetch(globalUrl + "/api/v1/files/download_remote", {
method: "POST",
mode: "cors",
headers: {
Accept: "application/json",
},
body: JSON.stringify(parsedData),
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
toast("Successfully loaded files from " + downloadUrl);
setLoadFileModalOpen(false);
}
return response.json();
})
.then((responseJson) => {
if (!responseJson.success) {
if (responseJson.reason !== undefined) {
toast("Failed loading: " + responseJson.reason);
} else {
toast("Failed loading");
}
}
})
.catch((error) => {
toast(error.toString());
});
}
const handleGithubValidation = () => {
importStandardsFromUrl(downloadUrl, downloadFolder);
}
const fileDownloadModal = loadFileModalOpen ?
<Dialog
open={loadFileModalOpen}
onClose={() => {}}
PaperProps={{
style: {
backgroundColor: theme.palette.surfaceColor,
color: "white",
minWidth: "800px",
minHeight: "320px",
},
}}
>
<DialogTitle>
<div style={{ color: "rgba(255,255,255,0.9)" }}>
Load Files from Github
</div>
<Typography variant="body2" color="textSecondary">
Files will be loaded from the repository and branch you specify, with the focus on files in one folder at a time. This is NOT recursive.
</Typography>
</DialogTitle>
<DialogContent style={{ color: "rgba(255,255,255,0.65)" }}>
Repository URL (supported: github, gitlab, bitbucket)
<TextField
style={{ backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
defaultValue={downloadUrl}
InputProps={{
style: {
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={(e) => setDownloadUrl(e.target.value)}
placeholder="https://github.com/shuffle/standards"
fullWidth
/>
<div style={{ display: "flex" }}>
<span>
<span style={{ marginTop: 10 }}>
Branch (default value is "main"):
</span>
<TextField
style={{ backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
defaultValue={downloadBranch}
InputProps={{
style: {
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={(e) => setDownloadBranch(e.target.value)}
placeholder="master"
fullWidth
/>
</span>
<span>
<span style={{ marginTop: 10 }}>
Folder (can use / for subfolders):
</span>
<TextField
style={{ backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
defaultValue={downloadFolder}
InputProps={{
style: {
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={(e) => setDownloadFolder(e.target.value)}
placeholder="translation_standards"
fullWidth
/>
</span>
</div>
<span style={{ marginTop: 10 }}>
Authentication (optional - private repos etc):
</span>
<div style={{ display: "flex" }}>
<TextField
style={{ flex: 1, backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
InputProps={{
style: {
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={(e) => setField1(e.target.value)}
type="username"
placeholder="Username / APIkey (optional)"
fullWidth
/>
<TextField
style={{ flex: 1, backgroundColor: theme.palette.inputColor }}
variant="outlined"
margin="normal"
InputProps={{
style: {
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={(e) => setField2(e.target.value)}
type="password"
placeholder="Password (optional)"
fullWidth
/>
</div>
</DialogContent>
<DialogActions>
<Button
style={{ borderRadius: "0px" }}
onClick={() => setLoadFileModalOpen(false)}
color="primary"
>
Cancel
</Button>
<Button
variant="contained"
style={{ borderRadius: "0px" }}
disabled={downloadUrl.length === 0 || !downloadUrl.includes("http")}
onClick={() => {
handleGithubValidation();
}}
color="primary"
>
Submit
</Button>
</DialogActions>
</Dialog>
: null
const deleteFile = (file) => {
fetch(globalUrl + "/api/v1/files/" + file.id, {
method: "DELETE",
@@ -140,7 +384,7 @@ const Files = (props) => {
})
.then((responseJson) => {
if (responseJson.success) {
toast("Successfully deleted file " + file.name);
toast("Successfully deleted file")
} else if (
responseJson.reason !== undefined &&
responseJson.reason !== null
@@ -254,7 +498,7 @@ const Files = (props) => {
});
};
const handleCreateFile = (filename, file) => {
const handleCreateFile = (filename, file) => {
var data = {
filename: filename,
org_id: selectedOrganization.id,
@@ -355,7 +599,7 @@ const Files = (props) => {
}
setTimeout(() => {
getFiles();
getFiles()
}, 2500);
};
@@ -378,7 +622,21 @@ const Files = (props) => {
}}
onDrop={uploadFile}
>
<div>
<div style={{position: "relative"}}>
<Tooltip color="primary" title={"Import files to Shuffle from Git"} placement="top">
<IconButton
color="secondary"
style={{position: "absolute", right: 0, top: 0, }}
variant="text"
onClick={() => setLoadFileModalOpen(true)}
>
<CloudDownloadIcon />
</IconButton>
</Tooltip>
{fileDownloadModal}
<div style={{ marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>Files</h2>
<span style={{ marginLeft: 25 }}>
@@ -393,6 +651,9 @@ const Files = (props) => {
</a>
</span>
</div>
<Button
color="primary"
variant="contained"
@@ -426,6 +687,8 @@ const Files = (props) => {
<CachedIcon />
</Button>
{fileNamespaces !== undefined &&
fileNamespaces !== null &&
fileNamespaces.length > 1 ? (
@@ -442,8 +705,13 @@ const Files = (props) => {
}}
value={selectedNamespace}
onChange={(event) => {
console.log("CHANGE NAMESPACE: ", event.target);
setSelectedNamespace(event.target.value);
if (event.target.value === "all" || event.target.value === "default") {
getFiles()
} else {
getFiles(event.target.value)
}
}}
>
{fileNamespaces.map((data, index) => {
@@ -453,7 +721,7 @@ const Files = (props) => {
value={data}
style={{ color: "white" }}
>
{data}
{data.replaceAll("_", " ")}
</MenuItem>
);
})}
@@ -487,6 +755,8 @@ const Files = (props) => {
<AddIcon/>
</Button>
</Tooltip> }
{renderTextBox && <TextField
onKeyPress={(event)=>{
handleKeyDown(event);
@@ -504,7 +774,7 @@ const Files = (props) => {
autoFocus
/>}</div>
<CodeEditor
<ShuffleCodeEditor
isCloud={isCloud}
expansionModalOpen={openEditor}
setExpansionModalOpen={setOpenEditor}
@@ -602,7 +872,7 @@ const Files = (props) => {
/>
<ListItemText
primary={
file.workflow_id === "global" ? (
file.workflow_id === "global" || file.workflow_id === "" || file.workflow_id === null || file.workflow_id === undefined ?
<IconButton
disabled={file.workflow_id === "global"}
>
@@ -615,7 +885,7 @@ const Files = (props) => {
}}
/>
</IconButton>
) : (
: (
<Tooltip
title={"Go to workflow"}
style={{}}
+23 -39
View File
@@ -88,12 +88,6 @@ import {
SquareFoot as SquareFootIcon,
} from '@mui/icons-material';
//import CodeMirror from "@uiw/react-codemirror";
//import "codemirror/keymap/sublime";
//import "codemirror/theme/gruvbox-dark.css";
//import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx";
const useStyles = makeStyles({
notchedOutline: {
borderColor: "#f85a3e !important",
@@ -1040,22 +1034,28 @@ const ParsedAction = (props) => {
};
const returnHelperText = (name, value) => {
if (name === undefined || value === undefined) {
return ""
}
var helperText = ""
//console.log("DATA: ", name, value)
if (name.includes("url")) {
if (value.includes("localhost") || value.includes("127.0.0.1")) {
helperText = "Can't use localhost. Please change to an external IP or hostname."
}
}
return helperText
const returnHelperText = (name, value) => {
if (name === undefined || value === undefined || name === null || value === null || name === "" || value === "") {
//console.log("RETURNING EMPTY")
return ""
}
var helperText = ""
//console.log("DATA: ", name, value)
if (name.includes("url")) {
if (value.includes("localhost") || value.includes("127.0.0.1")) {
helperText = "Can't use localhost. Please change to your external IP."
}
}
// Helpertext for openapi fields
//if (helperText === "" && name === "body" && selectedApp.generated && selectedApp.activated) {
// helperText = <span style={{color: "white", marginBottom: 5, marginleft: 5}}>
//}
return helperText
}
//console.log("AUTH: ", authenticationType)
if (authenticationType !== undefined && authenticationType !== null && authenticationType.type === "oauth2") {
/*
@@ -1297,17 +1297,13 @@ const ParsedAction = (props) => {
}
var placeholder = "Value";
if (
data.example !== undefined &&
data.example !== null &&
data.example.length > 0
) {
if (data.example !== undefined && data.example !== null && data.example.length > 0) {
placeholder = data.example;
if (data.name === "url" && data.value !== undefined && data.value !== null && data.value.length === 0) {
data.value = data.example;
}
// In case of data.example
if (data.value === undefined || data.value === null) {
data.value = ""
@@ -1642,7 +1638,6 @@ const ParsedAction = (props) => {
),
}}
multiline={data.name.startsWith("${") && data.name.endsWith("}") ? true : multiline}
helperText={returnHelperText(data.name, data.value)}
onClick={() => {
/*
setExpansionModalOpen(false);
@@ -1680,18 +1675,7 @@ const ParsedAction = (props) => {
//changeActionParameterCodemirror(event, count, data)
changeActionParameter(event, count, data);
}}
helperText={baseHelperText.length > 0 ? baseHelperText :
selectedApp.generated &&
selectedApp.activated &&
data.name === "body" ? (
<span
style={{ color: "white", marginBottom: 5, marginleft: 5 }}
>
{openApiHelperText}
</span>
) : data.name.startsWith("${") && data.name.endsWith("}") ?
null : null
}
helperText={returnHelperText(data.name, data.value)}
onBlur={(event) => {
baseHelperText = calculateHelpertext(event.target.value)
if (setLastSaved !== undefined) {
+76 -8
View File
@@ -67,9 +67,15 @@ const Priorities = (props) => {
})
}
const dismissNotification = (alert_id) => {
// Don't really care about the logout
fetch(`${globalUrl}/api/v1/notifications/${alert_id}/markasread`, {
const dismissNotification = (alert_id, disabled) => {
var notificationurl = `${globalUrl}/api/v1/notifications/${alert_id}/markasread`
if (disabled === true) {
notificationurl += "?disabled=true"
} else if (disabled === false) {
notificationurl += "?disabled=false"
}
fetch(notificationurl , {
credentials: "include",
method: "GET",
headers: {
@@ -85,12 +91,50 @@ const Priorities = (props) => {
})
.then(function (responseJson) {
if (responseJson.success === true) {
const newNotifications = notifications.filter(
(data) => data.id !== alert_id
);
console.log("NEW NOTIFICATIONS: ", newNotifications);
// Mark current one as read
var newNotifications = notifications.map((notification) => {
if (notification.id === alert_id) {
notification.read = true
}
if (setNotifications !== undefined) {
return notification
})
if (disabled === true) {
toast("Notification disabled, and will not be shown again.")
newNotifications = newNotifications.map((notification) => {
if (notification.id === alert_id) {
notification.ignored = true
}
return notification
})
console.log("NEW NOTIFICATIONS: ", newNotifications);
} else if (disabled === false) {
toast("Notification re-enabled successfully")
newNotifications = newNotifications.map((notification) => {
if (notification.id === alert_id) {
notification.ignored = false
}
return notification
})
} else {
toast("Notification dismissed successfully")
}
//const newNotifications = notifications.filter(
// (data) => data.id !== alert_id
//)
//console.log("NEW NOTIFICATIONS: ", newNotifications);
if (setNotifications !== undefined && newNotifications !== undefined) {
setNotifications(newNotifications)
}
} else {
@@ -174,6 +218,14 @@ const Priorities = (props) => {
style={{marginRight: 15, height: 25, }}
/>
: null}
{data.ignored === true ?
<Chip
label={"Disabled"}
variant="outlined"
color="primary"
style={{marginRight: 15, height: 25, }}
/>
: null}
{data.read === false ?
<Chip
label={"Unread"}
@@ -227,6 +279,22 @@ const Priorities = (props) => {
Dismiss
</Button>
) : null}
<Tooltip title="Disabling a notification makes it so similar notifications to this one will NOT be re-opened. It will NOT forward notifications to your notification workflow, but WILL still keep counting." placement="top">
<Button
color="secondary"
variant={data.ignored === true ? "contained" : "outlined"}
style={{ marginTop: 15, }}
onClick={() => {
if (data.ignored === true) {
dismissNotification(data.id, false)
} else {
dismissNotification(data.id, true)
}
}}
>
{data.ignored === true ? "Re-enable" : "Disable"}
</Button>
</Tooltip>
</ButtonGroup>
<Typography variant="body2" color="textSecondary" style={{marginLeft: 20, marginTop: 20, }}>
-1
View File
@@ -32,7 +32,6 @@ const Priority = (props) => {
let newdescription = priority.description
const descsplit = priority.description.split("&")
if (appFramework !== undefined && descsplit.length === 5 && priority.description.includes(":default")) {
console.log("descsplit: ", descsplit)
if (descsplit[1] === "") {
const item = findSpecificApp(appFramework, descsplit[0])
+6 -28
View File
@@ -5,13 +5,13 @@ import cystyle from "../defaultCytoscapeStyle.jsx";
const surfaceColor = "#27292D";
const CytoscapeWrapper = (props) => {
const { globalUrl, inworkflow } = props;
const { globalUrl, inworkflow, height, width } = props;
const [elements, setElements] = useState([]);
const [workflow, setWorkflow] = useState(inworkflow);
const [cy, setCy] = React.useState();
const bodyWidth = 200;
const bodyHeight = 150;
const bodyWidth = height === undefined ? 1000 : width
const bodyHeight = width === undefined ? 1000 : height
const setupGraph = () => {
const actions = workflow.actions.map((action) => {
@@ -76,29 +76,6 @@ const CytoscapeWrapper = (props) => {
};
// This is an attempt at prettier edges. The numbers are weird to work with.
/*
//http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html
const sourcenode = actions.find(node => node.data._id === branch.source_id)
const destinationnode = actions.find(node => node.data._id === branch.destination_id)
if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) {
//node.data._id = action["id"]
console.log("SOURCE: ", sourcenode.position)
console.log("DESTINATIONNODE: ", destinationnode.position)
var opposite = true
if (sourcenode.position.x > destinationnode.position.x) {
opposite = false
} else {
opposite = true
}
edge.style = {
'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"],
'control-point-weight': ['0.3', '0.7'],
}
}
*/
return edge;
});
@@ -135,9 +112,10 @@ const CytoscapeWrapper = (props) => {
elements={elements}
minZoom={0.35}
maxZoom={2.0}
zoom={1.0}
style={{
width: bodyWidth - 15,
height: bodyHeight - 5,
width: bodyWidth,
height: bodyHeight,
backgroundColor: surfaceColor,
}}
stylesheet={cystyle}
+14 -4
View File
@@ -75,9 +75,15 @@ const RuntimeDebugger = (props) => {
setTotalCount(0)
var starttime = ""
var endtime = ""
var count = 0
var starttime = startTime === undefined || startTime === null || startTime === "" ? "" : new Date(startTime).toISOString()
var endtime = endTime === undefined || endTime === null || endTime == "" ? "" : new Date(endTime).toISOString()
try {
starttime = startTime === undefined || startTime === null || startTime === "" ? "" : new Date(startTime).toISOString()
endtime = endTime === undefined || endTime === null || endTime == "" ? "" : new Date(endTime).toISOString()
} catch (e) {
toast("Invalid date format", { type: "error" })
}
var maxworkflows = 5
@@ -88,6 +94,11 @@ const RuntimeDebugger = (props) => {
}
const workflowId = workflows[key].id
if (workflowId === undefined || workflowId === null || workflowId === "") {
continue
}
// Fetch the data for the workflow
var url = `${globalUrl}/api/v1/workflows/${workflowId}/executions/count`
if (starttime !== "") {
@@ -133,7 +144,6 @@ const RuntimeDebugger = (props) => {
}
setTimeout(() => {
console.log("Setting total count: ", count)
setTotalCount(count)
}, maxworkflows*300)
}
@@ -578,7 +588,7 @@ const RuntimeDebugger = (props) => {
onClick={() => {
window.open(`${globalUrl}/api/v1/workflows/search/${params.row.id}`, "_blank")
}}
disabled={!userdata.support}
disabled={userdata.region_url !== "https://shuffler.io"}
>
<InsightsIcon fontSize="small" />
</IconButton>
@@ -57,7 +57,6 @@ import { padding, textAlign } from '@mui/system';
import data from '../frameworkStyle.jsx';
import { useNavigate, Link, useParams } from "react-router-dom";
import { tags as t } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
@@ -84,37 +83,6 @@ const pythonFilters = [
{"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``},
]
/*
const shuffleTheme = createTheme({
theme: 'dark',
settings: {
background: "rgba(40,40,40, 1)",
foreground: '#75baff',
caret: '#5d00ff',
selection: '#036dd626',
selectionMatch: '#036dd626',
lineHighlight: '#8a91991a',
gutterForeground: '#8a919966',
},
styles: [
{ tag: t.comment, color: '#787b8099' },
{ tag: t.variableName, color: '#0080ff' },
{ tag: [t.string, t.special(t.brace)], color: '#5c6166' },
{ tag: t.number, color: '#5c6166' },
{ tag: t.bool, color: '#5c6166' },
{ tag: t.null, color: '#5c6166' },
{ tag: t.keyword, color: '#5c6166' },
{ tag: t.operator, color: '#5c6166' },
{ tag: t.className, color: '#5c6166' },
{ tag: t.definition(t.typeName), color: '#5c6166' },
{ tag: t.typeName, color: '#5c6166' },
{ tag: t.angleBracket, color: '#5c6166' },
{ tag: t.tagName, color: '#5c6166' },
{ tag: t.attributeName, color: '#5c6166' },
],
});
*/
const CodeEditor = (props) => {
const {
globalUrl,
@@ -5,6 +5,7 @@ import theme from '../theme.jsx';
import { useNavigate, Link, useParams } from "react-router-dom";
import AppSearchButtons from "../components/AppSearchButtons.jsx";
import { isMobile } from "react-device-detect";
import RenderCytoscape from "../components/RenderCytoscape.jsx";
import {
Button,
Typography,
@@ -29,7 +30,7 @@ import WorkflowTemplatePopup2 from "./WorkflowTemplatePopup.jsx";
import ConfigureWorkflow from "../components/ConfigureWorkflow.jsx";
const WorkflowTemplatePopup = (props) => {
const { userdata, appFramework, globalUrl, img1, srcapp, img2, dstapp, title, description, visualOnly, apps, isLoggedIn, isHomePage } = props;
const { userdata, appFramework, globalUrl, img1, srcapp, img2, dstapp, title, description, visualOnly, apps, isLoggedIn, isHomePage, getAppFramework, } = props;
const [isActive, setIsActive] = useState(false);
const [isHovered, setIsHovered] = useState(false);
@@ -39,17 +40,41 @@ const WorkflowTemplatePopup = (props) => {
const [workflow, setWorkflow] = useState({});
const [showLoginButton, setShowLoginButton] = useState(false);
const [appAuthentication, setAppAuthentication] = React.useState(undefined);
const [missingSource, setMissingSource] = React.useState(undefined)
const [missingDestination, setMissingDestination] = React.useState(undefined);
const [configurationFinished, setConfigurationFinished] = React.useState(false);
const [appSetupDone, setAppSetupDone] = React.useState(false)
useEffect(() => {
}, [missingSource, missingDestination])
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const [requestSent, setRequestSent] = React.useState(false)
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
let navigate = useNavigate();
useEffect(() => {
if (modalOpen !== true) {
return
}
if (!srcapp.includes(":default") && !dstapp.includes(":default")) {
if (appSetupDone === false && setAppSetupDone !== undefined) {
setAppSetupDone(true)
}
getGeneratedWorkflow()
}
if (missingSource !== undefined && missingDestination !== undefined) {
if (appSetupDone === false && setAppSetupDone !== undefined) {
setAppSetupDone(true)
}
}
if (getAppFramework !== undefined) {
setTimeout(() => {
getAppFramework()
}, 500)
}
}, [modalOpen, missingSource, missingDestination])
const imagestyleWrapper = {
height: 40,
@@ -272,6 +297,9 @@ const WorkflowTemplatePopup = (props) => {
// middle:[]
// name: "Email analysis"
// source:{app_id: "accdaaf2eeba6a6ed43b2efc0112032d", app_name
if (requestSent === true) {
return
}
if (srcapp.includes(":default") || dstapp.includes(":default")) {
@@ -309,9 +337,8 @@ const WorkflowTemplatePopup = (props) => {
},
}
setRequestSent(true)
const url = isCloud ? `${globalUrl}/api/v1/workflows/merge` : `https://shuffler.io/api/v1/workflows/merge`
//const url = `https://shuffler.io/api/v1/workflows/merge`
fetch(url, {
method: "POST",
headers: {
@@ -324,6 +351,7 @@ const WorkflowTemplatePopup = (props) => {
.then((response) => {
if (response.status !== 200) {
//console.log("Status not 200 for framework!");
setRequestSent(false)
}
setWorkflowLoading(false)
@@ -341,6 +369,7 @@ const WorkflowTemplatePopup = (props) => {
if (responseJson.success === false) {
//console.log("Error in workflow template: ", responseJson.error);
setRequestSent(false)
const defaultMessage = "Failed to generate workflow the workflow - the Shuffle team has been notified. Contact support@shuffler.io for further assistance."
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason !== "") {
@@ -366,10 +395,21 @@ const WorkflowTemplatePopup = (props) => {
})
.catch((error) => {
console.log("err in framework: ", error.toString());
setRequestSent(false)
setWorkflowLoading(false)
})
}
if (modalOpen === true && !srcapp.includes(":default") && !dstapp.includes(":default")) {
if (appSetupDone === false && setAppSetupDone !== undefined) {
setAppSetupDone(true)
}
if (workflow.id === undefined && workflowLoading === false && errorMessage === "") {
getGeneratedWorkflow()
}
}
const isFinished = () => {
// Look for configuration fields being done in the current modal
// 1. Start by finding the modal
@@ -388,6 +428,13 @@ const WorkflowTemplatePopup = (props) => {
}
const ModalView = () => {
if (modalOpen === false) {
return null
}
const divHeight = 500
const divWidth = 500
return (
<Drawer
anchor={"left"}
@@ -475,9 +522,9 @@ const WorkflowTemplatePopup = (props) => {
</div>
}
{(missingSource !== undefined || missingDestination !== undefined) ?
{(appSetupDone === false && missingSource !== undefined || missingDestination !== undefined) ?
<Typography variant="body1" style={{marginTop: 75, marginBottom: 10, }}>
{"Find relevevant Apps for this Usecase"}
{"Find relevant Apps for this Usecase"}
</Typography>
: null}
@@ -491,6 +538,8 @@ const WorkflowTemplatePopup = (props) => {
AppImage={missingSource.image}
setMissing={setMissingSource}
getAppFramework={getAppFramework}
/>
</div>
: null}
@@ -505,6 +554,8 @@ const WorkflowTemplatePopup = (props) => {
AppImage={missingDestination.image}
setMissing={setMissingDestination}
getAppFramework={getAppFramework}
/>
</div>
: null}
@@ -521,12 +572,29 @@ const WorkflowTemplatePopup = (props) => {
setConfigurationFinished={setConfigurationFinished}
/>
{/*workflow !== undefined && workflow !== null && workflow.id !== undefined && workflow.id !== null && workflow.id !== "" ?
<div style={{position: "fixed", right: "5%", top: "20%", border: "1px solid rgba(255,255,255,0.3)", height: divHeight, width: divWidth, borderRadius: theme.palette.borderRadius, }}>
<RenderCytoscape
inworkflow={workflow}
height={divHeight}
width={divWidth}
/>
</div>
: null*/}
{errorMessage === "" && configurationFinished === true && workflow.id !== undefined && workflowLoading === false ?
<Tooltip title="Workflow generated!" placement="top">
<span style={{display: "flex", }}>
<Tooltip title="Click to explore the workflow" placement="top">
<span
style={{position: "fixed", display: "flex", right: "10%", top: "20%", border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, padding: "15px 30px 15px 30px", backgroundColor: theme.palette.platformColor, cursor: "pointer", }}
onClick={() => {
// Open in new tab
window.open("/workflows/" + workflow.id, "_blank")
}}
>
{/*<CheckIcon color="primary" sx={{ borderRadius: 4 }} /> */}
<Typography variant="h6" style={{ marginLeft: 20, }}>
Workflow generated!
<Typography variant="h5" style={{ }}>
Workflow Successfully Generated!
</Typography>
</span>
</Tooltip>
@@ -648,6 +716,7 @@ const WorkflowTemplatePopup = (props) => {
<CheckIcon color="primary" sx={{ borderRadius: 4 }} style={{ position: "absolute", color: theme.palette.green, top: 10, right: 10, }} />
: ""}
</div>
</div>
+31 -22
View File
@@ -5,8 +5,6 @@ import { makeStyles } from "@mui/styles";
import { useNavigate, Link } from "react-router-dom";
import countries from "../components/Countries.jsx";
import CodeEditor from "../components/ShuffleCodeEditor.jsx";
import getLocalCodeData from "../components/ShuffleCodeEditor.jsx";
import CacheView from "../components/CacheView.jsx";
import {
@@ -1867,16 +1865,24 @@ If you're interested, please let me know a time that works for you, or set up a
{selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app" ?
<div>
<Typography variant="body1" color="textSecondary" style={{ marginBottom: 0, marginTop: 10 }}>
Only the name of the auth can be modified for Oauth2. Please remake the authentication to change the fields like Client ID, Secret, Scopes etc.
Only the name and url can be modified for Oauth2/OpenID connect. Please remake the authentication if you want to change the other fields like Client ID, Secret, Scopes etc.
</Typography>
</div>
:
selectedAuthentication.fields.map((data, index) => {
: null }
{selectedAuthentication.fields.map((data, index) => {
var fieldname = data.key.replaceAll("_", " ")
if (fieldname.endsWith(" basic")) {
fieldname = fieldname.substring(0, fieldname.length - 6)
}
if (selectedAuthentication.type === "oauth" || selectedAuthentication.type === "oauth2" || selectedAuthentication.type === "oauth2-app") {
if (selectedAuthentication.fields[index].key !== "url") {
return null
}
}
//console.log("DATA: ", data, selectedAuthentication)
return (
<div key={index}>
@@ -3415,24 +3421,26 @@ If you're interested, please let me know a time that works for you, or set up a
// using request id or trace id
<ListItem key={index} style={{ backgroundColor: index % 2 === 0 ? "#1f2023" : "#27292d" }}>
<ListItemText
primary={new Date(data.start_time.seconds * 1000).toISOString().slice(0, 10)}
primary={new Date(data.start_time.seconds * 1000).toLocaleString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
})}
style={{
minWidth: 150,
maxWidth: 150,
minWidth: 200,
maxWidth: 200,
}}
/>
<ListItemText
primary={data.status}
primary={data.referrer}
style={{
minWidth: 70,
maxWidth: 70,
}}
/>
<ListItemText
primary={data.method}
style={{
minWidth: 150,
maxWidth: 150,
minWidth: 300,
maxWidth: 300,
overflow: "hidden",
}}
/>
<ListItemText
@@ -3441,6 +3449,7 @@ If you're interested, please let me know a time that works for you, or set up a
minWidth: 700,
maxWidth: 700,
overflow: "hidden",
marginLeft: 10,
}}
/>
</ListItem>
@@ -4406,7 +4415,7 @@ If you're interested, please let me know a time that works for you, or set up a
/>
<ListItemText
primary="Default"
style={{ minWidth: 100, maxWidth: 100 }}
style={{ minWidth: 110, maxWidth: 110, }}
/>
<ListItemText
primary="Actions"
@@ -4550,8 +4559,8 @@ If you're interested, please let me know a time that works for you, or set up a
/>
<ListItemText
style={{
minWidth: 80,
maxWidth: 80,
minWidth: 100,
maxWidth: 100,
overflow: "hidden",
}}
primary={environment.default ? "true" : null}
@@ -4559,7 +4568,7 @@ If you're interested, please let me know a time that works for you, or set up a
{environment.default ? null : (
<Button
variant="outlined"
style={{ marginLeft: 0, marginRight: 0, }}
style={{ marginLeft: 0, marginRight: 0, maxWidth: 150, }}
onClick={() => setDefaultEnvironment(environment)}
color="primary"
>
+252 -64
View File
@@ -121,7 +121,7 @@ import * as edgehandles from "cytoscape-edgehandles";
import CytoscapeComponent from "react-cytoscapejs";
import Draggable from "react-draggable";
import cytoscapestyle from "../defaultCytoscapeStyle.jsx";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor.jsx";
import ShuffleCodeEditor from "../components/ShuffleCodeEditor1.jsx";
import { validateJson, GetIconInfo } from "../views/Workflows.jsx";
import { GetParsedPaths, internalIds, } from "../views/Apps.jsx";
@@ -13366,6 +13366,67 @@ const AngularWorkflow = (defaultprops) => {
</Link>
<h2 style={{ margin: 0 }}>{workflow.name}</h2>
</Breadcrumbs>
{workflow.public === true || userdata.active_org.id === undefined || userdata.active_org.id === null || workflow.org_id === null || workflow.org_id === undefined || workflow.org_id.length === 0 || userdata.active_org.id === workflow.org_id ? null :
<Typography variant="body1">
<b>Warning</b>: Change <span
style={{color: "#f85a3e", cursor: "pointer"}}
onClick={() => {
toast("Changing to correct organization. Please wait a few seconds.")
localStorage.setItem("globalUrl", "");
localStorage.setItem("getting_started_sidebar", "open");
fetch(`${globalUrl}/api/v1/orgs/${workflow.org_id}/change`, {
mode: "cors",
credentials: "include",
crossDomain: true,
method: "POST",
body: JSON.stringify({"org_id": workflow.org_id}),
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then(function (response) {
if (response.status !== 200) {
console.log("Error in response");
}
return response.json();
})
.then(function (responseJson) {
console.log("In here?")
if (responseJson.success === true) {
if (responseJson.region_url !== undefined && responseJson.region_url !== null && responseJson.region_url.length > 0) {
console.log("Region Change: ", responseJson.region_url);
localStorage.setItem("globalUrl", responseJson.region_url);
//globalUrl = responseJson.region_url
}
setTimeout(() => {
window.location.reload();
}, 2000);
toast("Successfully changed active organization - refreshing!");
} else {
if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) {
toast(responseJson.reason);
} else {
toast("Failed changing org. Try again or contact support@shuffler.io if this persists.");
}
}
})
.catch((error) => {
console.log("error changing: ", error);
//removeCookie("session_token", {path: "/"})
})
}}
>Active Organization</span> to edit this Workflow.
</Typography>
}
</div>
<div style={{display: "flex", marginLeft: 10, }}>
{parentWorkflows.slice(0,5).map((wf, index) => {
@@ -13744,9 +13805,64 @@ const AngularWorkflow = (defaultprops) => {
</div>
: null
const RightsideBar = () => {
const [hovered, setHovered] = useState(false)
useEffect(() => {
const handleKeyDown = (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === '/') {
event.preventDefault(); // Prevent default browser behavior (like opening search bar)
if (!workflow.public && !executionRequestStarted) {
executeWorkflow(executionText, workflow.start, lastSaved);
}
}
if ((event.ctrlKey || event.metaKey) && event.key === "'") {
// Check if Ctrl (Windows/Linux) or Command (Mac) key is pressed along with '/'
if (!workflow.public && !executionModalOpen) {
setExecutionModalOpen(true);
getWorkflowExecution(props.match.params.key, "");
} else if (!workflow.public && executionModalOpen) {
setExecutionModalOpen(false);
}
}
if ((event.ctrlKey || event.metaKey) && event.key === "]") {
console.log("Show workflow revisions key pressed")
if (!workflow.public) {
setShowWorkflowRevisions(true)
setSelectedRevision(workflow)
//setOriginalWorkflow(workflow)
}
}
if (( event.ctrlKey || event.metaKey ) && event.key === ";") {
if (!workflow.public && executionModalOpen) {
getWorkflowExecution(props.match.params.key, "");
}
}
if (( event.ctrlKey || event.metaKey ) && event.shiftKey) {
console.log("Shift key pressed")
if (!workflow.public && executionModalOpen) {
setExecutionRunning(false);
stop()
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem)
setExecutionModalView(0);
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}, [executeWorkflow, executionText, workflow, lastSaved, executionRequestStarted]);
return (
<div
style={{
@@ -13802,7 +13918,7 @@ const AngularWorkflow = (defaultprops) => {
</span>
</Tooltip>
) : (
<Tooltip color="primary" title="Test Execution" placement="top">
<Tooltip color="primary" title="Test Execution (Ctrl + /)" placement="top">
<span>
<Button
disabled={
@@ -14000,7 +14116,7 @@ const AngularWorkflow = (defaultprops) => {
</Tooltip>
<Tooltip
color="secondary"
title={`Show executions (${workflowExecutions.length})`}
title={`Show executions (${workflowExecutions.length}) (Ctrl + ')`}
placement="top-start"
>
<span>
@@ -14069,7 +14185,7 @@ const AngularWorkflow = (defaultprops) => {
</Tooltip>
<Tooltip
color="secondary"
title="Show Workflow Revision History (Beta)"
title="Show Workflow Revision History (Beta) (Ctrl + ])"
placement="top"
>
<span>
@@ -14080,8 +14196,8 @@ const AngularWorkflow = (defaultprops) => {
variant={"outlined"}
onClick={() => {
setShowWorkflowRevisions(true)
setSelectedRevision(workflow)
//setOriginalWorkflow(workflow)
setSelectedRevision(workflow)
//setOriginalWorkflow(workflow)
}}
>
<RestoreIcon />
@@ -14283,6 +14399,52 @@ const AngularWorkflow = (defaultprops) => {
//return null;
};
const unPublishWorkflow = (data) => {
if (!isCloud) {
toast("Function only supported on cloud")
return
}
if (data.public !== true) {
toast("Workflow is not public. Can't unpublish");
return
}
// This ALWAYS talks to Shuffle cloud
data = JSON.parse(JSON.stringify(data));
const url = `${globalUrl}/api/v1/workflows/${data.id}/unpublish`;
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflow publish :O!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.reason !== undefined) {
toast("Unpublishing: "+responseJson.reason)
}
if (responseJson.success === true) {
workflow.public = false
setWorkflow(workflow)
}
})
.catch((error) => {
toast("Failed publishing: is the workflow valid? Remember to save the workflow first.")
console.log(error.toString())
})
}
// This can execute a workflow with firestore. Used for test, as datastore is old and stuff
// Too much work to move everything over alone, so won't touch it for now
//<Button style={{borderRadius: "0px"}} color="primary" variant="contained" onClick={() => {
@@ -14538,8 +14700,8 @@ const AngularWorkflow = (defaultprops) => {
</Button>
<Button
color="secondary"
disabled
variant="outlined"
disabled={workflow.public !== true}
fullWidth
style={{ marginTop: 15, }}
onClick={() => {
@@ -14547,6 +14709,8 @@ const AngularWorkflow = (defaultprops) => {
// workflow.public = false
// setWorkflow(workflow)
// setUpdate(Math.random());
toast("Unpublishing this workflow from the search engine. It will remain public until it is deleted, as it is a copy of the original workflow.")
unPublishWorkflow(workflow)
}}
>
Unpublish Workflow
@@ -15112,6 +15276,7 @@ const AngularWorkflow = (defaultprops) => {
</a>
</Tooltip>
</div>
<Tooltip title="Refresh runs (Ctrl + ;)" arrow>
<Button
style={{ borderRadius: "0px" }}
variant="outlined"
@@ -15124,6 +15289,7 @@ const AngularWorkflow = (defaultprops) => {
<CachedIcon style={{ marginRight: 10 }} />
Refresh Runs
</Button>
</Tooltip>
<Divider
style={{
backgroundColor: "rgba(255,255,255,0.5)",
@@ -15351,48 +15517,51 @@ const AngularWorkflow = (defaultprops) => {
</div>
) : (
<div style={{ padding: isMobile ? "0px 10px 25px 10px" : "25px 15px 25px 15px", maxWidth: isMobile ? "100%" : 400, overflowX: "hidden" }}>
<Breadcrumbs
aria-label="breadcrumb"
separator=""
style={{ color: "white", fontSize: 16 }}
>
<span
style={{ color: "rgba(255,255,255,0.5)", display: "flex" }}
onClick={() => {
setExecutionRunning(false);
stop();
getWorkflowExecution(props.match.params.key, "");
setExecutionModalView(0);
setLastExecution(executionData.execution_id);
}}
<Breadcrumbs
aria-label="breadcrumb"
separator=""
style={{ color: "white", fontSize: 16 }}
>
<IconButton
style={{
paddingLeft: 0,
marginTop: "auto",
marginBottom: "auto",
}}
onClick={() => {
setExecutionRunning(false);
stop()
}}
>
<ArrowBackIcon style={{ color: "rgba(255,255,255,0.5)" }} />
</IconButton>
<h2
style={{ color: "rgba(255,255,255,0.5)", cursor: "pointer" }}
onClick={() => {
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem)
setExecutionRunning(false);
stop()
}}
>
See more runs
</h2>
</span>
</Breadcrumbs>
<span
style={{ color: "rgba(255,255,255,0.5)", display: "flex" }}
onClick={() => {
setExecutionRunning(false);
stop();
getWorkflowExecution(props.match.params.key, "");
setExecutionModalView(0);
setLastExecution(executionData.execution_id);
}}
>
<IconButton
style={{
paddingLeft: 0,
marginTop: "auto",
marginBottom: "auto",
}}
onClick={() => {
setExecutionRunning(false);
stop()
}}
>
<ArrowBackIcon style={{ color: "rgba(255,255,255,0.5)" }} />
</IconButton>
<Tooltip title="See more runs (Ctrl + Shift)" arrow>
<h2
style={{ color: "rgba(255,255,255,0.5)", cursor: "pointer" }}
onClick={() => {
const cursearch = typeof window === "undefined" || window.location === undefined ? "" : window.location.search;
const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem)
setExecutionRunning(false);
stop()
}}
>
See more runs
</h2>
</Tooltip>
</span>
</Breadcrumbs>
<Divider
style={{
backgroundColor: "rgba(255,255,255,0.6)",
@@ -15456,15 +15625,19 @@ const AngularWorkflow = (defaultprops) => {
color="primary"
title="Explore logs for the workflow"
placement="top"
style={{ zIndex: 50000 }}
style={{ zIndex: 50000, }}
>
<span style={{}}>
<Button
color="primary"
style={{ float: "right", marginTop: 20, marginLeft: 10 }}
disabled={!userdata.support}
disabled={userdata.region_url !== "https://shuffler.io"}
onClick={() => {
window.open(`/api/v1/workflows/search/${executionData.execution_id}`, "_blank")
toast("Opening logs in a new tab")
setTimeout(() => {
window.open(`/api/v1/workflows/search/${executionData.execution_id}`, "_blank")
}, 250)
}}
>
<InsightsIcon color="secondary" />
@@ -17855,42 +18028,45 @@ const AngularWorkflow = (defaultprops) => {
return null
}
var newrevision = JSON.parse(JSON.stringify(revision))
// Make unix timestamp into ISO timestamp in the format July 27th, 3:05 AM
// Format: July 27th, 3:05 AM
console.log("Edited time: ", revision.edited)
//console.log("Edited time: ", revision.edited)
// Convert 1692128391 to valid timestamp
const validTimestamp = revision.edited.toString().length === 10 ? revision.edited * 1000 : revision.edited
const validTimestamp = newrevision.edited.toString().length === 10 ? newrevision.edited * 1000 : newrevision.edited
const translatedDate = new Date(validTimestamp).toLocaleString('en-US', { month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true })
var workflowStatus = revision.status !== undefined && revision.status !== null && revision.status !== "" ? revision.status : "test"
if (revision.name !== undefined && revision.name !== null && revision.name !== "") {
if (revision.name.toLowerCase().includes("test")) {
var workflowStatus = newrevision.status !== undefined && newrevision.status !== null && newrevision.status !== "" ? newrevision.status : "test"
if (newrevision.name !== undefined && newrevision.name !== null && newrevision.name !== "") {
if (newrevision.name.toLowerCase().includes("test")) {
workflowStatus = "test"
}
if (revision.name.toLowerCase().includes("dev") || revision.name.toLowerCase().includes("staging") || revision.name.toLowerCase().includes("rollback")) {
if (newrevision.name.toLowerCase().includes("dev") || newrevision.name.toLowerCase().includes("staging") || newrevision.name.toLowerCase().includes("rollback")) {
workflowStatus = "dev"
}
if (revision.name.toLowerCase().includes("prod") || revision.name.toLowerCase().includes("main")) {
if (newrevision.name.toLowerCase().includes("prod") || newrevision.name.toLowerCase().includes("main")) {
workflowStatus = "prod"
}
}
return (
<Paper
style={{padding: "15px 15px 15px 25px", minHeight: 105, maxHeight: 105, cursor: "pointer", backgroundColor: revision.edited === selectedRevision.edited ? "rgba(255,255,255,0.3)" : theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.3)", marginBottom: 10,
style={{padding: "15px 15px 15px 25px", minHeight: 105, maxHeight: 105, cursor: "pointer", backgroundColor: newrevision.edited === selectedRevision.edited ? "rgba(255,255,255,0.3)" : theme.palette.surfaceColor, border: "1px solid rgba(255,255,255,0.3)", marginBottom: 10,
}} onClick={(e) => {
if (revision.edited === selectedRevision.edited) {
if (newrevision.edited === selectedRevision.edited) {
console.log("Same revision! No setting.")
return
}
// Should render if it's not the same as workflow.edited
console.log("Clicked revision: ", revision)
console.log("Clicked revision: ", newrevision)
setLastSaved(false)
setSelectedRevision(revision)
setWorkflow(revision)
setSelectedRevision(newrevision)
setWorkflow(newrevision)
setSelectedAction({});
setSelectedApp({})
@@ -17910,7 +18086,7 @@ const AngularWorkflow = (defaultprops) => {
cy.removeListener("cxttap");
}
setupGraph(revision)
setupGraph(newrevision)
// Re-adding cytoscape triggers
if (cy !== undefined && cy !== null) {
@@ -17936,8 +18112,20 @@ const AngularWorkflow = (defaultprops) => {
cy.on("free", "node", (e) => onNodeDragStop(e, selectedAction));
cy.on("cxttap", "node", (e) => onCtxTap(e));
if (selectedAction.id !== undefined && selectedAction.id !== null && selectedAction.id !== "") {
setTimeout(() => {
const foundaction = cy.$id(selectedAction.id)
if (foundaction !== undefined && foundaction !== null) {
foundaction.select()
}
}, 250)
}
}
// Need to run through graph setup with this one
}}>
<div style={{display: "flex", }}>
+23 -19
View File
@@ -1,16 +1,11 @@
import React, { useState, useEffect } from "react";
import { useInterval } from "react-powerhooks";
import AppFramework from "../components/AppFramework.jsx";
import { makeStyles, } from "@mui/styles";
// nodejs library that concatenates classes
import classNames from "classnames";
import theme from '../theme.jsx';
import { useNavigate, Link, useParams } from "react-router-dom";
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx";
// react plugin used to create charts
//import { Line, Bar } from "react-chartjs-2";
//import { useAlert
import React, { useState, useEffect } from "react"
import { useInterval } from "react-powerhooks"
import AppFramework from "../components/AppFramework.jsx"
import { makeStyles, } from "@mui/styles"
import classNames from "classnames"
import theme from '../theme.jsx'
import { useNavigate, Link, useParams } from "react-router-dom"
import WorkflowTemplatePopup from "../components/WorkflowTemplatePopup.jsx"
import { ToastContainer, toast } from "react-toastify"
import { parsedDatatypeImages } from "../components/AppFramework.jsx"
import { findSpecificApp } from "../components/AppFramework.jsx"
@@ -96,7 +91,8 @@ const useStyles = makeStyles({
const UsecaseListComponent = (props) => {
const { keys, userdata, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows, } = props
const { keys, userdata, isCloud, globalUrl, frameworkData, isLoggedIn, workflows, setWorkflows, getFramework, setFrameworkData, } = props
const [expandedIndex, setExpandedIndex] = useState(-1);
const [expandedItem, setExpandedItem] = useState(-1);
@@ -114,11 +110,13 @@ const UsecaseListComponent = (props) => {
const [firstLoad, setFirstLoad] = useState(true)
const [apps, setApps] = useState([])
const classes = useStyles();
let navigate = useNavigate();
const [mitreTags, setMitreTags] = useState([]);
const parseUsecase = (subcase) => {
const srcdata = findSpecificApp(frameworkData, subcase.type)
const dstdata = findSpecificApp(frameworkData, subcase.last)
@@ -137,13 +135,11 @@ const UsecaseListComponent = (props) => {
}
useEffect(() => {
console.log("In frameworkData useEffect: frameworkData: ", frameworkData)
//console.log("Frameworkdata changed. Use to set inputUsecase: ", frameworkData, prevSubcase)
if (frameworkData === undefined || prevSubcase === undefined) {
return
}
console.log("PAST!")
var parsedUsecase = inputUsecase
const subcase = parseUsecase(prevSubcase)
@@ -917,6 +913,9 @@ const UsecaseListComponent = (props) => {
description={inputUsecase.description}
apps={apps}
getAppFramework={getFramework}
//appSetupDone={appSetupDone}
//setAppSetupDone={setAppSetupDone}
/>
: null}
</span>
@@ -990,13 +989,15 @@ const UsecaseListComponent = (props) => {
}}>
<AppFramework
inputUsecase={inputUsecase}
frameworkData={frameworkData}
selectedOption={"Draw"}
showOptions={false}
isLoaded={true}
isLoggedIn={true}
globalUrl={globalUrl}
size={0.6}
frameworkData={frameworkData}
setFrameworkData={setFrameworkData}
/>
</div>
</div>
@@ -1713,12 +1714,15 @@ const Dashboard = (props) => {
<UsecaseListComponent
userdata={userdata}
isLoggedIn={isLoggedIn}
frameworkData={frameworkData}
keys={selectedUsecases}
isCloud={isCloud}
globalUrl={globalUrl}
workflows={workflows}
setWorkflows={setWorkflows}
frameworkData={frameworkData}
setFrameworkData={setFrameworkData}
getFramework={getFramework}
/>
{treeKeys.length > 0 ?
+61 -4
View File
@@ -13,6 +13,11 @@ import {
//import { useAlert
import { ToastContainer, toast } from "react-toastify"
import { FileCopy, Visibility, VisibilityOff } from "@mui/icons-material";
import IconButton from "@mui/material/IconButton";
import { Tooltip } from "@mui/material";
const Settings = (props) => {
const { globalUrl, isLoaded, userdata, setUserData } = props;
//const alert = useAlert();
@@ -44,6 +49,17 @@ const Settings = (props) => {
const [userSettings, setUserSettings] = useState({});
const [showApiKey, setShowApiKey] = useState(false);
const [apiKeyCopied, setApiKeyCopied] = useState(false);
const handleCopyApiKey = () => {
navigator.clipboard.writeText(userSettings.apikey);
setApiKeyCopied(true);
setTimeout(() => {
setApiKeyCopied(false);
}, 2000);
}
/*
const [userdata.eth_info, setEthInfo] = useState(userdata.eth_info !== undefined && userdata.eth_info.account !== undefined && userdata.eth_info.account.length > 0 ? userdata.eth_info : {
"account": "",
@@ -467,7 +483,7 @@ const Settings = (props) => {
>
What is the API key used for?
</a>
<TextField
{/* <TextField
style={{ backgroundColor: theme.palette.inputColor, flex: "1" }}
InputProps={{
style: {
@@ -484,7 +500,43 @@ const Settings = (props) => {
id="standard-required"
margin="normal"
variant="outlined"
/>
/> */}
<TextField
style={{ backgroundColor: theme.palette.inputColor, flex: "1" }}
InputProps={{
style: {
height: "50px",
color: "white",
},
endAdornment: (
<>
<Tooltip title={showApiKey ? "Hide API Key" : "Show API Key"}>
<IconButton
onClick={() => setShowApiKey(!showApiKey)}
>
{showApiKey ? <VisibilityOff /> : <Visibility />}
</IconButton>
</Tooltip>
<Tooltip title={apiKeyCopied ? "API Key copied!" : "Copy API Key"}>
<IconButton
onClick={handleCopyApiKey}
>
<FileCopy />
</IconButton>
</Tooltip>
</>
),
}}
color="primary"
value={showApiKey ? userSettings.apikey : '*'.repeat(36)} // Show API key if showApiKey is true, else show asterisks
required
disabled
fullWidth
placeholder="APIKEY"
id="standard-required"
margin="normal"
variant="outlined"
/>
<Button
style={{ width: "100%", height: "40px", marginTop: "10px" }}
variant="outlined"
@@ -676,8 +728,13 @@ const Settings = (props) => {
Submit password change
</Button>
<h3>{passwordFormMessage}</h3>
<Divider style={{ marginTop: "40px" }} />
<h2>Creator Incentive Program</h2>
{isCloud && (
<>
<Divider style={{ marginTop: "40px" }} />
<h2>Creator Incentive Program</h2>
</>
)}
<div style={{ display: runFlex ? "flex" : "", width: "100%" }}>
<div>
{isCloud ?
+5 -7
View File
@@ -551,9 +551,7 @@ const Workflows = (props) => {
const [field1, setField1] = React.useState("");
const [field2, setField2] = React.useState("");
const [downloadUrl, setDownloadUrl] = React.useState(
"https://github.com/frikky/shuffle-workflows"
);
const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/workflows")
const [downloadBranch, setDownloadBranch] = React.useState("master");
const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] =
React.useState(false);
@@ -924,7 +922,7 @@ const Workflows = (props) => {
>
<DialogTitle>
<div style={{ textAlign: "center", color: "rgba(255,255,255,0.9)" }}>
Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId).name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}? <div />
Are you sure you want to delete {selectedWorkflowId.length > 0 ? filteredWorkflows.find((w) => w.id === selectedWorkflowId)?.name : `${selectedWorkflowIndexes.length} workflow${selectedWorkflowIndexes.length === 1 ? '' : 's'}`}? <div />
Other workflows relying on {selectedWorkflowIndexes.length > 0 ? "them" : "it"} one will stop working
</div>
@@ -3630,7 +3628,7 @@ const Workflows = (props) => {
const handleGithubValidation = () => {
importWorkflowsFromUrl(downloadUrl);
setLoadWorkflowsModalOpen(false);
};
}
const workflowDownloadModalOpen = loadWorkflowsModalOpen ? (
<Dialog
@@ -3677,11 +3675,11 @@ const Workflows = (props) => {
},
}}
onChange={(e) => setDownloadUrl(e.target.value)}
placeholder="https://github.com/frikky/shuffle-apps"
placeholder="https://github.com/shuffle/workflows"
fullWidth
/>
<span style={{ marginTop: 10 }}>
Branch (default value is "master"):
Branch (default value is "main"):
</span>
<div style={{ display: "flex" }}>
<TextField