MSSP multi tenant workflow features ayy

This commit is contained in:
Frikky
2024-06-17 01:39:52 +02:00
parent fe43b683b9
commit 3f0ed868cc
14 changed files with 1064 additions and 364 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ require (
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/h2non/filetype v1.1.3 github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0 github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.6.40 github.com/shuffle/shuffle-shared v0.6.46
golang.org/x/crypto v0.22.0 golang.org/x/crypto v0.22.0
google.golang.org/api v0.176.1 google.golang.org/api v0.176.1
google.golang.org/grpc v1.63.2 google.golang.org/grpc v1.63.2
+4
View File
@@ -303,6 +303,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -443,6 +445,8 @@ github.com/shuffle/shuffle-shared v0.6.27 h1:q4qZD6bGZFIvZ5Y10unGr3N3rZ7OryWyvva
github.com/shuffle/shuffle-shared v0.6.27/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/shuffle/shuffle-shared v0.6.27/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/shuffle/shuffle-shared v0.6.31 h1:MK1SW1pwjIP7hznq+mMlTPM1R3LIOfi1/bUL5xFSg/8= github.com/shuffle/shuffle-shared v0.6.31 h1:MK1SW1pwjIP7hznq+mMlTPM1R3LIOfi1/bUL5xFSg/8=
github.com/shuffle/shuffle-shared v0.6.31/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4= github.com/shuffle/shuffle-shared v0.6.31/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/shuffle/shuffle-shared v0.6.46 h1:v/IXc+4V8DCWflGKGgaI37uuGnsylMjqKRylwNoXVrA=
github.com/shuffle/shuffle-shared v0.6.46/go.mod h1:rWkh1eWdIx7OqQzJ1+JzF3Hck1X/Ty1WkUtjLrp+CU4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
+2
View File
@@ -3382,6 +3382,8 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
return return
} }
debugUrl := fmt.Sprintf("/workflows/%s?execution_id=%s", workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
resp.Header().Add("X-Debug-Url", debugUrl)
workflowExecution.Priority = 11 workflowExecution.Priority = 11
environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id) environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id)
+92 -58
View File
@@ -44,9 +44,9 @@ const Files = (props) => {
const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props; const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props;
const [files, setFiles] = React.useState([]); const [files, setFiles] = React.useState([]);
const [selectedNamespace, setSelectedNamespace] = React.useState("default"); const [selectedCategory, setSelectedCategory] = React.useState("default");
const [openFileId, setOpenFileId] = React.useState(false); const [openFileId, setOpenFileId] = React.useState(false);
const [fileNamespaces, setFileNamespaces] = React.useState([]); const [fileCategories, setFileCategories] = React.useState([]);
const [fileContent, setFileContent] = React.useState(""); const [fileContent, setFileContent] = React.useState("");
const [openEditor, setOpenEditor] = React.useState(false); const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false); const [renderTextBox, setRenderTextBox] = React.useState(false);
@@ -57,6 +57,7 @@ const Files = (props) => {
const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards") const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/shuffle/standards")
const [downloadBranch, setDownloadBranch] = React.useState("main"); const [downloadBranch, setDownloadBranch] = React.useState("main");
const [downloadFolder, setDownloadFolder] = React.useState("translation_standards"); const [downloadFolder, setDownloadFolder] = React.useState("translation_standards");
const [contentLoading, setContentLoading] = React.useState(false)
//const alert = useAlert(); //const alert = useAlert();
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1", "response"] const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv", "log", "eml", "msg", "md", "xml", "sh", "bat", "ps1", "psm1", "psd1", "ps1xml", "pssc", "psc1", "response"]
@@ -67,8 +68,8 @@ const Files = (props) => {
console.log('do validate') console.log('do validate')
console.log("new namespace name->",event.target.value); console.log("new namespace name->",event.target.value);
fileNamespaces.push(event.target.value); fileCategories.push(event.target.value);
setSelectedNamespace(event.target.value); setSelectedCategory(event.target.value);
setRenderTextBox(false); setRenderTextBox(false);
} }
@@ -107,12 +108,13 @@ const Files = (props) => {
const getFiles = (namespace) => { const getFiles = (namespace) => {
var parsedurl = `${globalUrl}/api/v1/files` var parsedurl = `${globalUrl}/api/v1/files`
if (namespace === undefined || namespace === "default") {
if (namespace === undefined || namespace === null || namespace === "default") {
} else if (namespace !== undefined && namespace !== null && namespace !== "") { } else if (namespace !== undefined && namespace !== null && namespace !== "") {
parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true` parsedurl = `${globalUrl}/api/v1/files/namespaces/${namespace}?ids=true`
} else if (selectedNamespace !== undefined && selectedNamespace !== null && selectedNamespace !== "default" && selectedNamespace !== "") { } else if (selectedCategory !== undefined && selectedCategory !== null && selectedCategory !== "default" && selectedCategory !== "") {
parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedNamespace}?ids=true` parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedCategory}?ids=true`
} }
fetch(parsedurl, { fetch(parsedurl, {
@@ -149,9 +151,11 @@ const Files = (props) => {
setFiles([]); setFiles([]);
} }
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileNamespaces.length === 0 || responseJson.namespaces.length > fileNamespaces.length)) { if (namespace === undefined || namespace === null || namespace === "default") {
setFileNamespaces(responseJson.namespaces); if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileCategories.length === 0 || responseJson.namespaces.length > fileCategories.length)) {
} setFileCategories(responseJson.namespaces)
}
}
}) })
.catch((error) => { .catch((error) => {
toast(error.toString()); toast(error.toString());
@@ -159,7 +163,21 @@ const Files = (props) => {
}; };
useEffect(() => { useEffect(() => {
getFiles(selectedNamespace) getFiles("default")
setTimeout(() => {
var category = selectedCategory
if (window.location.search.includes("category=")) {
const urlParams = new URLSearchParams(window.location.search)
category = urlParams.get("category")
}
if (category !== undefined && category !== null && category.length > 0 && category !== "default") {
setSelectedCategory(category)
}
getFiles(category)
}, 1000)
}, []); }, []);
const importStandardsFromUrl = (url, folder) => { const importStandardsFromUrl = (url, folder) => {
@@ -393,7 +411,7 @@ const Files = (props) => {
toast("Failed to delete file: " + responseJson.reason); toast("Failed to delete file: " + responseJson.reason);
} }
setTimeout(() => { setTimeout(() => {
getFiles(); getFiles(selectedCategory)
}, 1500); }, 1500);
}) })
.catch((error) => { .catch((error) => {
@@ -402,6 +420,8 @@ const Files = (props) => {
}; };
const readFileData = (file) => { const readFileData = (file) => {
setContentLoading(true)
fetch(globalUrl + "/api/v1/files/" + file.id + "/content", { fetch(globalUrl + "/api/v1/files/" + file.id + "/content", {
method: "GET", method: "GET",
headers: { headers: {
@@ -411,6 +431,7 @@ const Files = (props) => {
credentials: "include", credentials: "include",
}) })
.then((response) => { .then((response) => {
setContentLoading(false)
if (response.status !== 200) { if (response.status !== 200) {
console.log("Status not 200 for file :O!"); console.log("Status not 200 for file :O!");
return ""; return "";
@@ -428,12 +449,12 @@ const Files = (props) => {
return respdata return respdata
}) })
.then((responseData) => { .then((responseData) => {
setFileContent(responseData);
setFileContent(responseData); //console.log("filecontent state ",fileContent);
//console.log("filecontent state ",fileContent);
}) })
.catch((error) => { .catch((error) => {
toast(error.toString()); setContentLoading(false)
toast(error.toString())
}); });
}; };
@@ -505,12 +526,12 @@ const Files = (props) => {
}; };
if ( if (
selectedNamespace !== undefined && selectedCategory !== undefined &&
selectedNamespace !== null && selectedCategory !== null &&
selectedNamespace.length > 0 && selectedCategory.length > 0 &&
selectedNamespace !== "default" selectedCategory !== "default"
) { ) {
data.namespace = selectedNamespace; data.namespace = selectedCategory;
} }
fetch(globalUrl + "/api/v1/files/create", { fetch(globalUrl + "/api/v1/files/create", {
@@ -689,9 +710,9 @@ const Files = (props) => {
{fileNamespaces !== undefined && {fileCategories !== undefined &&
fileNamespaces !== null && fileCategories !== null &&
fileNamespaces.length > 1 ? ( fileCategories.length > 1 ? (
<FormControl style={{ minWidth: 150, maxWidth: 150 }}> <FormControl style={{ minWidth: 150, maxWidth: 150 }}>
<InputLabel id="input-namespace-label">File Category</InputLabel> <InputLabel id="input-namespace-label">File Category</InputLabel>
<Select <Select
@@ -703,18 +724,26 @@ const Files = (props) => {
maxWidth: 150, maxWidth: 150,
float: "right", float: "right",
}} }}
value={selectedNamespace} value={selectedCategory}
onChange={(event) => { onChange={(event) => {
setSelectedNamespace(event.target.value); setSelectedCategory(event.target.value)
if (event.target.value === "all" || event.target.value === "default") { if (event.target.value === "all" || event.target.value === "default") {
getFiles() getFiles()
} else { } else {
getFiles(event.target.value) getFiles(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}`)
}
}} }}
> >
{fileNamespaces.map((data, index) => { {fileCategories.map((data, index) => {
return ( return (
<MenuItem <MenuItem
key={index} key={index}
@@ -731,31 +760,31 @@ const Files = (props) => {
<div style={{display: "inline-flex", position:"relative"}}> <div style={{display: "inline-flex", position:"relative"}}>
{renderTextBox ? {renderTextBox ?
<Tooltip title={"Close"} style={{}} aria-label={""}> <Tooltip title={"Close"} style={{}} aria-label={""}>
<Button <Button
style={{ marginLeft: 5, marginRight: 15 }} style={{ marginLeft: 5, marginRight: 15 }}
color="primary" color="primary"
onClick={() => { onClick={() => {
setRenderTextBox(false); setRenderTextBox(false);
console.log(" close clicked") console.log(" close clicked")
}} }}
> >
<ClearIcon/> <ClearIcon/>
</Button> </Button>
</Tooltip> </Tooltip>
: :
<Tooltip title={"Add new file category"} style={{}} aria-label={""}> <Tooltip title={"Add new file category"} style={{}} aria-label={""}>
<Button <Button
style={{ marginLeft: 5, marginRight: 15 }} style={{ marginLeft: 5, marginRight: 15 }}
color="primary" color="primary"
onClick={() => { onClick={() => {
setRenderTextBox(true); setRenderTextBox(true);
}} }}
> >
<AddIcon/> <AddIcon/>
</Button> </Button>
</Tooltip> } </Tooltip>
}
{renderTextBox && <TextField {renderTextBox && <TextField
onKeyPress={(event)=>{ onKeyPress={(event)=>{
@@ -783,6 +812,7 @@ const Files = (props) => {
isFileEditor = {true} isFileEditor = {true}
key = {fileContent} //https://reactjs.org/docs/reconciliation.html#recursing-on-children key = {fileContent} //https://reactjs.org/docs/reconciliation.html#recursing-on-children
runUpdateText = {runUpdateText} runUpdateText = {runUpdateText}
contentLoading = {contentLoading}
/> />
{isSelectedFiles?null: {isSelectedFiles?null:
<Divider <Divider
@@ -795,15 +825,17 @@ const Files = (props) => {
<List style={{borderRadius: isSelectedFiles?8:null, border:isSelectedFiles?"1px solid #494949":null, marginTop:isSelectedFiles?24:null}}> <List style={{borderRadius: isSelectedFiles?8:null, border:isSelectedFiles?"1px solid #494949":null, marginTop:isSelectedFiles?24:null}}>
<ListItem style={{width:isSelectedFiles?"100%":null, borderBottom:isSelectedFiles?"1px solid #494949":null}}> <ListItem style={{width:isSelectedFiles?"100%":null, borderBottom:isSelectedFiles?"1px solid #494949":null}}>
{/*
<ListItemText <ListItemText
primary="Updated" primary="Updated"
style={{ maxWidth: 185, minWidth: 185 }} style={{ maxWidth: 185, minWidth: 185 }}
/> />
*/}
<ListItemText <ListItemText
primary="Name" primary="Name"
style={{ style={{
maxWidth: 150, maxWidth: 250,
minWidth: 150, minWidth: 250,
overflow: "hidden", overflow: "hidden",
marginLeft: 10, marginLeft: 10,
}} }}
@@ -814,7 +846,7 @@ const Files = (props) => {
/> />
<ListItemText <ListItemText
primary="Md5" primary="Md5"
style={{ minWidth: isSelectedFiles?210:250, maxWidth: isSelectedFiles?210:250, overflow: "hidden" }} style={{ minWidth: isSelectedFiles ? 210:250, maxWidth: isSelectedFiles?210:250, overflow: "hidden" }}
/> />
<ListItemText <ListItemText
primary="Status" primary="Status"
@@ -832,7 +864,7 @@ const Files = (props) => {
file.namespace = "default"; file.namespace = "default";
} }
if (file.namespace !== selectedNamespace) { if (file.namespace !== selectedCategory) {
return null; return null;
} }
@@ -853,6 +885,7 @@ const Files = (props) => {
overflow: "hidden", overflow: "hidden",
}} }}
> >
{/*
<ListItemText <ListItemText
style={{ style={{
maxWidth: isSelectedFiles ? 170:225, maxWidth: isSelectedFiles ? 170:225,
@@ -861,10 +894,11 @@ const Files = (props) => {
}} }}
primary={new Date(file.updated_at * 1000).toISOString()} primary={new Date(file.updated_at * 1000).toISOString()}
/> />
*/}
<ListItemText <ListItemText
style={{ style={{
maxWidth: 150, maxWidth: 250,
minWidth: 150, minWidth: 250,
overflow: "hidden", overflow: "hidden",
marginLeft: 10, marginLeft: 10,
}} }}
+2 -6
View File
@@ -546,7 +546,7 @@ const Header = (props) => {
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >
<Tooltip color="primary" title={"Join the Shuffle Automation Community"} placement="left"> <Tooltip color="primary" title={"Shuffle is Open Source, and has a thriving Discord Automation Community"} placement="left">
<IconButton <IconButton
color="primary" color="primary"
style={{}} style={{}}
@@ -554,11 +554,7 @@ const Header = (props) => {
aria-haspopup="true" aria-haspopup="true"
onClick={(event) => { }} onClick={(event) => { }}
> >
<img <svg viewBox="0 0 256 199" width="256" height="199" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" style={{height: 30, width: 30, }}><path d="M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z" fill="#5865F2"/></svg>
alt="Discord Community Join"
src={"/images/social/discord.png"}
style={{ height: 30, width: 30 }}
/>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</a> </a>
+9 -7
View File
@@ -1925,7 +1925,7 @@ const ParsedAction = (props) => {
}} }}
/> />
</Tooltip> </Tooltip>
<Tooltip title="Autocomplete text" placement="top"> <Tooltip title="Autocomplete text" placement="bottom">
<AddCircleOutlineIcon <AddCircleOutlineIcon
style={{ cursor: "pointer", margin: multiline ? 5 : 0, }} style={{ cursor: "pointer", margin: multiline ? 5 : 0, }}
onClick={(event) => { onClick={(event) => {
@@ -3078,7 +3078,6 @@ const ParsedAction = (props) => {
selectedApp.actions.filter((a) => selectedApp.actions.filter((a) =>
a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label")) a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label"))
).sort(sortByCategoryLabel)) ).sort(sortByCategoryLabel))
const selectedAppIcon = selectedAction.large_image const selectedAppIcon = selectedAction.large_image
var baselabel = selectedAction.label var baselabel = selectedAction.label
@@ -3156,9 +3155,11 @@ const ParsedAction = (props) => {
} }
if (!found) { if (!found) {
toast("No result for this action yet. Please run the workflow first.") toast.info("No result for this action yet. Please run the workflow first.")
} }
} } else {
toast.info("No workflow runs to search through. Run the workflow first.")
}
}} }}
> >
<Tooltip <Tooltip
@@ -3823,6 +3824,7 @@ const ParsedAction = (props) => {
</div> </div>
) : null} ) : null}
{showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? ( {showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? (
<div style={{ marginTop: "20px" }}> <div style={{ marginTop: "20px" }}>
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Environment</Typography> <Typography style={{color: "rgba(255,255,255,0.7)"}}>Environment</Typography>
@@ -3831,9 +3833,9 @@ const ParsedAction = (props) => {
disableScrollLock: true, disableScrollLock: true,
}} }}
value={ value={
selectedActionEnvironment === undefined || selectedActionEnvironment === null || selectedActionEnvironment === undefined || selectedActionEnvironment === null || selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null
selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null ?
? isCloud ? "Cloud" : "Shuffle" selectedAction.environment !== undefined && selectedAction.environment !== null && selectedAction.environment.length > 0 ? selectedAction.environment : isCloud ? "Cloud" : "Shuffle"
: selectedActionEnvironment.Name : selectedActionEnvironment.Name
} }
SelectDisplayProps={{ SelectDisplayProps={{
+18 -2
View File
@@ -27,8 +27,9 @@ const Priorities = (props) => {
const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT"); const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT");
const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT"); const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT");
let navigate = useNavigate(); const [highlightKMS, setHighlightKMS] = React.useState(false)
let navigate = useNavigate();
useEffect(() => { useEffect(() => {
getFramework() getFramework()
@@ -36,6 +37,12 @@ const Priorities = (props) => {
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
const workflow = urlParams.get("workflow") const workflow = urlParams.get("workflow")
const execution_id = urlParams.get("execution_id") const execution_id = urlParams.get("execution_id")
const kms = urlParams.get("kms")
if (kms !== null && kms !== undefined && kms.length > 0 && kms === "true") {
toast.info("KMS-related notifications are highlighted.")
setHighlightKMS(true)
}
if (execution_id !== null) { if (execution_id !== null) {
setSelectedExecutionId(execution_id) setSelectedExecutionId(execution_id)
@@ -217,7 +224,16 @@ const Priorities = (props) => {
var orgId = ""; var orgId = "";
const highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow) var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow)
if (!highlighted && highlightKMS) {
if (data.title !== undefined && data.title !== null && data.title.toLowerCase().includes("kms")) {
highlighted = true
} else if (data.description !== undefined && data.description !== null && data.description.toLowerCase().includes("kms")) {
highlighted = true
}
}
if (userdata.orgs !== undefined) { if (userdata.orgs !== undefined) {
const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]);
+12 -2
View File
@@ -107,10 +107,9 @@ const CodeEditor = (props) => {
getParents, getParents,
fieldname, fieldname,
contentLoading,
} = props } = props
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata); const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
//const { setContainer } = useCodeMirror({ //const { setContainer } = useCodeMirror({
@@ -989,6 +988,17 @@ const CodeEditor = (props) => {
}, },
}} }}
> >
{contentLoading === true ?
<Tooltip
color="primary"
title={`The File content is loading. Please wait a moment.`}
placement="top"
>
<CircularProgress style={{position: "absolute", right: 106, top: 6, }}/>
</Tooltip>
: null}
<Tooltip <Tooltip
color="primary" color="primary"
title={`Move window`} title={`Move window`}
+420 -234
View File
@@ -44,6 +44,7 @@ import {
Box, Box,
InputAdornment, InputAdornment,
Autocomplete, Autocomplete,
Modal
} from "@mui/material"; } from "@mui/material";
import { import {
@@ -164,7 +165,7 @@ const Admin = (props) => {
const [selectedOrganization, setSelectedOrganization] = React.useState({}); const [selectedOrganization, setSelectedOrganization] = React.useState({});
//console.log("Selected: ", selectedOrganization) //console.log("Selected: ", selectedOrganization)
const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false); const [appAuthenticationGroupModalOpen, setAppAuthenticationGroupModalOpen] = React.useState(false);
const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]); const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]);
const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState(""); const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState("");
const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState(""); const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState("");
@@ -212,6 +213,13 @@ const Admin = (props) => {
const [allSchedules, setAllSchedules] = React.useState([]); const [allSchedules, setAllSchedules] = React.useState([]);
const [pipelines, setPipelines] = React.useState([]); const [pipelines, setPipelines] = React.useState([]);
const [, forceUpdate] = React.useState(); const [, forceUpdate] = React.useState();
const [MFARequired, setMFARequired] = React.useState(selectedOrganization.mfa_required === undefined ? false : selectedOrganization.mfa_required);
useEffect(() => {
if (selectedOrganization.mfa_required !== undefined) {
setMFARequired(selectedOrganization.mfa_required);
}
}, [selectedOrganization.mfa_required]);
const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] = const [showDeleteAccountTextbox, setShowDeleteAccountTextbox] =
React.useState(false); React.useState(false);
@@ -1097,6 +1105,7 @@ If you're interested, please let me know a time that works for you, or set up a
code: code, code: code,
user_id: userId, user_id: userId,
}; };
toast("Verifying 2fa code. Please wait...");
fetch(`${globalUrl}/api/v1/users/${userId}/set2fa`, { fetch(`${globalUrl}/api/v1/users/${userId}/set2fa`, {
mode: "cors", mode: "cors",
@@ -1120,11 +1129,15 @@ If you're interested, please let me know a time that works for you, or set up a
}) })
.then((responseJson) => { .then((responseJson) => {
if (responseJson.success === true) { if (responseJson.success === true) {
toast("Successfully enabled 2fa"); if (responseJson.MFAActive === true) {
toast.success("Successfully enabled 2fa");
}
if (responseJson.MFAActive === false) {
toast.success("Successfully disabled 2fa");
}
setTimeout(() => { setTimeout(() => {
getUsers(); getUsers();
setImage2FA(""); setImage2FA("");
setValue2FA(""); setValue2FA("");
setSecret2FA(""); setSecret2FA("");
@@ -2922,8 +2935,8 @@ If you're interested, please let me know a time that works for you, or set up a
color="primary" color="primary"
> >
{selectedUser.mfa_info !== undefined && {selectedUser.mfa_info !== undefined &&
selectedUser.mfa_info !== null && selectedUser.mfa_info !== null &&
selectedUser.mfa_info.active === true selectedUser.mfa_info.active === true
? "Disable 2FA" ? "Disable 2FA"
: "Enable 2FA"} : "Enable 2FA"}
</Button> </Button>
@@ -3009,15 +3022,16 @@ If you're interested, please let me know a time that works for you, or set up a
maxWidth: 300, maxWidth: 300,
minWidth: 300, minWidth: 300,
marginTop: 25, marginTop: 25,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}} }}
> >
{/*<Divider style={{marginTop: 20, marginBottom: 20}} />*/}
{secret2FA !== undefined && {secret2FA !== undefined &&
secret2FA !== null && secret2FA !== null &&
secret2FA.length > 0 ? ( secret2FA.length > 0 ? (
<span> <span>
<Typography variant="body2" color="textSecondary"> <Typography variant="body2" color="textSecondary" style={{ textAlign: 'left' }}>
Scan the image below with the two-factor authentication app on Scan the image below with the two-factor authentication app on
your phone. If you cant use a QR code, use the code{" "} your phone. If you cant use a QR code, use the code{" "}
{secret2FA} instead. {secret2FA} instead.
@@ -3025,18 +3039,16 @@ If you're interested, please let me know a time that works for you, or set up a
</span> </span>
) : null} ) : null}
{image2FA !== undefined && {image2FA !== undefined &&
image2FA !== null && image2FA !== null &&
image2FA.length > 0 ? ( image2FA.length > 0 ? (
<img <img
alt={"2 factor img"} alt="2 factor img"
src={image2FA} src={image2FA}
style={{ style={{
margin: "auto", margin: "15px auto",
marginTop: 25,
maxHeight: 200, maxHeight: 200,
maxWidth: 200, maxWidth: 200,
minWidth: 200, display: 'block',
maxWidth: 200,
}} }}
/> />
) : ( ) : (
@@ -3047,7 +3059,7 @@ If you're interested, please let me know a time that works for you, or set up a
After scanning the QR code image, the app will display a code that After scanning the QR code image, the app will display a code that
you can enter below. you can enter below.
</Typography> </Typography>
<div style={{ display: "flex" }}> <div style={{ display: "flex", width: '100%', marginTop: 10 }}>
<TextField <TextField
color="primary" color="primary"
style={{ style={{
@@ -3076,13 +3088,18 @@ If you're interested, please let me know a time that works for you, or set up a
setValue2FA(event.target.value); setValue2FA(event.target.value);
}} }}
onKeyPress={(event) => {
if (event.key === 'Enter' && event.target.value.length === 6) {
handleVerify2FA(userdata.id, event.target.value, false);
}
}}
/> />
<Button <Button
disabled={value2FA.length !== 6} disabled={value2FA.length !== 6}
variant="contained" variant="contained"
style={{ marginTop: 15, height: 50, flex: 1 }} style={{ marginTop: 15, height: 50, flex: 1 }}
onClick={() => { onClick={() => {
handleVerify2FA(userdata.id, value2FA); handleVerify2FA(userdata.id, value2FA, false);
}} }}
color="primary" color="primary"
> >
@@ -4225,40 +4242,101 @@ If you're interested, please let me know a time that works for you, or set up a
</Dialog> </Dialog>
); );
const UpdateMFAInUserOrg = (org_id) => {
if (MFARequired === false) {
toast("Making MFA required for your organization. Please wait...");
} else {
toast("Making MFA optional for your organization. Please wait...");
}
const data = {
mfa_required: !selectedOrganization.mfa_required,
org_id: selectedOrganization.id,
}
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
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) => {
console.log(responseJson)
if (responseJson["success"] === false) {
toast.error("Failed updating org: ", responseJson.reason);
} else {
if (MFARequired === false) {
setMFARequired(true)
toast.success("Successfully make MFA required for your organization!");
} else {
setMFARequired(false)
toast.success("Successfully make MFA optional for your organization!")
}
}
}),
)
.catch((error) => {
toast("Err: " + error.toString());
});
}
const usersView = const usersView =
curTab === 1 ? ( curTab === 1 ? (
<div> <div>
<div style={{ marginTop: 20, marginBottom: 20 }}> <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', marginTop: 20, marginBottom: 20 }}>
<h2 style={{ display: "inline" }}>User Management</h2> <div style={{ display: 'flex', flexDirection: 'column', }}>
<span style={{ marginLeft: 25 }}> <div>
Add, edit, block or change passwords.{" "} <h2 style={{ display: "inline" }}>User Management</h2>
<a <span style={{ marginLeft: 25 }}>
target="_blank" Add, edit, block or change passwords.{" "}
rel="noopener noreferrer" <a
href="/docs/organizations#user_management" target="_blank"
style={{ textDecoration: "none", color: "#f85a3e" }} rel="noopener noreferrer"
> href="/docs/organizations#user_management"
Learn more style={{ textDecoration: "none", color: "#f85a3e" }}
</a> >
</span> Learn more
</a>
</span>
</div>
<div style={{ display: 'flex', flexDirection: 'row', marginTop: 20, }}>
<Button
style={{}}
variant="contained"
color="primary"
onClick={() => setModalOpen(true)}
>
Add user
</Button>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
variant="contained"
color="primary"
onClick={() => getUsers()}
>
<CachedIcon />
</Button>
</div>
</div>
<div />
<div style={{ marginleft: 20, maxWidth: 500 }}>
<Typography variant="body1">MFA Required</Typography>
<Switch
checked={MFARequired}
onChange={(event) => {
UpdateMFAInUserOrg(selectedOrganization.id);
}}
/>
</div>
</div> </div>
<div />
<Button
style={{}}
variant="contained"
color="primary"
onClick={() => setModalOpen(true)}
>
Add user
</Button>
<Button
style={{ marginLeft: 5, marginRight: 15 }}
variant="contained"
color="primary"
onClick={() => getUsers()}
>
<CachedIcon />
</Button>
<Divider <Divider
style={{ style={{
marginTop: 20, marginTop: 20,
@@ -4714,6 +4792,12 @@ If you're interested, please let me know a time that works for you, or set up a
) : null; ) : null;
const run2FASetup = (data) => { const run2FASetup = (data) => {
if (MFARequired === true && (selectedUser.mfa_info && selectedUser.mfa_info.active === true)) {
toast("MFA is required for your organization. You can't disable it.");
return;
}
if (!show2faSetup) { if (!show2faSetup) {
get2faCode(data.id); get2faCode(data.id);
} else { } else {
@@ -5247,6 +5331,282 @@ If you're interested, please let me know a time that works for you, or set up a
setAppsForAppAuthGroup(newappauth.concat(data.id)) setAppsForAppAuthGroup(newappauth.concat(data.id))
} }
const KMSItem = (props) => {
const {
data,
index,
} = props
const [showEnvironmentDropdown, setShowEnvironmentDropdown] = React.useState(false)
var bgColor = "#27292d";
if (index % 2 === 0) {
bgColor = "#1f2023";
}
const isDistributed =
data.suborg_distributed === true ? true : false;
const isKms = data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage"
var selectedEnvironment = ""
if (data.environment !== undefined && data.environment !== null && data.environment.length > 0) {
selectedEnvironment = data.environment
}
if (selectedEnvironment === "" && environments !== undefined && environments !== null && environments.length > 0) {
for (var i = 0; i < environments.length; i++) {
if (environments[i].default === true) {
selectedEnvironment = environments[i].Name
break
}
}
}
console.log("AUTH: ", data)
return (
<ListItem key={index} style={{ backgroundColor: bgColor }}>
<ListItemText
primary=<img
alt=""
src={data.app.large_image}
style={{
maxWidth: 50,
borderRadius: theme.palette.borderRadius,
}}
/>
style={{ minWidth: 75, maxWidth: 75 }}
/>
<ListItemText
primary={!isKms ? data.label :
<div style={{display: "flex", flexDirection: "column", maxWidth: 200, }}>
<Chip
label={"KMS Shuffle Storage"}
variant="contained"
color="secondary"
style={{cursor: "pointer"}}
onClick={() => {
setShowEnvironmentDropdown(true)
if (environments === undefined || environments === null || environments.length === 0) {
toast.error("No environments found. Please try again in a second, or reload to configure environment to use for KMS")
}
}}
/>
{showEnvironmentDropdown === true && environments !== undefined && environments !== null && environments.length > 0 ?
<FormControl fullWidth sx={{ m: 1 }}>
<InputLabel id="envselect" style={{ padding: 5 }}>
Environment
</InputLabel>
<Select
labelId="envselect"
defaultValue={selectedEnvironment}
onChange={(e) => {
if (e.target.value === "") {
return
}
if (e.target.value === selectedEnvironment) {
return
}
toast.info("Updating environment KMS runs on to " + e.target.value)
data.environment = e.target.value
const envIndex = environments.findIndex((env) => env.Name === e.target.value)
if (envIndex === -1) {
toast.error("Environment not found")
return
}
environments[envIndex].environment = e.target.value
setEnvironments(environments)
setShowEnvironmentDropdown(false)
saveAuthentication(data)
}}
>
{environments.map((env, index) => {
if (env.archived === true) {
return null
}
return (
<MenuItem key={index} value={env.Name}>
{env.default === true ? "Default - " : ""}{env.Name}
</MenuItem>
)
})}
</Select>
</FormControl>
: null}
</div>
}
style={{
minWidth: 225,
maxWidth: 225,
overflow: "hidden",
}}
/>
<ListItemText
primary={data.app.name.replaceAll("_", " ")}
style={{ minWidth: 175, maxWidth: 175, marginLeft: 10 }}
/>
{/*
<ListItemText
primary={data.defined === false ? "No" : "Yes"}
style={{ minWidth: 100, maxWidth: 100, }}
/>
*/}
<ListItemText
primary={
data.workflow_count === null ? 0 : data.workflow_count
}
style={{
minWidth: 100,
maxWidth: 100,
textAlign: "center",
overflow: "hidden",
}}
/>
{/*
<ListItemText
primary={data.node_count}
style={{
minWidth: 110,
maxWidth: 110,
textAlign: "center",
overflow: "hidden",
}}
/>
*/}
<ListItemText
primary={
data.fields === null || data.fields === undefined
? ""
: data.fields
.map((data) => {
return data.key;
})
.join(", ")
}
style={{
minWidth: 125,
maxWidth: 125,
overflow: "auto",
marginRight: 10,
}}
/>
<ListItemText
style={{
maxWidth: 230,
minWidth: 230,
overflow: "hidden",
}}
primary={new Date(data.edited * 1000).toISOString()}
/>
<ListItemText>
<IconButton
onClick={() => {
updateAppAuthentication(data);
}}
disabled={
data.org_id !== selectedOrganization.id ? true : false
}
>
<EditIcon color="secondary" />
</IconButton>
{data.defined ? (
<Tooltip
color="primary"
title="Set for EVERY instance of this App being used in this organization"
placement="top"
>
<IconButton
style={{ marginRight: 10 }}
disabled={
data.defined === false ||
data.org_id !== selectedOrganization.id
? true
: false
}
onClick={() => {
editAuthenticationConfig(data.id);
}}
>
<SelectAllIcon color={"secondary"} />
</IconButton>
</Tooltip>
) : (
<Tooltip
color="primary"
title="Must edit before you can set in all workflows"
placement="top"
>
<IconButton
style={{}}
onClick={() => {}}
disabled={
data.org_id !== selectedOrganization.id
? true
: false
}
>
<SelectAllIcon color="secondary" />
</IconButton>
</Tooltip>
)}
<IconButton
style={{ marginLeft: 0 }}
disabled={
data.org_id !== selectedOrganization.id ? true : false
}
onClick={() => {
deleteAuthentication(data);
}}
>
<DeleteIcon color="secondary" />
</IconButton>
</ListItemText>
<ListItemText>
{selectedOrganization.id !== undefined &&
data.org_id !== selectedOrganization.id ? (
<Tooltip
title="Parent organization controlled auth. You can use, but not modify this auth. Contact an admin of your parent organization if you need changes to this."
placement="top"
>
<Chip
label={"Parent"}
variant="contained"
color="secondary"
/>
</Tooltip>
) : (
<Tooltip
title="Distributed to sub-organizations. This means the sub organizations can use this authentication, but not modify it."
placement="top"
>
<Checkbox
disabled={
selectedOrganization.creator_org !== undefined &&
selectedOrganization.creator_org !== null &&
selectedOrganization.creator_org !== ""
? true
: false
}
checked={isDistributed}
color="secondary"
onClick={() => {
changeDistribution(data, !isDistributed);
}}
/>
</Tooltip>
)}
</ListItemText>
</ListItem>
)
}
const authenticationView = const authenticationView =
curTab === 2 ? ( curTab === 2 ? (
@@ -5361,6 +5721,10 @@ If you're interested, please let me know a time that works for you, or set up a
<div> <div>
{authentication.map((data, index) => { {authentication.map((data, index) => {
var checked = data.checked var checked = data.checked
if (data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage") {
return null
}
if (checked === undefined || checked === null) { if (checked === undefined || checked === null) {
checked = false checked = false
} }
@@ -5476,11 +5840,6 @@ If you're interested, please let me know a time that works for you, or set up a
{authentication === undefined || authentication === null {authentication === undefined || authentication === null
? null ? null
: authentication.map((data, index) => { : authentication.map((data, index) => {
var bgColor = "#27292d";
if (index % 2 === 0) {
bgColor = "#1f2023";
}
//console.log("Auth data: ", data) //console.log("Auth data: ", data)
if (data.type === "oauth2") { if (data.type === "oauth2") {
data.fields = [ data.fields = [
@@ -5503,187 +5862,14 @@ If you're interested, please let me know a time that works for you, or set up a
]; ];
} }
const isDistributed =
data.suborg_distributed === true ? true : false;
return ( return (
<ListItem key={index} style={{ backgroundColor: bgColor }}> <KMSItem
<ListItemText data={data}
primary=<img index={index}
alt="" />
src={data.app.large_image} )
style={{
maxWidth: 50,
borderRadius: theme.palette.borderRadius,
}}
/>
style={{ minWidth: 75, maxWidth: 75 }}
/>
<ListItemText
primary={data.label}
style={{
minWidth: 225,
maxWidth: 225,
overflow: "hidden",
}}
/>
<ListItemText
primary={data.app.name.replaceAll("_", " ")}
style={{ minWidth: 175, maxWidth: 175, marginLeft: 10 }}
/>
{/*
<ListItemText
primary={data.defined === false ? "No" : "Yes"}
style={{ minWidth: 100, maxWidth: 100, }}
/>
*/}
<ListItemText
primary={
data.workflow_count === null ? 0 : data.workflow_count
}
style={{
minWidth: 100,
maxWidth: 100,
textAlign: "center",
overflow: "hidden",
}}
/>
{/*
<ListItemText
primary={data.node_count}
style={{
minWidth: 110,
maxWidth: 110,
textAlign: "center",
overflow: "hidden",
}}
/>
*/}
<ListItemText
primary={
data.fields === null || data.fields === undefined
? ""
: data.fields
.map((data) => {
return data.key;
})
.join(", ")
}
style={{
minWidth: 125,
maxWidth: 125,
overflow: "auto",
marginRight: 10,
}}
/>
<ListItemText
style={{
maxWidth: 230,
minWidth: 230,
overflow: "hidden",
}}
primary={new Date(data.edited * 1000).toISOString()}
/>
<ListItemText>
<IconButton
onClick={() => {
updateAppAuthentication(data);
}}
disabled={
data.org_id !== selectedOrganization.id ? true : false
}
>
<EditIcon color="secondary" />
</IconButton>
{data.defined ? (
<Tooltip
color="primary"
title="Set for EVERY instance of this App being used in this organization"
placement="top"
>
<IconButton
style={{ marginRight: 10 }}
disabled={
data.defined === false ||
data.org_id !== selectedOrganization.id
? true
: false
}
onClick={() => {
editAuthenticationConfig(data.id);
}}
>
<SelectAllIcon color={"secondary"} />
</IconButton>
</Tooltip>
) : (
<Tooltip
color="primary"
title="Must edit before you can set in all workflows"
placement="top"
>
<IconButton
style={{}}
onClick={() => {}}
disabled={
data.org_id !== selectedOrganization.id
? true
: false
}
>
<SelectAllIcon color="secondary" />
</IconButton>
</Tooltip>
)}
<IconButton
style={{ marginLeft: 0 }}
disabled={
data.org_id !== selectedOrganization.id ? true : false
}
onClick={() => {
deleteAuthentication(data);
}}
>
<DeleteIcon color="secondary" />
</IconButton>
</ListItemText>
<ListItemText>
{selectedOrganization.id !== undefined &&
data.org_id !== selectedOrganization.id ? (
<Tooltip
title="Parent organization controlled auth. You can use, but not modify this auth. Contact an admin of your parent organization if you need changes to this."
placement="top"
>
<Chip
label={"Parent"}
variant="contained"
color="secondary"
/>
</Tooltip>
) : (
<Tooltip
title="Distributed to sub-organizations. This means the sub organizations can use this authentication, but not modify it."
placement="top"
>
<Checkbox
disabled={
selectedOrganization.creator_org !== undefined &&
selectedOrganization.creator_org !== null &&
selectedOrganization.creator_org !== ""
? true
: false
}
checked={isDistributed}
color="secondary"
onClick={() => {
changeDistribution(data, !isDistributed);
}}
/>
</Tooltip>
)}
</ListItemText>
</ListItem>
);
})} })}
</List> </List>
</div> </div>
+388 -42
View File
@@ -88,6 +88,7 @@ import {
Error as ErrorIcon, Error as ErrorIcon,
Warning as WarningIcon, Warning as WarningIcon,
ArrowLeft as ArrowLeftIcon, ArrowLeft as ArrowLeftIcon,
ArrowRight as ArrowRightIcon,
Cached as CachedIcon, Cached as CachedIcon,
DirectionsRun as DirectionsRunIcon, DirectionsRun as DirectionsRunIcon,
FormatListNumbered as FormatListNumberedIcon, FormatListNumbered as FormatListNumberedIcon,
@@ -123,6 +124,8 @@ import {
Add as AddIcon, Add as AddIcon,
ErrorOutline as ErrorOutlineIcon, ErrorOutline as ErrorOutlineIcon,
ArrowForward as ArrowForwardIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
//import * as cytoscape from "cytoscape"; //import * as cytoscape from "cytoscape";
@@ -378,7 +381,7 @@ const svgSize = 24;
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
const AngularWorkflow = (defaultprops) => { const AngularWorkflow = (defaultprops) => {
const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id } = defaultprops; const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops;
const referenceUrl = globalUrl + "/api/v1/hooks/"; const referenceUrl = globalUrl + "/api/v1/hooks/";
//const alert = useAlert() //const alert = useAlert()
let navigate = useNavigate(); let navigate = useNavigate();
@@ -499,6 +502,7 @@ const AngularWorkflow = (defaultprops) => {
const [selectedApp, setSelectedApp] = React.useState({}); const [selectedApp, setSelectedApp] = React.useState({});
const [selectedAction, setSelectedAction] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({});
const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
const [selectedMeta, setSelectedMeta] = React.useState(undefined);
// Disabled streaming for now // Disabled streaming for now
const [streamDisabled, setStreamDisabled] = React.useState(true) const [streamDisabled, setStreamDisabled] = React.useState(true)
@@ -950,6 +954,10 @@ const AngularWorkflow = (defaultprops) => {
}) })
.then((responseJson) => { .then((responseJson) => {
if (responseJson.success === true) { if (responseJson.success === true) {
if (responseJson.meta !== undefined && responseJson.meta !== null && Object.getOwnPropertyNames(responseJson.meta).length > 0) {
setSelectedMeta(responseJson.meta)
}
if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) { if (responseJson.reason !== undefined && responseJson.reason !== undefined && responseJson.reason.length > 0) {
if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) { if (!responseJson.reason.includes("404: Not Found") && responseJson.reason.length > 25) {
// Translate <img> into markdown ![]() // Translate <img> into markdown ![]()
@@ -8018,10 +8026,10 @@ const AngularWorkflow = (defaultprops) => {
backgroundColor: theme.palette.surfaceColor, backgroundColor: theme.palette.surfaceColor,
cursor: "pointer", cursor: "pointer",
display: "flex", display: "flex",
}; }
const VariableItem = (props) => { const VariableItem = (props) => {
const { variable, index, type } = props; const { variable, index, type } = props;
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null); const [anchorEl, setAnchorEl] = React.useState(null);
@@ -8792,7 +8800,25 @@ const AngularWorkflow = (defaultprops) => {
? "cloud" ? "cloud"
: environments[defaultEnvironmentIndex] === undefined : environments[defaultEnvironmentIndex] === undefined
? "cloud" ? "cloud"
: environments[defaultEnvironmentIndex].Name; : environments[defaultEnvironmentIndex].Name
// Basic automatic auth mapping
var authId = ""
if (appAuthentication !== undefined && appAuthentication !== null && appAuthentication.length > 0) {
const appname = app.name.toLowerCase().replace(" ", "_")
for (var key in appAuthentication) {
const authKey = appAuthentication[key]
if (authKey.app.id === app.id) {
authId = authKey.id
break
}
const appauthname = authKey.app.name.toLowerCase().replace(" ", "_")
if (appauthname === appname) {
authId = authKey.id
}
}
}
// List other nodes in the workflow and see if they have an environment set. If they do, use that as the default // List other nodes in the workflow and see if they have an environment set. If they do, use that as the default
if (cy !== undefined && cy !== null) { if (cy !== undefined && cy !== null) {
@@ -8840,7 +8866,7 @@ const AngularWorkflow = (defaultprops) => {
app.categories.length > 0 app.categories.length > 0
? app.categories[0] ? app.categories[0]
: "", : "",
authentication_id: "", authentication_id: authId,
finished: false, finished: false,
template: app.template === true ? true : false, template: app.template === true ? true : false,
}; };
@@ -9722,30 +9748,49 @@ const AngularWorkflow = (defaultprops) => {
} }
} }
setSelectedAction(newSelectedAction); // Last fix for params
setUpdate(Math.random()); if (newSelectedAction.parameters !== undefined && newSelectedAction.parameters !== null && newSelectedAction.parameters.length > 0) {
for (let paramkey in newSelectedAction.parameters) {
const param = newSelectedAction.parameters[paramkey]
if (param.name !== "body") {
continue
}
const allNodes = cy.nodes().jsons(); if (param.example !== undefined && param.example !== null && param.example.length > 0) {
if (allNodes !== undefined && allNodes !== null) { if (param.value === undefined || param.value === null || param.value.length === 0) {
for (let nodekey in allNodes) { param.value = param.example
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === oldaction.id &&
currentNode.data.isDescriptor
) {
const foundnode = cy.getElementById(currentNode.data.id);
if (foundnode !== null && foundnode !== undefined) {
const iconInfo = GetIconInfo(newaction);
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
foundnode.data("image", svgpin_Url);
foundnode.data("imageColor", iconInfo.iconBackgroundColor);
}
break;
} }
} }
newSelectedAction.parameters[paramkey] = param
} }
}
console.log("New selected action: ", newSelectedAction)
setSelectedAction(newSelectedAction)
setUpdate(Math.random())
const allNodes = cy.nodes().jsons()
if (allNodes !== undefined && allNodes !== null) {
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === oldaction.id &&
currentNode.data.isDescriptor
) {
const foundnode = cy.getElementById(currentNode.data.id);
if (foundnode !== null && foundnode !== undefined) {
const iconInfo = GetIconInfo(newaction);
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
foundnode.data("image", svgpin_Url);
foundnode.data("imageColor", iconInfo.iconBackgroundColor);
}
break;
}
}
}
// Send it in here, after all fields are filled // Send it in here, after all fields are filled
// Disabled for now :( // Disabled for now :(
@@ -17673,6 +17718,71 @@ const AngularWorkflow = (defaultprops) => {
) )
} }
const changeExecution = (data) => {
if ((data.result === undefined || data.result === null || data.result.length === 0) && data.status !== "FINISHED" && data.status !== "ABORTED") {
start()
setExecutionRunning(true)
setExecutionRequestStarted(false)
}
var checkStarted = false
if (data.results !== undefined && data.results !== null && data.results.length > 0) {
if (data.execution_argument !== undefined && data.execution_argument !== null && data.execution_argument.includes("too large")) {
setExecutionData({});
checkStarted = true
start();
setExecutionRunning(true);
setExecutionRequestStarted(false);
} else {
if (data.results !== undefined && data.results !== null) {
for (let resultkey in data.results) {
if (data.results[resultkey].status !== "SUCCESS") {
continue
}
if (data.results[resultkey].result.includes("too large")) {
setExecutionData({});
checkStarted = true
start();
setExecutionRunning(true);
setExecutionRequestStarted(false);
break
}
}
}
}
}
const cur_execution = {
execution_id: data.execution_id,
authorization: data.authorization,
}
setExecutionRequest(cur_execution)
setExecutionModalView(1)
if (!checkStarted) {
handleUpdateResults(data, cur_execution)
if (cy !== undefined && cy !== null) {
cy.elements().removeClass("success-highlight failure-highlight executing-highlight");
for (let actionKey in data.workflow.actions) {
var actionitem = data.workflow.actions[actionKey]
handleColoring(actionitem.id, "", actionitem.label)
}
for (let resultKey in data.results) {
var item = data.results[resultKey]
handleColoring(item.action.id, item.status, item.action.label)
}
}
setExecutionData(data)
}
}
const ShowCopyingTooltip = () => { const ShowCopyingTooltip = () => {
const [showCopying, setShowCopying] = React.useState(true) const [showCopying, setShowCopying] = React.useState(true)
@@ -18101,7 +18211,7 @@ const AngularWorkflow = (defaultprops) => {
<h2>Details</h2> <h2>Details</h2>
<Tooltip <Tooltip
color="primary" color="primary"
title="Rerun workflow" title="Rerun workflow (uses same startnode as the original)"
placement="top" placement="top"
style={{ zIndex: 50000 }} style={{ zIndex: 50000 }}
> >
@@ -18147,7 +18257,80 @@ const AngularWorkflow = (defaultprops) => {
</span> </span>
</Tooltip> </Tooltip>
) : null} ) : null}
{isCloud ? (
<Tooltip
color="primary"
title="Previous execution"
placement="top"
style={{ zIndex: 50000, }}
>
<span style={{}}>
<Button
color="primary"
style={{ float: "right", marginTop: 20, marginLeft: 10 }}
onClick={() => {
// Find current one in execution list
var nextindex = -1
const currentIndex = workflowExecutions.findIndex((item) => item.execution_id === executionData.execution_id)
if (currentIndex === -1) {
nextindex = workflowExecutions.length - 1
} else {
nextindex = currentIndex - 1
}
if (nextindex < 0) {
toast.warn("Use the workflow run debugger to dig deeper - nothing more to show here.")
return
}
const data = workflowExecutions[nextindex]
navigate(`?execution_id=${data.execution_id}`)
changeExecution(data)
}}
>
<ArrowBackIcon color="secondary" />
</Button>
</span>
</Tooltip>
<Tooltip
color="primary"
title="Next execution"
placement="top"
style={{ zIndex: 50000, }}
>
<span style={{}}>
<Button
color="primary"
style={{ float: "right", marginTop: 20, }}
onClick={() => {
// Find current one in execution list
var nextindex = -1
const currentIndex = workflowExecutions.findIndex((item) => item.execution_id === executionData.execution_id)
if (currentIndex === -1) {
nextindex = 0
} else {
nextindex = currentIndex + 1
}
if (nextindex >= workflowExecutions.length) {
toast.warn("Use the workflow run debugger to dig deeper - nothing more to show here.")
return
}
const data = workflowExecutions[nextindex]
navigate(`?execution_id=${data.execution_id}`)
changeExecution(data)
}}
>
<ArrowForwardIcon color="secondary" />
</Button>
</span>
</Tooltip>
{isCloud ?
<Tooltip <Tooltip
color="primary" color="primary"
title="Explore logs for the workflow" title="Explore logs for the workflow"
@@ -18171,7 +18354,8 @@ const AngularWorkflow = (defaultprops) => {
</Button> </Button>
</span> </span>
</Tooltip> </Tooltip>
) : null} : null}
</div> </div>
{executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ? {executionData.workflow !== undefined && executionData.workflow !== null && executionData.workflow.actions !== undefined && executionData.workflow.actions !== null && executionData.workflow.actions.length > 0 ?
<div style={{ display: "flex", marginLeft: 10, }}> <div style={{ display: "flex", marginLeft: 10, }}>
@@ -18801,21 +18985,49 @@ const AngularWorkflow = (defaultprops) => {
validate.result = JSON.parse(validate.result) validate.result = JSON.parse(validate.result)
} }
const AppResultVariable = ({ data }) => { const AppResultVariable = ({ data, action }) => {
const [open, setOpen] = React.useState(false) const [open, setOpen] = React.useState(false)
const showVariable = data.value.length < 60 const showVariable = data.value.length < 60
// Check if it's valid JSON // Check if it's valid JSON
const checked = validateJson(data.value.trim()) const checked = validateJson(data.value.trim())
if (data.name === "shuffle_action_logs" && data.value !== undefined && data.value !== null && data.value.length > 0 && data.value.includes("add env SHUFFLE_LOGS_DISABLED")) {
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: "#f85a3e", }} href="/admin?tab=environments" target="_blank" rel="noopener noreferrer">an onprem environment</a> with the <a style={{color: "#f85a3e", }} 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
if (data.name.endsWith("-Url")) {
//data.name = data.name.toLowerCase().replaceAll("-", "_")
if (data.value.startsWith(", ")) {
data.value = data.value.substring(2)
}
if (!data.value.startsWith("http") || (data.value.startsWith("/") && data.value.includes("?"))) {
showlink = true
}
}
return ( return (
<div style={{ maxWidth: 600, overflowX: "hidden", }}> <div style={{ maxWidth: 600, overflowX: "hidden", }}>
{data.value.length > 60 || checked.valid ? {data.value.length > 60 || checked.valid ?
<IconButton <IconButton
style={{ style={{
marginBottom: 0, marginBottom: 0,
marginTop: 5, marginTop: 5,
cursor: "pointer", cursor: "pointer",
padding: 3, padding: 3,
border: "1px solid rgba(255,255,255,0.3)", border: "1px solid rgba(255,255,255,0.3)",
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
@@ -18868,8 +19080,17 @@ const AngularWorkflow = (defaultprops) => {
variant="body2" variant="body2"
style={{ style={{
whiteSpace: 'pre-line', whiteSpace: 'pre-line',
color: showlink ? "#f85a3e" : "white",
cursor: showlink ? "pointer" : "default",
}} }}
color="textSecondary" onClick={(e) => {
if (showlink) {
e.preventDefault()
e.stopPropagation()
window.open(data.value, "_blank")
}
}}
color={showlink ? "inherit" : "textSecondary"}
> >
{data.value} {data.value}
</Typography> </Typography>
@@ -18981,6 +19202,10 @@ const AngularWorkflow = (defaultprops) => {
return "Consider whether your Orborus environment can connect to a local IP or not." return "Consider whether your Orborus environment can connect to a local IP or not."
} }
if (stringjson.includes("kms/")) {
return "KMS authentication most likely failed. Check your notifications for more details on this page: /admin?admin_tab=priorities. If you need help with KMS, please contact support@shuffler.io"
}
if (stringjson.includes("invalidurl")) { if (stringjson.includes("invalidurl")) {
// IF count of "http" is more than one, 1, it's prolly invalid // IF count of "http" is more than one, 1, it's prolly invalid
var additionalinfo = "" var additionalinfo = ""
@@ -19006,7 +19231,7 @@ const AngularWorkflow = (defaultprops) => {
if (stringjson.includes("connectionerror")) { if (stringjson.includes("connectionerror")) {
if (stringjson.includes("kms")) { if (stringjson.includes("kms")) {
return "KMS authentication failed. Check your notifications for more details." return "KMS authentication most likely failed (2). Check your notifications for more details on this page: /admin?admin_tab=priorities&kms=true. If you need help with KMS, please contact support@shuffler.io"
} }
return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs." return "The URL is incorrect, or Shuffle can't reach it. Set up a Shuffle Environment in the same VLAN, or whitelist Shuffle's IPs."
@@ -19352,7 +19577,7 @@ const AngularWorkflow = (defaultprops) => {
} }
return ( return (
<AppResultVariable key={index} data={data} /> <AppResultVariable key={index} data={data} action={selectedResult.action} />
); );
})} })}
</div> </div>
@@ -20184,6 +20409,8 @@ const AngularWorkflow = (defaultprops) => {
//if (configureWorkflowModalOpen) { //if (configureWorkflowModalOpen) {
// setSelectedAction({}); // setSelectedAction({});
//} //}
//
setSelectedMeta(undefined)
}} }}
PaperProps={{ PaperProps={{
style: { style: {
@@ -20315,11 +20542,27 @@ const AngularWorkflow = (defaultprops) => {
overflowY: "auto", overflowY: "auto",
overflowX: "hidden", overflowX: "hidden",
}} }}
onLoad={() => {
/*
if (isCloud && ReactGA !== undefined) {
toast("Sending GA info")
// Google analytics info about what app people are looking at
ReactGA.event({
category: "workflow",
action: `documentation_load`,
label: selectedApp.name,
})
}
*/
}}
> >
{selectedApp.documentation === undefined || {selectedApp.documentation === undefined ||
selectedApp.documentation === null || selectedApp.documentation === null ||
selectedApp.documentation.length === 0 ? ( selectedApp.documentation.length === 0 ? (
<span style={{ textAlign: "center" }}> <span
style={{ textAlign: "center" }}
>
<Typography <Typography
variant="body1" variant="body1"
style={{ marginLeft: 25, marginRight: 25 }} style={{ marginLeft: 25, marginRight: 25 }}
@@ -20330,15 +20573,39 @@ const AngularWorkflow = (defaultprops) => {
style={{ style={{
marginTop: 25, marginTop: 25,
marginBottom: 25, marginBottom: 25,
backgroundColor: theme.palette.inputColor, backgroundColor: "rgba(255,255,255,0.6)",
}} }}
/> />
<Typography variant="h6">
There is currently no extended documentation available for this <div
app. style={{
</Typography> backgroundColor: theme.palette.inputColor,
padding: 15,
borderRadius: theme.palette.borderRadius,
marginBottom: 30,
}}
>
<Typography variant="h6" style={{marginBottom: 25, }}>
There is no Shuffle-specific documentation for this app yet. Documentation is written custom for each app, and is a community effort. Hope to see your contribution
</Typography>
<Button
variant="contained"
color="primary"
onClick={() => {
toast.success("Opening remote Github documentation link. Thanks for contributing!")
setTimeout(() => {
window.open(`https://github.com/Shuffle/openapi-apps/new/master/docs?filename=${selectedApp.name.toLowerCase()}.md`, "_blank")
}, 2500)
}}
>
<EditIcon /> &nbsp;&nbsp;Create Docs
</Button>
</div>
<Typography variant="body1" style={{ marginTop: 25 }}> <Typography variant="body1" style={{ marginTop: 25 }}>
Want to help the making of, or imrpvoe this app?{" "} Want to help the making of, or improve this app?{" "}
<br />
<a <a
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
@@ -20385,6 +20652,84 @@ const AngularWorkflow = (defaultprops) => {
)} )}
</span> </span>
) : ( ) : (
<div>
{selectedMeta !== undefined && selectedMeta !== null && Object.getOwnPropertyNames(selectedMeta).length > 0 && selectedMeta.name !== undefined && selectedMeta.name !== null ?
<div
style={{
backgroundColor: theme.palette.inputColor,
padding: 15,
borderRadius: theme.palette.borderRadius,
marginBottom: 30,
display: "flex",
}}
>
<div style={{ flex: 3, display: "flex", vAlign: "center", position: "sticky", top: 50, }}>
{isMobile ? null : (
<Typography style={{ display: "inline", marginTop: 6 }}>
<a
rel="noopener noreferrer"
target="_blank"
href={selectedMeta.link}
style={{ textDecoration: "none", color: "#f85a3e" }}
>
<Button style={{ color: "white", }} variant="outlined" color="secondary">
<EditIcon /> &nbsp;&nbsp;Edit
</Button>
</a>
</Typography>
)}
{isMobile ? null : (
<div
style={{
height: "100%",
width: 1,
backgroundColor: "white",
marginLeft: 50,
marginRight: 50,
}}
/>
)}
<Typography style={{ display: "inline", marginTop: 11 }}>
{selectedMeta.read_time} minute
{selectedMeta.read_time === 1 ? "" : "s"} to read
</Typography>
</div>
<div style={{ flex: 2 }}>
{isMobile ||
selectedMeta.contributors === undefined ||
selectedMeta.contributors === null ? (
""
) : (
<div style={{ margin: 10, height: "100%", display: "inline" }}>
{selectedMeta.contributors.slice(0, 7).map((data, index) => {
return (
<a
key={index}
rel="noopener noreferrer"
target="_blank"
href={data.url}
style={{ textDecoration: "none", color: "#f85a3e" }}
>
<Tooltip title={data.url} placement="bottom">
<img
alt={data.url}
src={data.image}
style={{
marginTop: 5,
marginRight: 10,
height: 40,
borderRadius: 40,
}}
/>
</Tooltip>
</a>
);
})}
</div>
)}
</div>
</div>
: null}
<Markdown <Markdown
components={{ components={{
img: Img, img: Img,
@@ -20405,6 +20750,7 @@ const AngularWorkflow = (defaultprops) => {
> >
{selectedApp.documentation} {selectedApp.documentation}
</Markdown> </Markdown>
</div>
)} )}
</div> </div>
</div> </div>
+13 -8
View File
@@ -955,7 +955,6 @@ const Apps = (props) => {
</Link> </Link>
: null : null
console.log("Sharing config: ", sharingConfiguration);
const activateButton = const activateButton =
selectedApp.generated && !selectedApp.activated ? ( selectedApp.generated && !selectedApp.activated ? (
<div> <div>
@@ -1281,10 +1280,15 @@ const Apps = (props) => {
{isCloud && !internalIds.includes(selectedApp.name.toLowerCase()) ? {isCloud && !internalIds.includes(selectedApp.name.toLowerCase()) ?
<Tooltip title="Deactivates this app for the current organisation. This means the app will not be usable again until you re-activate it." placement="top"> <Tooltip title="Deactivates this app for the current organisation. This means the app will not be usable again until you re-activate it." placement="top">
<Button <Button
variant="contained" variant={selectedApp.reference_org === userdata.active_org.id ? "outlined" : "contained"}
component="label" component="label"
color="primary" color="primary"
onClick={() => { onClick={() => {
if (selectedApp.reference_org === userdata.active_org.id) {
toast.info("Can't deactivate apps made in this org. Please contact support if you want to deactivate this app.")
return
}
const tmpurl = new URL(window.location.href); const tmpurl = new URL(window.location.href);
const searchParams = tmpurl.searchParams; const searchParams = tmpurl.searchParams;
const queryID = searchParams.get("queryID"); const queryID = searchParams.get("queryID");
@@ -1316,7 +1320,7 @@ const Apps = (props) => {
console.log("No query to handle when activating"); console.log("No query to handle when activating");
} }
activateApp(selectedApp.id, true) activateApp(selectedApp.id, true, true)
}} }}
style={{ height: 35, marginTop: 0, marginLeft: 10, }} style={{ height: 35, marginTop: 0, marginLeft: 10, }}
> >
@@ -1845,9 +1849,12 @@ const Apps = (props) => {
) )
} }
const activateApp = (appid, refresh) => { const activateApp = (appid, refresh, deactivate) => {
const appExists = userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appid) const appExists = userdata.active_apps !== undefined && userdata.active_apps !== null && userdata.active_apps.includes(appid)
const url = appExists ? `${globalUrl}/api/v1/apps/${appid}/deactivate` : `${globalUrl}/api/v1/apps/${appid}/activate` const url = deactivate === true ?
`${globalUrl}/api/v1/apps/${appid}/deactivate`
:
appExists ? `${globalUrl}/api/v1/apps/${appid}/deactivate` : `${globalUrl}/api/v1/apps/${appid}/activate`
fetch(url, { fetch(url, {
method: 'GET', method: 'GET',
@@ -1876,7 +1883,7 @@ const Apps = (props) => {
if (appExists) { if (appExists) {
toast("App deactivated for your organization! Existing workflows with the app will continue to work.") toast("App deactivated for your organization! Existing workflows with the app will continue to work.")
} else { } else {
toast("App activated for your organization!") toast("App activation changed for your organization!")
} }
if (refresh === true) { if (refresh === true) {
@@ -2021,8 +2028,6 @@ const Apps = (props) => {
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id, userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
} }
]) ])
} else {
console.log("No query to handle when activating")
} }
activateApp(hit.objectID, true) activateApp(hit.objectID, true)
+7 -1
View File
@@ -156,7 +156,13 @@ export const OuterLink = (props) => {
export const Img = (props) => { export const Img = (props) => {
return <img style={{ borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }} alt={props.alt} src={props.src} />; return(
<img
style={{border: "1px solid rgba(255,255,255,0.3)", borderRadius: theme.palette.borderRadius, width: 750, maxWidth: "100%", marginTop: 15, marginBottom: 15, }}
alt={props.alt}
src={props.src}
/>
)
} }
export const CodeHandler = (props) => { export const CodeHandler = (props) => {
+86 -2
View File
@@ -10,6 +10,7 @@ import {
Divider, Divider,
TextField, TextField,
Modal, Modal,
Switch,
} from "@mui/material"; } from "@mui/material";
//import { useAlert //import { useAlert
import { ToastContainer, toast } from "react-toastify"; import { ToastContainer, toast } from "react-toastify";
@@ -19,6 +20,9 @@ import { FileCopy, Visibility, VisibilityOff } from "@mui/icons-material";
import IconButton from "@mui/material/IconButton"; import IconButton from "@mui/material/IconButton";
import { Tooltip } from "@mui/material"; import { Tooltip } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { Box } from "@mui/system";
import CircularProgress from "@mui/material/CircularProgress";
const Settings = (props) => { const Settings = (props) => {
const { globalUrl, isLoaded, userdata, setUserData } = props; const { globalUrl, isLoaded, userdata, setUserData } = props;
@@ -32,6 +36,11 @@ const Settings = (props) => {
const [currentPassword, setCurrentPassword] = useState(""); const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState(""); const [newPassword, setNewPassword] = useState("");
const [newPassword2, setNewPassword2] = useState(""); const [newPassword2, setNewPassword2] = useState("");
const [selectedOrganization, setSelectedOrganization] = useState({});
const [MFARequired, setMFARequired] = React.useState(false);
const [image2FA, setImage2FA] = React.useState("");
const [value2FA, setValue2FA] = React.useState("");
// const [file, setFile] = React.useState(""); // const [file, setFile] = React.useState("");
// const [fileBase64, setFileBase64] = React.useState( // const [fileBase64, setFileBase64] = React.useState(
// userdata.image === undefined || userdata.image === null // userdata.image === undefined || userdata.image === null
@@ -87,6 +96,82 @@ const Settings = (props) => {
flexDirection: "column", flexDirection: "column",
}; };
useEffect(() => {
if (userdata.active_org === undefined || userdata.active_org === null || userdata.active_org.id === undefined || userdata.active_org.id === null) {
return;
}
fetch(`${globalUrl}/api/v1/orgs/${userdata.active_org.id}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
credentials: "include",
})
.then((response) => {
console.log("Received response:", response);
if (response.status !== 200) {
console.error("Status not 200:", response.status);
throw new Error(`Status not 200: ${response.status}`);
}
return response.json();
})
.then((responseJson) => {
setSelectedOrganization(responseJson);
setMFARequired(responseJson.mfa_required);
})
.catch((error) => {
console.error("Error fetching organization:", error);
});
}, [userdata]);
const UpdateMFAInUserOrg = (org_id) => {
if (MFARequired === false) {
toast("Making MFA required for your organization. Please wait...");
} else {
toast("Making MFA optional for your organization. Please wait...");
}
const data = {
mfa_required: !selectedOrganization.mfa_required,
org_id: selectedOrganization.id,
}
const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`;
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) => {
console.log(responseJson)
if (responseJson["success"] === false) {
toast.error("Failed updating org: ", responseJson.reason);
} else {
if (MFARequired === false) {
setMFARequired(true)
toast.success("Successfully make MFA required for your organization!");
} else {
setMFARequired(false)
toast.success("Successfully make MFA optional for your organization!")
}
}
}),
)
.catch((error) => {
toast("Err: " + error.toString());
});
}
const checkOwner = (data, userdata) => { const checkOwner = (data, userdata) => {
var currentOwner = false; var currentOwner = false;
if (data.owner.address === userdata.eth_info.account) { if (data.owner.address === userdata.eth_info.account) {
@@ -938,8 +1023,7 @@ const Settings = (props) => {
> >
Submit password change Submit password change
</Button> </Button>
<h3>{passwordFormMessage}</h3> <h3>{passwordFormMessage}</h3>
{isCloud && ( {isCloud && (
<> <>
+10 -1
View File
@@ -2153,7 +2153,7 @@ const Workflows = (props) => {
} }
return ( return (
<div style={{width: "100%", minWidth: 321, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? "2px solid #40E0D0" : "inherit", borderRadius: theme.palette.borderRadius, }}> <div style={{width: "100%", minWidth: 321, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? "1px solid #40E0D0" : "inherit", borderRadius: theme.palette.borderRadius, }}>
<Paper square style={paperAppStyle}> <Paper square style={paperAppStyle}>
{selectedCategory !== "" ? {selectedCategory !== "" ?
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom"> <Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
@@ -2206,8 +2206,17 @@ const Workflows = (props) => {
<Typography> <Typography>
Edit '{data.name}' Edit '{data.name}'
</Typography> </Typography>
<br />
{isDistributed || hasSuborgs ?
<Typography>
This is a parentorg-controlled workflow.
</Typography>
: null}
</div> </div>
} placement="right"> } placement="right">
<Typography <Typography
variant="body1" variant="body1"
style={{ style={{