MSSP multi tenant workflow features ayy
This commit is contained in:
@@ -20,7 +20,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/h2non/filetype v1.1.3
|
||||
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
|
||||
google.golang.org/api v0.176.1
|
||||
google.golang.org/grpc v1.63.2
|
||||
|
||||
@@ -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/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/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/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
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.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.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.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
|
||||
@@ -3382,6 +3382,8 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
debugUrl := fmt.Sprintf("/workflows/%s?execution_id=%s", workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
|
||||
resp.Header().Add("X-Debug-Url", debugUrl)
|
||||
|
||||
workflowExecution.Priority = 11
|
||||
environments, err := shuffle.GetEnvironments(ctx, user.ActiveOrg.Id)
|
||||
|
||||
@@ -44,9 +44,9 @@ const Files = (props) => {
|
||||
const { globalUrl, userdata, serverside, selectedOrganization, isCloud,isSelectedFiles } = props;
|
||||
|
||||
const [files, setFiles] = React.useState([]);
|
||||
const [selectedNamespace, setSelectedNamespace] = React.useState("default");
|
||||
const [selectedCategory, setSelectedCategory] = React.useState("default");
|
||||
const [openFileId, setOpenFileId] = React.useState(false);
|
||||
const [fileNamespaces, setFileNamespaces] = React.useState([]);
|
||||
const [fileCategories, setFileCategories] = React.useState([]);
|
||||
const [fileContent, setFileContent] = React.useState("");
|
||||
const [openEditor, setOpenEditor] = 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 [downloadBranch, setDownloadBranch] = React.useState("main");
|
||||
const [downloadFolder, setDownloadFolder] = React.useState("translation_standards");
|
||||
const [contentLoading, setContentLoading] = React.useState(false)
|
||||
|
||||
//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"]
|
||||
@@ -67,8 +68,8 @@ const Files = (props) => {
|
||||
|
||||
console.log('do validate')
|
||||
console.log("new namespace name->",event.target.value);
|
||||
fileNamespaces.push(event.target.value);
|
||||
setSelectedNamespace(event.target.value);
|
||||
fileCategories.push(event.target.value);
|
||||
setSelectedCategory(event.target.value);
|
||||
setRenderTextBox(false);
|
||||
}
|
||||
|
||||
@@ -107,12 +108,13 @@ const Files = (props) => {
|
||||
|
||||
const getFiles = (namespace) => {
|
||||
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 !== "") {
|
||||
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`
|
||||
} else if (selectedCategory !== undefined && selectedCategory !== null && selectedCategory !== "default" && selectedCategory !== "") {
|
||||
parsedurl = `${globalUrl}/api/v1/files/namespaces/${selectedCategory}?ids=true`
|
||||
}
|
||||
|
||||
fetch(parsedurl, {
|
||||
@@ -149,9 +151,11 @@ const Files = (props) => {
|
||||
setFiles([]);
|
||||
}
|
||||
|
||||
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileNamespaces.length === 0 || responseJson.namespaces.length > fileNamespaces.length)) {
|
||||
setFileNamespaces(responseJson.namespaces);
|
||||
}
|
||||
if (namespace === undefined || namespace === null || namespace === "default") {
|
||||
if (responseJson.namespaces !== undefined && responseJson.namespaces !== null && (fileCategories.length === 0 || responseJson.namespaces.length > fileCategories.length)) {
|
||||
setFileCategories(responseJson.namespaces)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
@@ -159,7 +163,21 @@ const Files = (props) => {
|
||||
};
|
||||
|
||||
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) => {
|
||||
@@ -393,7 +411,7 @@ const Files = (props) => {
|
||||
toast("Failed to delete file: " + responseJson.reason);
|
||||
}
|
||||
setTimeout(() => {
|
||||
getFiles();
|
||||
getFiles(selectedCategory)
|
||||
}, 1500);
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -402,6 +420,8 @@ const Files = (props) => {
|
||||
};
|
||||
|
||||
const readFileData = (file) => {
|
||||
setContentLoading(true)
|
||||
|
||||
fetch(globalUrl + "/api/v1/files/" + file.id + "/content", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@@ -411,6 +431,7 @@ const Files = (props) => {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
setContentLoading(false)
|
||||
if (response.status !== 200) {
|
||||
console.log("Status not 200 for file :O!");
|
||||
return "";
|
||||
@@ -428,12 +449,12 @@ const Files = (props) => {
|
||||
return respdata
|
||||
})
|
||||
.then((responseData) => {
|
||||
|
||||
setFileContent(responseData);
|
||||
//console.log("filecontent state ",fileContent);
|
||||
setFileContent(responseData);
|
||||
//console.log("filecontent state ",fileContent);
|
||||
})
|
||||
.catch((error) => {
|
||||
toast(error.toString());
|
||||
setContentLoading(false)
|
||||
toast(error.toString())
|
||||
});
|
||||
};
|
||||
|
||||
@@ -505,12 +526,12 @@ const Files = (props) => {
|
||||
};
|
||||
|
||||
if (
|
||||
selectedNamespace !== undefined &&
|
||||
selectedNamespace !== null &&
|
||||
selectedNamespace.length > 0 &&
|
||||
selectedNamespace !== "default"
|
||||
selectedCategory !== undefined &&
|
||||
selectedCategory !== null &&
|
||||
selectedCategory.length > 0 &&
|
||||
selectedCategory !== "default"
|
||||
) {
|
||||
data.namespace = selectedNamespace;
|
||||
data.namespace = selectedCategory;
|
||||
}
|
||||
|
||||
fetch(globalUrl + "/api/v1/files/create", {
|
||||
@@ -689,9 +710,9 @@ const Files = (props) => {
|
||||
|
||||
|
||||
|
||||
{fileNamespaces !== undefined &&
|
||||
fileNamespaces !== null &&
|
||||
fileNamespaces.length > 1 ? (
|
||||
{fileCategories !== undefined &&
|
||||
fileCategories !== null &&
|
||||
fileCategories.length > 1 ? (
|
||||
<FormControl style={{ minWidth: 150, maxWidth: 150 }}>
|
||||
<InputLabel id="input-namespace-label">File Category</InputLabel>
|
||||
<Select
|
||||
@@ -703,18 +724,26 @@ const Files = (props) => {
|
||||
maxWidth: 150,
|
||||
float: "right",
|
||||
}}
|
||||
value={selectedNamespace}
|
||||
value={selectedCategory}
|
||||
onChange={(event) => {
|
||||
setSelectedNamespace(event.target.value);
|
||||
setSelectedCategory(event.target.value)
|
||||
|
||||
if (event.target.value === "all" || event.target.value === "default") {
|
||||
getFiles()
|
||||
} else {
|
||||
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 (
|
||||
<MenuItem
|
||||
key={index}
|
||||
@@ -731,31 +760,31 @@ const Files = (props) => {
|
||||
<div style={{display: "inline-flex", position:"relative"}}>
|
||||
{renderTextBox ?
|
||||
|
||||
<Tooltip title={"Close"} style={{}} aria-label={""}>
|
||||
<Button
|
||||
style={{ marginLeft: 5, marginRight: 15 }}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setRenderTextBox(false);
|
||||
console.log(" close clicked")
|
||||
}}
|
||||
>
|
||||
<ClearIcon/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip title={"Add new file category"} style={{}} aria-label={""}>
|
||||
<Button
|
||||
style={{ marginLeft: 5, marginRight: 15 }}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setRenderTextBox(true);
|
||||
}}
|
||||
>
|
||||
<AddIcon/>
|
||||
</Button>
|
||||
</Tooltip> }
|
||||
|
||||
<Tooltip title={"Close"} style={{}} aria-label={""}>
|
||||
<Button
|
||||
style={{ marginLeft: 5, marginRight: 15 }}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setRenderTextBox(false);
|
||||
console.log(" close clicked")
|
||||
}}
|
||||
>
|
||||
<ClearIcon/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip title={"Add new file category"} style={{}} aria-label={""}>
|
||||
<Button
|
||||
style={{ marginLeft: 5, marginRight: 15 }}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setRenderTextBox(true);
|
||||
}}
|
||||
>
|
||||
<AddIcon/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
{renderTextBox && <TextField
|
||||
onKeyPress={(event)=>{
|
||||
@@ -783,6 +812,7 @@ const Files = (props) => {
|
||||
isFileEditor = {true}
|
||||
key = {fileContent} //https://reactjs.org/docs/reconciliation.html#recursing-on-children
|
||||
runUpdateText = {runUpdateText}
|
||||
contentLoading = {contentLoading}
|
||||
/>
|
||||
{isSelectedFiles?null:
|
||||
<Divider
|
||||
@@ -795,15 +825,17 @@ const Files = (props) => {
|
||||
|
||||
<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}}>
|
||||
{/*
|
||||
<ListItemText
|
||||
primary="Updated"
|
||||
style={{ maxWidth: 185, minWidth: 185 }}
|
||||
/>
|
||||
*/}
|
||||
<ListItemText
|
||||
primary="Name"
|
||||
style={{
|
||||
maxWidth: 150,
|
||||
minWidth: 150,
|
||||
maxWidth: 250,
|
||||
minWidth: 250,
|
||||
overflow: "hidden",
|
||||
marginLeft: 10,
|
||||
}}
|
||||
@@ -814,7 +846,7 @@ const Files = (props) => {
|
||||
/>
|
||||
<ListItemText
|
||||
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
|
||||
primary="Status"
|
||||
@@ -832,7 +864,7 @@ const Files = (props) => {
|
||||
file.namespace = "default";
|
||||
}
|
||||
|
||||
if (file.namespace !== selectedNamespace) {
|
||||
if (file.namespace !== selectedCategory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -853,6 +885,7 @@ const Files = (props) => {
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{/*
|
||||
<ListItemText
|
||||
style={{
|
||||
maxWidth: isSelectedFiles ? 170:225,
|
||||
@@ -861,10 +894,11 @@ const Files = (props) => {
|
||||
}}
|
||||
primary={new Date(file.updated_at * 1000).toISOString()}
|
||||
/>
|
||||
*/}
|
||||
<ListItemText
|
||||
style={{
|
||||
maxWidth: 150,
|
||||
minWidth: 150,
|
||||
maxWidth: 250,
|
||||
minWidth: 250,
|
||||
overflow: "hidden",
|
||||
marginLeft: 10,
|
||||
}}
|
||||
|
||||
@@ -546,7 +546,7 @@ const Header = (props) => {
|
||||
rel="noopener noreferrer"
|
||||
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
|
||||
color="primary"
|
||||
style={{}}
|
||||
@@ -554,11 +554,7 @@ const Header = (props) => {
|
||||
aria-haspopup="true"
|
||||
onClick={(event) => { }}
|
||||
>
|
||||
<img
|
||||
alt="Discord Community Join"
|
||||
src={"/images/social/discord.png"}
|
||||
style={{ height: 30, width: 30 }}
|
||||
/>
|
||||
<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>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</a>
|
||||
|
||||
@@ -1925,7 +1925,7 @@ const ParsedAction = (props) => {
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Autocomplete text" placement="top">
|
||||
<Tooltip title="Autocomplete text" placement="bottom">
|
||||
<AddCircleOutlineIcon
|
||||
style={{ cursor: "pointer", margin: multiline ? 5 : 0, }}
|
||||
onClick={(event) => {
|
||||
@@ -3078,7 +3078,6 @@ const ParsedAction = (props) => {
|
||||
selectedApp.actions.filter((a) =>
|
||||
a.category_label !== undefined && a.category_label !== null && a.category_label.length > 0).concat(sortByKey(selectedApp.actions, "label"))
|
||||
).sort(sortByCategoryLabel))
|
||||
|
||||
|
||||
const selectedAppIcon = selectedAction.large_image
|
||||
var baselabel = selectedAction.label
|
||||
@@ -3156,9 +3155,11 @@ const ParsedAction = (props) => {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -3823,6 +3824,7 @@ const ParsedAction = (props) => {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
{showEnvironment !== undefined && showEnvironment && environments.length > 1 && !isIntegration ? (
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Typography style={{color: "rgba(255,255,255,0.7)"}}>Environment</Typography>
|
||||
@@ -3831,9 +3833,9 @@ const ParsedAction = (props) => {
|
||||
disableScrollLock: true,
|
||||
}}
|
||||
value={
|
||||
selectedActionEnvironment === undefined || selectedActionEnvironment === null ||
|
||||
selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null
|
||||
? isCloud ? "Cloud" : "Shuffle"
|
||||
selectedActionEnvironment === undefined || selectedActionEnvironment === null || selectedActionEnvironment.Name === undefined || selectedActionEnvironment.Name === null
|
||||
?
|
||||
selectedAction.environment !== undefined && selectedAction.environment !== null && selectedAction.environment.length > 0 ? selectedAction.environment : isCloud ? "Cloud" : "Shuffle"
|
||||
: selectedActionEnvironment.Name
|
||||
}
|
||||
SelectDisplayProps={{
|
||||
|
||||
@@ -27,8 +27,9 @@ const Priorities = (props) => {
|
||||
|
||||
const [selectedWorkflow, setSelectedWorkflow] = React.useState("NO HIGHLIGHT");
|
||||
const [selectedExecutionId, setSelectedExecutionId] = React.useState("NO HIGHLIGHT");
|
||||
let navigate = useNavigate();
|
||||
const [highlightKMS, setHighlightKMS] = React.useState(false)
|
||||
|
||||
let navigate = useNavigate();
|
||||
useEffect(() => {
|
||||
getFramework()
|
||||
|
||||
@@ -36,6 +37,12 @@ const Priorities = (props) => {
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const workflow = urlParams.get("workflow")
|
||||
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) {
|
||||
setSelectedExecutionId(execution_id)
|
||||
@@ -217,7 +224,16 @@ const Priorities = (props) => {
|
||||
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) {
|
||||
const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]);
|
||||
|
||||
@@ -107,10 +107,9 @@ const CodeEditor = (props) => {
|
||||
getParents,
|
||||
|
||||
fieldname,
|
||||
contentLoading,
|
||||
} = props
|
||||
|
||||
|
||||
|
||||
const [localcodedata, setlocalcodedata] = React.useState(codedata === undefined || codedata === null || codedata.length === 0 ? "" : codedata);
|
||||
|
||||
//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
|
||||
color="primary"
|
||||
title={`Move window`}
|
||||
|
||||
+420
-234
@@ -44,6 +44,7 @@ import {
|
||||
Box,
|
||||
InputAdornment,
|
||||
Autocomplete,
|
||||
Modal
|
||||
} from "@mui/material";
|
||||
|
||||
import {
|
||||
@@ -164,7 +165,7 @@ const Admin = (props) => {
|
||||
const [selectedOrganization, setSelectedOrganization] = React.useState({});
|
||||
|
||||
//console.log("Selected: ", selectedOrganization)
|
||||
const [appAuthenticationGroupModalOpen , setAppAuthenticationGroupModalOpen] = React.useState(false);
|
||||
const [appAuthenticationGroupModalOpen, setAppAuthenticationGroupModalOpen] = React.useState(false);
|
||||
const [appsForAppAuthGroup, setAppsForAppAuthGroup] = React.useState([]);
|
||||
const [appAuthenticationGroupId, setAppAuthenticationGroupId] = React.useState("");
|
||||
const [appAuthenticationGroupName, setAppAuthenticationGroupName] = React.useState("");
|
||||
@@ -212,6 +213,13 @@ const Admin = (props) => {
|
||||
const [allSchedules, setAllSchedules] = React.useState([]);
|
||||
const [pipelines, setPipelines] = 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] =
|
||||
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,
|
||||
user_id: userId,
|
||||
};
|
||||
toast("Verifying 2fa code. Please wait...");
|
||||
|
||||
fetch(`${globalUrl}/api/v1/users/${userId}/set2fa`, {
|
||||
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) => {
|
||||
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(() => {
|
||||
getUsers();
|
||||
|
||||
setImage2FA("");
|
||||
setValue2FA("");
|
||||
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"
|
||||
>
|
||||
{selectedUser.mfa_info !== undefined &&
|
||||
selectedUser.mfa_info !== null &&
|
||||
selectedUser.mfa_info.active === true
|
||||
selectedUser.mfa_info !== null &&
|
||||
selectedUser.mfa_info.active === true
|
||||
? "Disable 2FA"
|
||||
: "Enable 2FA"}
|
||||
</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,
|
||||
minWidth: 300,
|
||||
marginTop: 25,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
{/*<Divider style={{marginTop: 20, marginBottom: 20}} />*/}
|
||||
|
||||
{secret2FA !== undefined &&
|
||||
secret2FA !== null &&
|
||||
secret2FA.length > 0 ? (
|
||||
secret2FA !== null &&
|
||||
secret2FA.length > 0 ? (
|
||||
<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
|
||||
your phone. If you can’t use a QR code, use the code{" "}
|
||||
{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>
|
||||
) : null}
|
||||
{image2FA !== undefined &&
|
||||
image2FA !== null &&
|
||||
image2FA.length > 0 ? (
|
||||
image2FA !== null &&
|
||||
image2FA.length > 0 ? (
|
||||
<img
|
||||
alt={"2 factor img"}
|
||||
alt="2 factor img"
|
||||
src={image2FA}
|
||||
style={{
|
||||
margin: "auto",
|
||||
marginTop: 25,
|
||||
margin: "15px auto",
|
||||
maxHeight: 200,
|
||||
maxWidth: 200,
|
||||
minWidth: 200,
|
||||
maxWidth: 200,
|
||||
display: 'block',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@@ -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
|
||||
you can enter below.
|
||||
</Typography>
|
||||
<div style={{ display: "flex" }}>
|
||||
<div style={{ display: "flex", width: '100%', marginTop: 10 }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
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);
|
||||
}}
|
||||
onKeyPress={(event) => {
|
||||
if (event.key === 'Enter' && event.target.value.length === 6) {
|
||||
handleVerify2FA(userdata.id, event.target.value, false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={value2FA.length !== 6}
|
||||
variant="contained"
|
||||
style={{ marginTop: 15, height: 50, flex: 1 }}
|
||||
onClick={() => {
|
||||
handleVerify2FA(userdata.id, value2FA);
|
||||
handleVerify2FA(userdata.id, value2FA, false);
|
||||
}}
|
||||
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>
|
||||
);
|
||||
|
||||
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 =
|
||||
curTab === 1 ? (
|
||||
<div>
|
||||
<div style={{ marginTop: 20, marginBottom: 20 }}>
|
||||
<h2 style={{ display: "inline" }}>User Management</h2>
|
||||
<span style={{ marginLeft: 25 }}>
|
||||
Add, edit, block or change passwords.{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="/docs/organizations#user_management"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</span>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', marginTop: 20, marginBottom: 20 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', }}>
|
||||
<div>
|
||||
<h2 style={{ display: "inline" }}>User Management</h2>
|
||||
<span style={{ marginLeft: 25 }}>
|
||||
Add, edit, block or change passwords.{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="/docs/organizations#user_management"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
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 />
|
||||
<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
|
||||
style={{
|
||||
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;
|
||||
|
||||
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) {
|
||||
get2faCode(data.id);
|
||||
} 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))
|
||||
}
|
||||
|
||||
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 =
|
||||
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>
|
||||
{authentication.map((data, index) => {
|
||||
var checked = data.checked
|
||||
if (data.label !== undefined && data.label !== null && data.label.toLowerCase() === "kms shuffle storage") {
|
||||
return null
|
||||
}
|
||||
|
||||
if (checked === undefined || checked === null) {
|
||||
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
|
||||
? null
|
||||
: authentication.map((data, index) => {
|
||||
var bgColor = "#27292d";
|
||||
if (index % 2 === 0) {
|
||||
bgColor = "#1f2023";
|
||||
}
|
||||
|
||||
//console.log("Auth data: ", data)
|
||||
if (data.type === "oauth2") {
|
||||
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 (
|
||||
<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={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>
|
||||
);
|
||||
<KMSItem
|
||||
data={data}
|
||||
index={index}
|
||||
/>
|
||||
)
|
||||
|
||||
|
||||
})}
|
||||
</List>
|
||||
</div>
|
||||
|
||||
@@ -88,6 +88,7 @@ import {
|
||||
Error as ErrorIcon,
|
||||
Warning as WarningIcon,
|
||||
ArrowLeft as ArrowLeftIcon,
|
||||
ArrowRight as ArrowRightIcon,
|
||||
Cached as CachedIcon,
|
||||
DirectionsRun as DirectionsRunIcon,
|
||||
FormatListNumbered as FormatListNumberedIcon,
|
||||
@@ -123,6 +124,8 @@ import {
|
||||
Add as AddIcon,
|
||||
ErrorOutline as ErrorOutlineIcon,
|
||||
|
||||
ArrowForward as ArrowForwardIcon,
|
||||
|
||||
} from "@mui/icons-material";
|
||||
|
||||
//import * as cytoscape from "cytoscape";
|
||||
@@ -378,7 +381,7 @@ const svgSize = 24;
|
||||
|
||||
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
|
||||
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 alert = useAlert()
|
||||
let navigate = useNavigate();
|
||||
@@ -499,6 +502,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
const [selectedApp, setSelectedApp] = React.useState({});
|
||||
const [selectedAction, setSelectedAction] = React.useState({});
|
||||
const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
|
||||
const [selectedMeta, setSelectedMeta] = React.useState(undefined);
|
||||
|
||||
// Disabled streaming for now
|
||||
const [streamDisabled, setStreamDisabled] = React.useState(true)
|
||||
@@ -950,6 +954,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
})
|
||||
.then((responseJson) => {
|
||||
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.includes("404: Not Found") && responseJson.reason.length > 25) {
|
||||
// Translate <img> into markdown ![]()
|
||||
@@ -8018,10 +8026,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
backgroundColor: theme.palette.surfaceColor,
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
};
|
||||
}
|
||||
|
||||
const VariableItem = (props) => {
|
||||
const { variable, index, type } = props;
|
||||
const VariableItem = (props) => {
|
||||
const { variable, index, type } = props;
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
@@ -8792,7 +8800,25 @@ const AngularWorkflow = (defaultprops) => {
|
||||
? "cloud"
|
||||
: environments[defaultEnvironmentIndex] === undefined
|
||||
? "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
|
||||
if (cy !== undefined && cy !== null) {
|
||||
@@ -8840,7 +8866,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
app.categories.length > 0
|
||||
? app.categories[0]
|
||||
: "",
|
||||
authentication_id: "",
|
||||
authentication_id: authId,
|
||||
finished: false,
|
||||
template: app.template === true ? true : false,
|
||||
};
|
||||
@@ -9722,30 +9748,49 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedAction(newSelectedAction);
|
||||
setUpdate(Math.random());
|
||||
// Last fix for params
|
||||
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 (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;
|
||||
if (param.example !== undefined && param.example !== null && param.example.length > 0) {
|
||||
if (param.value === undefined || param.value === null || param.value.length === 0) {
|
||||
param.value = param.example
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// 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 [showCopying, setShowCopying] = React.useState(true)
|
||||
|
||||
@@ -18101,7 +18211,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
<h2>Details</h2>
|
||||
<Tooltip
|
||||
color="primary"
|
||||
title="Rerun workflow"
|
||||
title="Rerun workflow (uses same startnode as the original)"
|
||||
placement="top"
|
||||
style={{ zIndex: 50000 }}
|
||||
>
|
||||
@@ -18147,7 +18257,80 @@ const AngularWorkflow = (defaultprops) => {
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : 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
|
||||
color="primary"
|
||||
title="Explore logs for the workflow"
|
||||
@@ -18171,7 +18354,8 @@ const AngularWorkflow = (defaultprops) => {
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
: null}
|
||||
|
||||
</div>
|
||||
{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, }}>
|
||||
@@ -18801,21 +18985,49 @@ const AngularWorkflow = (defaultprops) => {
|
||||
validate.result = JSON.parse(validate.result)
|
||||
}
|
||||
|
||||
const AppResultVariable = ({ data }) => {
|
||||
const AppResultVariable = ({ data, action }) => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const showVariable = data.value.length < 60
|
||||
|
||||
// Check if it's valid JSON
|
||||
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 (
|
||||
<div style={{ maxWidth: 600, overflowX: "hidden", }}>
|
||||
{data.value.length > 60 || checked.valid ?
|
||||
<IconButton
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
marginTop: 5,
|
||||
cursor: "pointer",
|
||||
marginTop: 5,
|
||||
cursor: "pointer",
|
||||
padding: 3,
|
||||
border: "1px solid rgba(255,255,255,0.3)",
|
||||
borderRadius: theme.palette.borderRadius,
|
||||
@@ -18868,8 +19080,17 @@ const AngularWorkflow = (defaultprops) => {
|
||||
variant="body2"
|
||||
style={{
|
||||
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}
|
||||
</Typography>
|
||||
@@ -18981,6 +19202,10 @@ const AngularWorkflow = (defaultprops) => {
|
||||
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 count of "http" is more than one, 1, it's prolly invalid
|
||||
var additionalinfo = ""
|
||||
@@ -19006,7 +19231,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
|
||||
if (stringjson.includes("connectionerror")) {
|
||||
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."
|
||||
@@ -19352,7 +19577,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<AppResultVariable key={index} data={data} />
|
||||
<AppResultVariable key={index} data={data} action={selectedResult.action} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -20184,6 +20409,8 @@ const AngularWorkflow = (defaultprops) => {
|
||||
//if (configureWorkflowModalOpen) {
|
||||
// setSelectedAction({});
|
||||
//}
|
||||
//
|
||||
setSelectedMeta(undefined)
|
||||
}}
|
||||
PaperProps={{
|
||||
style: {
|
||||
@@ -20315,11 +20542,27 @@ const AngularWorkflow = (defaultprops) => {
|
||||
overflowY: "auto",
|
||||
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 === null ||
|
||||
selectedApp.documentation.length === 0 ? (
|
||||
<span style={{ textAlign: "center" }}>
|
||||
<span
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
<Typography
|
||||
variant="body1"
|
||||
style={{ marginLeft: 25, marginRight: 25 }}
|
||||
@@ -20330,15 +20573,39 @@ const AngularWorkflow = (defaultprops) => {
|
||||
style={{
|
||||
marginTop: 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
|
||||
app.
|
||||
</Typography>
|
||||
|
||||
<div
|
||||
style={{
|
||||
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 /> Create Docs
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<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
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
@@ -20385,6 +20652,84 @@ const AngularWorkflow = (defaultprops) => {
|
||||
)}
|
||||
</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 /> 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
|
||||
components={{
|
||||
img: Img,
|
||||
@@ -20405,6 +20750,7 @@ const AngularWorkflow = (defaultprops) => {
|
||||
>
|
||||
{selectedApp.documentation}
|
||||
</Markdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -955,7 +955,6 @@ const Apps = (props) => {
|
||||
</Link>
|
||||
: null
|
||||
|
||||
console.log("Sharing config: ", sharingConfiguration);
|
||||
const activateButton =
|
||||
selectedApp.generated && !selectedApp.activated ? (
|
||||
<div>
|
||||
@@ -1281,10 +1280,15 @@ const Apps = (props) => {
|
||||
{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">
|
||||
<Button
|
||||
variant="contained"
|
||||
variant={selectedApp.reference_org === userdata.active_org.id ? "outlined" : "contained"}
|
||||
component="label"
|
||||
color="primary"
|
||||
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 searchParams = tmpurl.searchParams;
|
||||
const queryID = searchParams.get("queryID");
|
||||
@@ -1316,7 +1320,7 @@ const Apps = (props) => {
|
||||
console.log("No query to handle when activating");
|
||||
}
|
||||
|
||||
activateApp(selectedApp.id, true)
|
||||
activateApp(selectedApp.id, true, true)
|
||||
}}
|
||||
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 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, {
|
||||
method: 'GET',
|
||||
@@ -1876,7 +1883,7 @@ const Apps = (props) => {
|
||||
if (appExists) {
|
||||
toast("App deactivated for your organization! Existing workflows with the app will continue to work.")
|
||||
} else {
|
||||
toast("App activated for your organization!")
|
||||
toast("App activation changed for your organization!")
|
||||
}
|
||||
|
||||
if (refresh === true) {
|
||||
@@ -2021,8 +2028,6 @@ const Apps = (props) => {
|
||||
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
|
||||
}
|
||||
])
|
||||
} else {
|
||||
console.log("No query to handle when activating")
|
||||
}
|
||||
|
||||
activateApp(hit.objectID, true)
|
||||
|
||||
@@ -156,7 +156,13 @@ export const OuterLink = (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) => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Divider,
|
||||
TextField,
|
||||
Modal,
|
||||
Switch,
|
||||
} from "@mui/material";
|
||||
//import { useAlert
|
||||
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 { Tooltip } from "@mui/material";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { Box } from "@mui/system";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
|
||||
|
||||
const Settings = (props) => {
|
||||
const { globalUrl, isLoaded, userdata, setUserData } = props;
|
||||
@@ -32,6 +36,11 @@ const Settings = (props) => {
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = 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 [fileBase64, setFileBase64] = React.useState(
|
||||
// userdata.image === undefined || userdata.image === null
|
||||
@@ -87,6 +96,82 @@ const Settings = (props) => {
|
||||
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) => {
|
||||
var currentOwner = false;
|
||||
if (data.owner.address === userdata.eth_info.account) {
|
||||
@@ -938,8 +1023,7 @@ const Settings = (props) => {
|
||||
>
|
||||
Submit password change
|
||||
</Button>
|
||||
<h3>{passwordFormMessage}</h3>
|
||||
|
||||
<h3>{passwordFormMessage}</h3>
|
||||
|
||||
{isCloud && (
|
||||
<>
|
||||
|
||||
@@ -2153,7 +2153,7 @@ const Workflows = (props) => {
|
||||
}
|
||||
|
||||
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}>
|
||||
{selectedCategory !== "" ?
|
||||
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
|
||||
@@ -2206,8 +2206,17 @@ const Workflows = (props) => {
|
||||
<Typography>
|
||||
Edit '{data.name}'
|
||||
</Typography>
|
||||
|
||||
<br />
|
||||
|
||||
{isDistributed || hasSuborgs ?
|
||||
<Typography>
|
||||
This is a parentorg-controlled workflow.
|
||||
</Typography>
|
||||
: null}
|
||||
</div>
|
||||
} placement="right">
|
||||
|
||||
<Typography
|
||||
variant="body1"
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user