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
+1
View File
@@ -14,6 +14,7 @@
"@mui/styles": "^5.14.0",
"@mui/x-data-grid": "^5.17.11",
"@mui/x-date-pickers": "^6.11.1",
"@uiw/codemirror-theme-vscode": "^4.21.20",
"@uiw/codemirror-themes": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9",
"@use-it/interval": "^1.0.0",
+30 -29
View File
@@ -269,35 +269,8 @@ const EditWorkflow = (props) => {
/>
</div>
<div style={{display: "flex", marginTop: 10, }}>
<MuiChipsInput
style={{ flex: 1, maxHeight: 40, }}
InputProps={{
style: {
color: "white",
},
}}
placeholder="Tags"
color="primary"
fullWidth
value={newWorkflowTags}
onChange={(chip) => {
console.log("Chip: ", chip)
//newWorkflowTags.push(chip);
setNewWorkflowTags(chip);
}}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
}}
onDelete={(chip, index) => {
console.log("Deleting: ", chip, index)
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
setUpdate(Math.random());
}}
/>
{usecases !== null && usecases !== undefined && usecases.length > 0 ?
<FormControl style={{flex: 1, marginLeft: 5, }}>
<FormControl style={{flex: 1, marginRight: 5,}}>
<InputLabel htmlFor="grouped-select-usecase">Usecases</InputLabel>
<Select
defaultValue=""
@@ -350,6 +323,33 @@ const EditWorkflow = (props) => {
</Select>
</FormControl>
: null}
<MuiChipsInput
style={{ flex: 1, maxHeight: 120, overflow: "auto",}}
InputProps={{
style: {
color: "white",
},
}}
placeholder="Tags"
color="primary"
fullWidth
value={newWorkflowTags}
onChange={(chip) => {
console.log("Chip: ", chip)
//newWorkflowTags.push(chip);
setNewWorkflowTags(chip);
}}
onAdd={(chip) => {
newWorkflowTags.push(chip);
setNewWorkflowTags(newWorkflowTags);
}}
onDelete={(chip, index) => {
console.log("Deleting: ", chip, index)
newWorkflowTags.splice(index, 1);
setNewWorkflowTags(newWorkflowTags);
setUpdate(Math.random());
}}
/>
</div>
{showMoreClicked === true ?
@@ -365,7 +365,8 @@ const EditWorkflow = (props) => {
onChange={(e) => {
console.log("Data: ", e.target.value)
innerWorkflow.workflow_type = e.target.value
//innerWorkflow.workflow_type = e.target.value
innerWorkflow.status = e.target.value
setInnerWorkflow(innerWorkflow)
}}
>
+5 -2
View File
@@ -1352,14 +1352,17 @@ const Header = (props) => {
);
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
return !isMobile ?
isLoggedIn ?
//
/*
!isLoggedIn ?
<div style={{minHeight: 68, maxHeight: 68, backgroundColor: theme.palette.backgroundColor, }}>
<BrowserView style={{position: "sticky", top: 0, }}>
{loginTextBrowser}
</BrowserView>
</div>
:
*/
return !isMobile ?
<AppBar
position="sticky"
color="transparent"
+36 -24
View File
@@ -112,8 +112,6 @@ const AuthenticationOauth2 = (props) => {
authenticationType.client_secret.length > 0
);
console.log("AUTH: ", authenticationType)
const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : "");
const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "");
@@ -125,7 +123,7 @@ const AuthenticationOauth2 = (props) => {
const [offlineAccess, setOfflineAccess] = React.useState(true);
const allscopes = authenticationType.scope !== undefined && authenticationType.scope !== null ? authenticationType.scope : [];
const [selectedScopes, setSelectedScopes] = React.useState(allscopes !== null && allscopes !== undefined ? allscopes.length > 0 && allscopes.length <= 3 ? [allscopes[0]] : [] : [])
const [selectedScopes, setSelectedScopes] = React.useState(allscopes !== null && allscopes !== undefined ? allscopes.length > 0 && allscopes.length <= 3 ? allscopes : [] : [])
const [manuallyConfigure, setManuallyConfigure] = React.useState(
defaultConfigSet ? false : true
@@ -303,6 +301,23 @@ const AuthenticationOauth2 = (props) => {
if ((authenticationType.redirect_uri === undefined || authenticationType.redirect_uri === null || authenticationType.redirect_uri.length === 0) && (authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0)) {
console.log("No redirect URI found, and token URI found. Assuming client credentials flow and saving directly in the database")
var tokenUri = authenticationType.token_uri;
if (oauthUrl !== undefined && oauthUrl !== null && oauthUrl.length > 0 && selectedApp !== undefined && selectedApp !== null) {
var same = false
for (var i = 0; i < selectedApp.authentication.parameters.length; i++) {
const param = selectedApp.authentication.parameters[i];
if (param.name === "url" && (param.value === oauthUrl || param.example === oauthUrl)) {
same = true
break
}
}
if (!same) {
tokenUri = oauthUrl
}
}
// Find app.configuration=true fields in the app.paramters
var parsedFields = [{
"key": "client_id",
@@ -318,7 +333,7 @@ const AuthenticationOauth2 = (props) => {
},
{
"key": "token_uri",
"value": authenticationType.token_uri,
"value": tokenUri,
}]
if (authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0) {
@@ -360,14 +375,14 @@ const AuthenticationOauth2 = (props) => {
"reference_workflow": workflowId,
}
setNewAppAuth(appAuthData)
setNewAppAuth(appAuthData, true)
// Wait 1 second, then get app auth with update
//
if (getAppAuthentication !== undefined) {
setTimeout(() => {
getAppAuthentication(true, true, true);
}, 1000)
}
//if (getAppAuthentication !== undefined) {
// setTimeout(() => {
// getAppAuthentication(true, true, true);
// }, 1000)
//}
return
}
@@ -397,8 +412,6 @@ const AuthenticationOauth2 = (props) => {
}
const authentication_url = authenticationType.token_uri;
//console.log("AUTH: ", authenticationType)
//console.log("SCOPES2: ", resources)
const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`;
const workflowId = workflow !== undefined ? workflow.id : "";
var state = `workflow_id%3D${workflowId}%26reference_action_id%3d${selectedAction.app_id}%26app_name%3d${selectedAction.app_name}%26app_id%3d${selectedAction.app_id}%26app_version%3d${selectedAction.app_version}%26authentication_url%3d${authentication_url}%26scope%3d${resources}%26client_id%3d${client_id}%26client_secret%3d${client_secret}`;
@@ -523,7 +536,6 @@ const AuthenticationOauth2 = (props) => {
}
const handleSubmitCheck = () => {
console.log("NEW AUTH: ", authenticationOption);
if (authenticationOption.label.length === 0) {
authenticationOption.label = `Auth for ${selectedApp.name}`;
//toast("Label can't be empty")
@@ -673,7 +685,7 @@ const AuthenticationOauth2 = (props) => {
)}
</Button>
if (authButtonOnly === true && (authenticationType.grant_type === undefined || authenticationType.grant_type === null || authenticationType.grant_type === "")) {
if (authButtonOnly === true && (authenticationType.redirect_uri !== undefined && authenticationType.redirect_uri !== null && authenticationType.redirect_uri.length > 0) && (authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0)) {
return autoAuthButton
}
@@ -777,10 +789,14 @@ const AuthenticationOauth2 = (props) => {
setOauthUrl(data.value);
}
const defaultValue = data.name === "url" && authenticationType.token_uri !== undefined && authenticationType.token_uri !== null && authenticationType.token_uri.length > 0 && (authenticationType.authorizationUrl === undefined || authenticationType.authorizationUrl === null || authenticationType.authorizationUrl.length === 0) ? authenticationType.token_uri : data.value === undefined || data.value === null ? "" : data.value
const fieldname = data.name === "url" && authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0 ? "Token URL" : data.name
return (
<div key={index} style={{ marginTop: 10 }}>
<LockOpenIcon style={{ marginRight: 10 }} />
<b>{data.name}</b>
<b>{fieldname}</b>
{data.schema !== undefined &&
data.schema !== null &&
@@ -793,6 +809,7 @@ const AuthenticationOauth2 = (props) => {
}}
defaultValue={"false"}
fullWidth
label={fieldname}
onChange={(e) => {
console.log("Value: ", e.target.value);
authenticationOption.fields[data.name] = e.target.value;
@@ -842,16 +859,11 @@ const AuthenticationOauth2 = (props) => {
: "text"
}
color="primary"
defaultValue={
data.value !== undefined && data.value !== null
? data.value
: ""
}
defaultValue={defaultValue}
placeholder={data.example}
onChange={(event) => {
authenticationOption.fields[data.name] =
event.target.value;
console.log("Setting oauth url");
authenticationOption.fields[data.name] = event.target.value;
console.log("Setting oauth url: ", event.target.value);
setOauthUrl(event.target.value);
//const [oauthUrl, setOauthUrl] = React.useState("")
}}
+8
View File
@@ -181,6 +181,8 @@ const ParsedAction = (props) => {
const [fieldCount, setFieldCount] = React.useState(0);
const [hiddenDescription, setHiddenDescription] = React.useState(true);
const [autoCompleting, setAutocompleting] = React.useState(false);
useEffect(() => {
if (setLastSaved !== undefined) {
@@ -2909,9 +2911,11 @@ const ParsedAction = (props) => {
marginLeft: 15,
paddingRight: 0,
}}
disabled={autoCompleting}
onClick={() => {
// aiSubmit(aiMsg, undefined, undefined, newSelectedAction)
aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
setAutocompleting(true)
}}
>
<Tooltip
@@ -2919,7 +2923,11 @@ const ParsedAction = (props) => {
title={"Autocompletes fields. Uses NAME of the action and previous values' results."}
placement="top"
>
{autoCompleting ?
<CircularProgress style={{height: 20, width: 20, }} />
:
<AutoFixHighIcon style={{ color: "rgba(255,255,255,0.7)", height: 24, }} />
}
</Tooltip>
</IconButton>
</div>
+15 -5
View File
@@ -315,16 +315,26 @@ const RuntimeDebugger = (props) => {
)
},
},
{ field: 'startTimestamp', headerName: 'Start time', width: 160, },
{ field: 'endTimestamp', headerName: 'End time', width: 160, },
{ field: 'startTimestamp', headerName: 'Start time (UTC)', width: 160, },
{ field: 'endTimestamp', headerName: 'End time (UTC)', width: 160, },
{
field: 'id',
headerName: 'Explore',
width: 65,
renderCell: (params) => (
<Link href={`/workflows/${params.row.workflow.id}?execution_id=${params.row.id}`} target="_blank" rel="noopener noreferrer">
<OpenInNewIcon fontSize="small" />
</Link>
<Tooltip arrow placement="right" title={
<Typography variant="body2" style={{whiteSpace: "pre-line", }}>
{params.row.result !== null && params.row.result !== undefined && params.row.result !== "" ?
params.row.result
:
null
}
</Typography>
} >
<Link href={`/workflows/${params.row.workflow.id}?execution_id=${params.row.id}`} target="_blank" rel="noopener noreferrer">
<OpenInNewIcon fontSize="small" />
</Link>
</Tooltip>
),
},
]
+141 -129
View File
@@ -22,6 +22,7 @@ import { isMobile } from "react-device-detect"
import { NestedMenuItem } from "mui-nested-menu"
import { GetParsedPaths, FindJsonPath } from "../views/Apps.jsx";
import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx";
import { vscodeDark, vscodeDarkInit } from '@uiw/codemirror-theme-vscode';
import {
FullscreenExit as FullscreenExitIcon,
@@ -82,6 +83,7 @@ const pythonFilters = [
{"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``},
]
/*
const shuffleTheme = createTheme({
theme: 'dark',
settings: {
@@ -110,6 +112,7 @@ const shuffleTheme = createTheme({
{ tag: t.attributeName, color: '#5c6166' },
],
});
*/
const CodeEditor = (props) => {
const {
@@ -547,7 +550,7 @@ const CodeEditor = (props) => {
var code_lines = localcodedata.split('\n')
for (var i = 0; i < code_lines.length; i++){
var current_code_line = code_lines[i]
// console.log(current_code_line)
console.log("Codeline: ", current_code_line)
var variable_occurence = current_code_line.match(/[\\]{0,1}[$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,}/g)
@@ -609,9 +612,10 @@ const CodeEditor = (props) => {
const fixedVariable = fixVariable(variable_occurence[occ])
var correctVariable = availableVariables.includes(fixedVariable)
if(!correctVariable) {
console.log("Line: ", i, "ch: ", dollar_occurence[occ])
value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: rgb(248, 106, 62, 0.9); padding-top: 2px; padding-bottom: 2px; color: white"})
}
else{
} else {
value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; padding-top: 2px; padding-bottom: 2px; color: white"})
}
// console.log(correctVariables)
@@ -674,36 +678,33 @@ const CodeEditor = (props) => {
try {
for (var i = 0; i < found.length; i++) {
try {
// For found specifically, should replace .#\d with .# with regex
//found[i] = found[i].toLowerCase()
const fixedVariable = fixVariable(found[i])
//var correctVariable = availableVariables.includes(fixedVariable)
//
var valuefound = false
for (var j = 0; j < actionlist.length; j++) {
if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){
valuefound = true
if(fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()){
continue
}
try {
if (typeof actionlist[j].example === "object") {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
valuefound = true
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else {
input = input.replace(found[i], actionlist[j].example, -1)
}
} catch (e) {
try {
if (typeof actionlist[j].example === "object") {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else {
input = input.replace(found[i], actionlist[j].example, -1)
}
} else {
// Couldn't find the correct example value
} catch (e) {
input = input.replace(found[i], actionlist[j].example, -1)
}
}
//if (!valuefound) {
// console.log("Couldn't find value "+fixedVariable)
//}
if (!valuefound && availableVariables.includes(fixedVariable)) {
var shouldbreak = false
for (var k=0; k < actionlist.length; k++){
@@ -714,46 +715,48 @@ const CodeEditor = (props) => {
for (var key in parsedPaths) {
const fullpath = "$"+actionlist[k].autocomplete.toLowerCase()+parsedPaths[key].autocomplete
if (fullpath === fixedVariable) {
//if (actionlist[k].example === undefined) {
// actionlist[k].example = "TMP"
//}
if (fullpath !== fixedVariable) {
continue
}
var new_input = ""
try {
new_input = FindJsonPath(fullpath, actionlist[k].example)
} catch (e) {
console.log("ERR IN INPUT: ", e)
}
//if (actionlist[k].example === undefined) {
// actionlist[k].example = "TMP"
//}
//console.log("Got output for: ", fullpath, new_input, actionlist[k].example, typeof new_input)
var new_input = ""
try {
new_input = FindJsonPath(fullpath, actionlist[k].example)
} catch (e) {
console.log("ERR IN INPUT: ", e)
}
if (typeof new_input === "object") {
new_input = JSON.stringify(new_input)
console.log("Got output for: ", fullpath, new_input, actionlist[k].example, typeof new_input)
if (typeof new_input === "object") {
new_input = JSON.stringify(new_input)
} else {
if (typeof new_input === "string") {
new_input = new_input
} else {
if (typeof new_input === "string") {
new_input = new_input
} else {
console.log("NO TYPE? ", typeof new_input)
try {
new_input = new_input.toString()
} catch (e) {
new_input = ""
}
console.log("NO TYPE? ", typeof new_input)
try {
new_input = new_input.toString()
} catch (e) {
new_input = ""
}
}
//console.log("FOUND2: ", fixedVariable, actionlist[j].example)
input = input.replace(fixedVariable, new_input, -1)
input = input.replace(found[i], new_input, -1)
//} catch (e) {
// input = input.replace(found[i], actionlist[k].example)
//}
shouldbreak = true
break
}
//console.log("FOUND2: ", fixedVariable, actionlist[j].example)
input = input.replace(fixedVariable, new_input, -1)
input = input.replace(found[i], new_input, -1)
//} catch (e) {
// input = input.replace(found[i], actionlist[k].example)
//}
shouldbreak = true
break
}
if (shouldbreak) {
@@ -766,7 +769,7 @@ const CodeEditor = (props) => {
}
}
} catch (e) {
//console.log("Outer replace error: ", e)
console.log("Outer replace error: ", e)
}
}
@@ -895,7 +898,7 @@ const CodeEditor = (props) => {
aria-labelledby="draggable-code-modal"
disableBackdropClick={true}
disableEnforceFocus={true}
//style={{ pointerEvents: "none" }}
//style={{ pointerEvents: "none" }}
hideBackdrop={true}
open={expansionModalOpen}
onClose={() => {
@@ -964,6 +967,7 @@ const CodeEditor = (props) => {
}}
>
<div style={{display: "flex"}}>
{/*
<DialogTitle
id="draggable-dialog-title"
style={{
@@ -974,59 +978,9 @@ const CodeEditor = (props) => {
>
Code Editor
</DialogTitle>
<IconButton
style={{
marginLeft: isMobile ? "80%" : 350,
height: 50,
width: 50,
}}
onClick={() => {
}}
>
<Tooltip
color="primary"
title={"Test Liquid in the playground"}
placement="top"
>
<a
href="https://pwwang.github.io/liquidpy/playground/"
rel="norefferer"
target="_blank"
>
<ExtensionIcon style={{color: "rgba(255,255,255,0.7)"}}/>
</a>
</Tooltip>
</IconButton>
<IconButton
style={{
height: 50,
width: 50,
}}
disabled={isAiLoading}
onClick={() => {
autoFormat(localcodedata)
}}
>
<Tooltip
color="primary"
title={"Auto format data"}
placement="top"
>
{isAiLoading ?
<CircularProgress style={{height: 20, width: 20, color: "rgba(255,255,255,0.7)"}}/>
:
<AutoFixHighIcon style={{color: "rgba(255,255,255,0.7)"}}/>
}
</Tooltip>
</IconButton>
</div>
</div>
}
*/}
{ isFileEditor ? null :
<div style={{display: "flex"}}>
<div style={{display: "flex", maxHeight: 40, }}>
<Button
id="basic-button"
aria-haspopup="true"
@@ -1035,7 +989,7 @@ const CodeEditor = (props) => {
variant="outlined"
color="secondary"
style={{
textTransform: "none",
textTransform: "none",
width: 100,
}}
onClick={(event) => {
@@ -1143,9 +1097,9 @@ const CodeEditor = (props) => {
variant="outlined"
color="secondary"
style={{
textTransform: "none",
textTransform: "none",
width: 130,
marginLeft: 170,
marginLeft: 20,
}}
onClick={(event) => {
setMenuPosition({
@@ -1393,28 +1347,86 @@ const CodeEditor = (props) => {
</Menu>
</div>
}
<span style={{
<IconButton
style={{
marginLeft: isMobile ? "80%" : 30,
height: 50,
width: 50,
}}
onClick={() => {
}}
>
<Tooltip
color="primary"
title={"Test Liquid in the playground"}
placement="top"
>
<a
href="https://pwwang.github.io/liquidpy/playground/"
rel="norefferer"
target="_blank"
>
<ExtensionIcon style={{color: "rgba(255,255,255,0.7)"}}/>
</a>
</Tooltip>
</IconButton>
<IconButton
style={{
height: 50,
width: 50,
}}
disabled={isAiLoading}
onClick={() => {
autoFormat(localcodedata)
}}
>
<Tooltip
color="primary"
title={"Auto format data"}
placement="top"
>
{isAiLoading ?
<CircularProgress style={{height: 20, width: 20, color: "rgba(255,255,255,0.7)"}}/>
:
<AutoFixHighIcon style={{color: "rgba(255,255,255,0.7)"}}/>
}
</Tooltip>
</IconButton>
</div>
</div>
}
<div style={{
borderRadius: theme.palette.borderRadius,
position: "relative",
paddingTop: 0,
minHeight: 548,
overflow: "hidden",
}}>
<CodeMirror
theme={vscodeDark}
value={localcodedata}
height={isFileEditor ? 450 : 525}
height={isFileEditor ? 450 : 500}
width={isFileEditor ? 650 : 600}
style={{
maxWidth: isFileEditor ? 450 : 500,
maxWidth: isFileEditor ? 450 : 600,
maxHeight: 548,
minHeight: 548,
wordBreak: "break-word",
marginTop: 0,
paddingTop: 0,
backgroundColor: "rgba(40,40,40,1)",
minHeight: 470,
paddingBottom: 10,
overflow: "hidden",
}}
onCursorActivity = {(value) => {
// console.log(value.getCursor())
console.log("CURSOR: ", value.getCursor())
setCurrentCharacter(value.getCursor().ch)
setCurrentLine(value.getCursor().line)
// console.log(value.getCursor().ch, value.getCursor().line)
findIndex(value.getCursor().line, value.getCursor().ch)
highlight_variables(value)
}}
onChange={(value, viewUpdate) => {
@@ -1422,20 +1434,19 @@ const CodeEditor = (props) => {
setlocalcodedata(value)
expectedOutput(value)
highlight_variables(value)
//if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){
// setEditorPopupOpen(true)
//}
}}
extensions={[]}//indentWithTab]}
theme={shuffleTheme}
options={{
styleSelectedText: true,
keyMap: 'sublime',
mode: validation === true ? "json" : "python",
lineWrapping: linewrap,
theme: vscodeDark,
}}
/>
</span>
</div>
{/*editorPopupOpen ?
<Paper
@@ -1550,13 +1561,14 @@ const CodeEditor = (props) => {
</div>
</div>
<div style={{flex: 1, marginLeft: 25, }}>
<div style={{flex: 1, marginLeft: 5, borderLeft: "1px solid rgba(255,255,255,0.3)", paddingLeft: 5, }}>
{isFileEditor ? null :
<div>
{isMobile ? null :
<DialogTitle
style={{
paddingLeft: 10,
paddingTop: 0,
display: "flex",
}}
>
@@ -1564,7 +1576,7 @@ const CodeEditor = (props) => {
Expected Output
</span>
<IconButton disabled={executing} color="primary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 100, padding: 8}} variant="contained" onClick={() => {
<IconButton disabled={executing} color="secondary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 250, padding: 8}} variant="contained" onClick={() => {
executeSingleAction(expOutput)
}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' or 'execute python' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
@@ -1613,8 +1625,8 @@ const CodeEditor = (props) => {
borderRadius: theme.palette.borderRadius,
maxHeight: 500,
minHeight: 500,
minWidth: 500,
maxWidth: 500,
minWidth: 580,
maxWidth: 580,
overflow: "auto",
whiteSpace: "pre-wrap",
}}
+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,