Many fixes for frontend and backend related to Oauth2 and environment control

This commit is contained in:
Frikky
2023-11-24 12:03:20 +01:00
parent 8c6a39f658
commit 1fd46a7307
14 changed files with 494 additions and 302 deletions
+108 -54
View File
@@ -16,6 +16,7 @@ import {
OutlinedInput,
Checkbox,
Card,
Chip,
Tooltip,
FormControlLabel,
Typography,
@@ -485,6 +486,14 @@ If you're interested, please let me know a time that works for you, or set up a
return `mailto:${admins}?bcc=frikky@shuffler.io,binu@shuffler.io&subject=${subject}&body=${body}`
}
const changeDistribution = (data) => {
//changeDistributed(data, !isDistributed)
console.log("Should change distribution to be shared among suborgs")
editAuthenticationConfig(data.id, "suborg_distribute")
}
const deleteAuthentication = (data) => {
toast("Deleting auth " + data.label);
@@ -800,10 +809,10 @@ If you're interested, please let me know a time that works for you, or set up a
});
};
const editAuthenticationConfig = (id) => {
const editAuthenticationConfig = (id, parentAction) => {
const data = {
id: id,
action: "assign_everywhere",
action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere",
};
const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config";
@@ -821,9 +830,9 @@ If you're interested, please let me know a time that works for you, or set up a
.then((response) =>
response.json().then((responseJson) => {
if (responseJson["success"] === false) {
toast("Failed overwriting appauth in workflows");
toast("Failed overwriting appauth");
} else {
toast("Successfully updated auth everywhere!");
toast("Successfully updated auth!");
setSelectedUserModalOpen(false);
setTimeout(() => {
getAppAuthentication();
@@ -1732,7 +1741,7 @@ If you're interested, please let me know a time that works for you, or set up a
const userId = user.id;
const data = { user_id: userId };
console.log(user, userdata)
toast("Generating new API key")
var fetchdata = {
method: "POST",
@@ -3440,6 +3449,7 @@ If you're interested, please let me know a time that works for you, or set up a
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
/>
<ListItemText primary="Actions" />
<ListItemText primary="Delegation" />
</ListItem>
{schedules === undefined || schedules === null
? null
@@ -3658,10 +3668,13 @@ If you're interested, please let me know a time that works for you, or set up a
style={{ minWidth: 125, maxWidth: 125, overflow: "hidden" }}
/>
<ListItemText
primary="Created"
primary="Edited"
style={{ minWidth: 230, maxWidth: 230, overflow: "hidden" }}
/>
<ListItemText primary="Actions" />
<ListItemText primary="Actions"
style={{ minWidth: 150, maxWidth: 150, }}
/>
<ListItemText primary="Distribution" />
</ListItem>
{authentication === undefined || authentication === null
? null
@@ -3693,6 +3706,8 @@ 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
@@ -3768,31 +3783,32 @@ If you're interested, please let me know a time that works for you, or set up a
minWidth: 230,
overflow: "hidden",
}}
primary={new Date(data.created * 1000).toISOString()}
primary={new Date(data.edited * 1000).toISOString()}
/>
<ListItemText>
<IconButton
onClick={() => {
updateAppAuthentication(data);
}}
disabled={data.org_id !== selectedOrganization.id ? true : false}
>
<EditIcon color="primary" />
<EditIcon color="secondary" />
</IconButton>
{data.defined ? (
<Tooltip
color="primary"
title="Set in EVERY workflow"
title="Set in EVERY workflow in the organization"
placement="top"
>
<IconButton
style={{ marginRight: 10 }}
disabled={data.defined === false}
disabled={data.defined === false || data.org_id !== selectedOrganization.id ? true : false}
onClick={() => {
editAuthenticationConfig(data.id);
}}
>
<SelectAllIcon
color={data.defined ? "primary" : "secondary"}
color={"secondary"}
/>
</IconButton>
</Tooltip>
@@ -3803,23 +3819,54 @@ If you're interested, please let me know a time that works for you, or set up a
placement="top"
>
<IconButton
style={{ marginRight: 10 }}
style={{}}
onClick={() => {}}
disabled={data.org_id !== selectedOrganization.id ? true : false}
>
<SelectAllIcon
color={data.defined ? "primary" : "secondary"}
color="secondary"
/>
</IconButton>
</Tooltip>
)}
<IconButton
style={{marginLeft: 0, }}
disabled={data.org_id !== selectedOrganization.id ? true : false}
onClick={() => {
deleteAuthentication(data);
}}
>
<DeleteIcon color="primary" />
<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>
);
})}
@@ -4018,49 +4065,56 @@ If you're interested, please let me know a time that works for you, or set up a
<ListItemText
style={{ minWidth: 100, maxWidth: 100 }}
primary={
<Tooltip
title={"Copy Orborus command"}
style={{}}
aria-label={"Copy orborus command"}
>
<IconButton
style={{}}
disabled={environment.Type === "cloud"}
onClick={() => {
if (environment.Type === "cloud") {
toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.")
return
}
<Tooltip
title={"Copy Orborus command"}
style={{}}
aria-label={"Copy orborus command"}
>
<IconButton
style={{}}
disabled={environment.Type === "cloud"}
onClick={() => {
if (environment.Type === "cloud") {
toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.")
return
}
const elementName = "copy_element_shuffle";
const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth
const commandData = `docker run --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${globalUrl}" --name="shuffle-orborus" -d ghcr.io/shuffle/shuffle-orborus:latest`
var copyText = document.getElementById(elementName);
if (copyText !== null && copyText !== undefined) {
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
toast("Can only copy over HTTPS (port 3443)");
return;
}
if (props.userdata.active_org === undefined || props.userdata.active_org === null) {
toast("No active organization yet. Are you logged in?")
return
}
navigator.clipboard.writeText(commandData);
copyText.select();
copyText.setSelectionRange(
0,
99999
); /* For mobile devices */
const elementName = "copy_element_shuffle";
const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth
const newUrl = globalUrl === "https://shuffler.io" ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" : globalUrl
/* Copy the text inside the text field */
document.execCommand("copy");
const commandData = `docker run --volume "/var/run/docker.sock:/var/run/docker.sock" -e ENVIRONMENT_NAME="${environment.Name}" -e 'AUTH=${auth}' -e ORG="${props.userdata.active_org.id}" -e DOCKER_API_VERSION=1.40 -e BASE_URL="${newUrl}" --name="shuffle-orborus" -d ghcr.io/shuffle/shuffle-orborus:latest`
var copyText = document.getElementById(elementName);
if (copyText !== null && copyText !== undefined) {
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
toast("Can only copy over HTTPS (port 3443)");
return;
}
toast("Orborus command copied to clipboard");
}
}}
>
<FileCopyIcon disabled={environment.Type === "cloud"} style={{ color: environment.Type === "cloud" ? "rgba(255,255,255,0.2)" : "rgba(255,255,255,0.8)" }} />
</IconButton>
</Tooltip>
}
navigator.clipboard.writeText(commandData);
copyText.select();
copyText.setSelectionRange(
0,
99999
); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
toast("Orborus command copied to clipboard");
}
}}
>
<FileCopyIcon disabled={environment.Type === "cloud"} style={{ color: environment.Type === "cloud" ? "rgba(255,255,255,0.2)" : "rgba(255,255,255,0.8)" }} />
</IconButton>
</Tooltip>
}
/>
<ListItemText
+115 -34
View File
@@ -501,6 +501,7 @@ const AngularWorkflow = (defaultprops) => {
const [selectedAction, setSelectedAction] = React.useState({});
const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
const [streamDisabled, setStreamDisabled] = React.useState(false);
const [executionRequest, setExecutionRequest] = React.useState({});
const [executionRunning, setExecutionRunning] = React.useState(false);
@@ -551,10 +552,18 @@ const AngularWorkflow = (defaultprops) => {
props.userdata.active_org !== undefined
? props.userdata.active_org.cloud_sync === true
: false;
const isCloud =
window.location.host === "localhost:3002" ||
window.location.host === "shuffler.io";
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
useEffect(() => {
return () => {
console.log("UNMOUNTING USER!")
sendStreamRequest({
"item": "workflow",
"type": "leave",
"id": workflow.id,
})
}
}, [])
/*
useEffect(() => {
console.log("In useeffect for workflow: ", workflow)
@@ -918,7 +927,7 @@ const AngularWorkflow = (defaultprops) => {
});
};
const setNewAppAuth = (appAuthData) => {
const setNewAppAuth = (appAuthData, refresh) => {
fetch(globalUrl + "/api/v1/apps/authentication", {
method: "PUT",
headers: {
@@ -937,9 +946,14 @@ const AngularWorkflow = (defaultprops) => {
})
.then((responseJson) => {
if (!responseJson.success) {
toast("Failed to set app auth: " + responseJson.reason);
toast("Error: " + responseJson.reason);
} else {
getAppAuthentication(true, false);
if (refresh === true) {
getAppAuthentication(true, true, true);
} else {
getAppAuthentication(true, false);
}
setAuthenticationModalOpen(false);
// Needs a refresh with the new authentication..
@@ -1003,16 +1017,15 @@ const AngularWorkflow = (defaultprops) => {
}
setExecutionModalView(1);
start();
setExecutionRequest({
execution_id: execution.execution_id,
authorization: execution.authorization,
});
const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem)
//props.history.push(curpath + newitem);
start();
//const newitem = removeParam("execution_id", cursearch);
//navigate(curpath + newitem)
} else {
console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView)
@@ -1025,8 +1038,8 @@ const AngularWorkflow = (defaultprops) => {
setExecutionRequest(cur_execution);
start();
const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem)
//const newitem = removeParam("execution_id", cursearch);
//navigate(curpath + newitem)
setTimeout(() => {
stop()
@@ -1310,12 +1323,28 @@ const AngularWorkflow = (defaultprops) => {
const sendStreamRequest = (body) => {
//console.log("Stream not activated yet.")
return
if (!isCloud) {
console.log("Stream not activated yet for onprem")
return
}
if (streamDisabled) {
console.log("Stream disabled")
return
}
// Session may be important here huh
body.user_id = userdata.id
fetch(`${globalUrl}/api/v1/workflows/${props.match.params.key}/stream`, {
//const url = ${globalUrl}/api/v1/workflows/${props.match.params.key}/stream
//const streamUrl = "http://localhost:5002"
console.log("Stream request: ", body)
const streamUrl = "https://stream.shuffler.io"
const url = `${streamUrl}/api/v1/workflows/${props.match.params.key}/stream`
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -1337,7 +1366,7 @@ const AngularWorkflow = (defaultprops) => {
})
.catch((error) => {
console.log("Stream send error: ", error.toString())
//toast(error.toString());
setStreamDisabled(true)
})
}
@@ -1872,6 +1901,7 @@ const AngularWorkflow = (defaultprops) => {
setSelectedAction(selectedAction);
setWorkflow(workflow);
saveWorkflow(workflow);
toast("Added and updated authentication!");
shouldClose = true
} else {
@@ -2084,7 +2114,9 @@ const AngularWorkflow = (defaultprops) => {
}
const onChunkedResponseError = (err) => {
console.error(err)
if (streamDisabled) {
return
}
}
@@ -2570,14 +2602,30 @@ const AngularWorkflow = (defaultprops) => {
}
const startWorkflowStream = async (workflowId) => {
const timeout = 60000
if (!isCloud) {
console.log("Not cloud, not starting workflow stream")
return
}
return
if (streamDisabled) {
console.log("Stream disabled")
return
}
const timeout = 60000
//const url = `${globalUrl}/api/v1/workflows/${workflowId}/stream`
//const streamUrl = "https://shuffle-streaming-backend-stbuwivzoq-ew.a.run.app"
//
const streamUrl = "https://stream.shuffler.io"
const url = `${streamUrl}/api/v1/workflows/${workflowId}/stream`
while (true) {
if (streamDisabled) {
break
}
// Wait 1 second before next request just in case of timeouts
await new Promise(r => setTimeout(r, 1000));
await fetchWithTimeout(`${globalUrl}/api/v1/workflows/${workflowId}/stream`, {
await fetchWithTimeout(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
@@ -6261,7 +6309,6 @@ const AngularWorkflow = (defaultprops) => {
}
insertedNodes = insertedNodes.concat(newedges);
setWorkflow(inputworkflow);
// Reset view for cytoscape
@@ -6271,6 +6318,8 @@ const AngularWorkflow = (defaultprops) => {
} else {
setElements(insertedNodes);
}
console.log("Setupgraph done 2!")
};
const removeNode = (nodeId) => {
@@ -6576,6 +6625,7 @@ const AngularWorkflow = (defaultprops) => {
}
// preview: true,
console.log("In POST graph setup 2")
cy.fit(null, 200);
cy.on("boxselect", "node", (e) => {
@@ -6624,6 +6674,7 @@ const AngularWorkflow = (defaultprops) => {
document.title = "Workflow - " + workflow.name;
console.log("In POST graph setup 3")
startWorkflowStream(props.match.params.key);
registerKeys();
@@ -7516,14 +7567,30 @@ const AngularWorkflow = (defaultprops) => {
description = app.actions[actionIndex].description
}
const parsedEnvironments =
var parsedEnvironments =
environments === null || environments === []
? "cloud"
: environments[defaultEnvironmentIndex] === undefined
? "cloud"
: environments[defaultEnvironmentIndex].Name;
// activated: app.generated === true ? app.activated === false ? false : true : true,
// 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) {
const foundnodes = cy.nodes().jsons()
if (foundnodes !== undefined && foundnodes !== null && foundnodes.length > 0) {
// As they should all be the same, this is just an override
for (let nodekey in foundnodes) {
const curnode = foundnodes[nodekey]
if (curnode.data.environment !== undefined && curnode.data.environment !== null && curnode.data.environment.length > 0) {
console.log("Found environment: ", curnode.data.environment)
parsedEnvironments = curnode.data.environment
break
}
}
}
}
console.log("Discovered environment: ", parsedEnvironments)
const newAppData = {
name: app.actions[actionIndex].name,
label: actionLabel,
@@ -8139,6 +8206,25 @@ const AngularWorkflow = (defaultprops) => {
</div>
)
})}
{visibleApps.length <= 4 ? (
<div
style={{ textAlign: "center", width: leftBarSize, marginTop: 40, maxWidth: 340, overflow: "hidden", }}
onLoad={() => {
}}
>
<Typography variant="body1" color="textSecondary">
Click one of the relevant public apps below to Activate it for your organization.
</Typography>
<InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
console.log("CLICKED")
}}>
<CustomSearchBox />
<Index indexName="appsearch">
<CustomAppHits />
</Index>
</InstantSearch>
</div>
) : null}
</div>
) : apps.length > 0 ? (
<div
@@ -8148,7 +8234,7 @@ const AngularWorkflow = (defaultprops) => {
}}
>
<Typography variant="body1" color="textSecondary">
Couldn't find the app you're looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization.
Couldn't find the apps you were looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization.
</Typography>
<InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
console.log("CLICKED")
@@ -8364,7 +8450,6 @@ const AngularWorkflow = (defaultprops) => {
}
}
console.log("NEW ACTION: ", newSelectedAction);
setSelectedAction(newSelectedAction);
setUpdate(Math.random());
@@ -13259,10 +13344,6 @@ const AngularWorkflow = (defaultprops) => {
"user": "Anonymous",
"user_id": "user_id",
"color": "blue",
}, {
"user": "frikky",
"user_id": "user_id",
"color": "red",
}]
@@ -13318,18 +13399,18 @@ const AngularWorkflow = (defaultprops) => {
const showErrors = !isMobile && !workflow.public && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 ?
<div
style={{
border: "1px solid rgba(255,255,255,0.3)",
border: "1px solid rgba(255,255,255,0.1)",
position: "absolute",
bottom: 130,
left: leftBarSize+20,
color: "white",
padding: 5,
padding: 10,
borderRadius: theme.palette.borderRadius,
}}
>
<Typography variant="body22">
<WarningIcon style={{color: "yellow", marginRight: 5, height: 15, width: 15, }} />
<b>{workflow.errors.length} Potential Workflow Issue{workflow.errors.length > 1 ? "s" : ""}</b>
<WarningIcon style={{marginRight: 5, height: 15, width: 15, }} />
<b>{workflow.errors.length} Workflow Issue{workflow.errors.length > 1 ? "s" : ""}</b>
</Typography>
<Typography
variant="body2"
+8 -2
View File
@@ -413,7 +413,9 @@ const AppCreator = (defaultprops) => {
const [oauth2Scopes, setOauth2Scopes] = useState([]);
const [oauth2Type, setOauth2Type] = useState("delegated");
const [oauth2GrantType, setOauth2GrantType] = useState("client_credentials");
//client_credentials
const [oauth2GrantType, setOauth2GrantType] = useState("");
const defaultAuth = {
name: "",
type: "header",
@@ -1757,7 +1759,7 @@ const AppCreator = (defaultprops) => {
// Kind of fucked up, but it works for now?
if (value["x-grant-type"] !== undefined && value["x-grant-type"] !== null && value["x-grant-type"].length !== 0) {
setOauth2Type(value["x-grant-type"])
setOauth2GrantType(value["x-grant-type"])
}
//console.log("FLOW2: ", value[flowkey][basekey])
@@ -5988,6 +5990,10 @@ const AppCreator = (defaultprops) => {
fullWidth
onChange={(e) => {
setOauth2Type(e.target.value);
if (e.target.value === "application" && oauth2GrantType === "") {
setOauth2GrantType("client_credentials")
}
}}
value={oauth2Type}
style={{
+2 -2
View File
@@ -368,14 +368,14 @@ const Docs = (defaultprops) => {
}
const markdownStyle = {
color: "rgba(255, 255, 255, 0.65)",
color: "rgba(255, 255, 255, 0.90)",
overflow: "hidden",
paddingBottom: 100,
margin: "auto",
maxWidth: "100%",
minWidth: "100%",
overflow: "hidden",
fontSize: isMobile ? "1.3rem" : "1.0rem",
fontSize: isMobile ? "1.3rem" : "1.1rem",
};
function OuterLink(props) {
+10 -10
View File
@@ -1982,16 +1982,16 @@ const Workflows = (props) => {
</div>
</Tooltip>
<Tooltip arrow title={
<span style={{}}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
<img src={data.image} alt={data.name} style={{backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Edit {data.name}
</Typography>
</span>
} placement="bottom">
<Typography
<div style={{width: "100%", minWidth: 250, maxWidth: 310, }}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ?
<img src={data.image} alt={data.name} style={{backgroundColor: theme.palette.surfaceColor, maxWidth: 300, minWidth: 250, borderRadius: theme.palette.borderRadius, }} />
: null}
<Typography>
Edit {data.name}
</Typography>
</div>
} placement="left">
<Typography
variant="body1"
style={{
marginBottom: 0,