From 1fd46a7307c2b19b94b37b8b30ed9fe161f6c57d Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 24 Nov 2023 12:03:20 +0100 Subject: [PATCH] Many fixes for frontend and backend related to Oauth2 and environment control --- backend/go-app/main.go | 15 +- backend/go-app/walkoff.go | 11 +- frontend/package.json | 1 + frontend/src/components/EditWorkflow.jsx | 59 ++-- frontend/src/components/NewHeader.jsx | 7 +- frontend/src/components/Oauth2Auth.jsx | 60 ++-- frontend/src/components/ParsedAction.jsx | 8 + frontend/src/components/RuntimeDebugger.jsx | 20 +- frontend/src/components/ShuffleCodeEditor.jsx | 270 +++++++++--------- frontend/src/views/Admin.jsx | 162 +++++++---- frontend/src/views/AngularWorkflow.jsx | 149 +++++++--- frontend/src/views/AppCreator.jsx | 10 +- frontend/src/views/Docs.jsx | 4 +- frontend/src/views/Workflows.jsx | 20 +- 14 files changed, 494 insertions(+), 302 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index e7756dc0..33383850 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1965,6 +1965,7 @@ func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error { return err } + defer newresp.Body.Close() respBody, err := ioutil.ReadAll(newresp.Body) if err != nil { return err @@ -3513,15 +3514,13 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { ) req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey)) - - //log.Printf("[INFO] Sending org sync with autho %s", org.SyncConfig.Apikey) - newresp, err := client.Do(req) if err != nil { //log.Printf("Failed request in org sync: %s", err) return err } + defer newresp.Body.Close() respBody, err := ioutil.ReadAll(newresp.Body) if err != nil { log.Printf("[ERROR] Failed body read in job sync: %s", err) @@ -3574,6 +3573,8 @@ func runInitEs(ctx context.Context) { log.Printf("[DEBUG] Getting organizations for Elasticsearch/Opensearch") activeOrgs, err := shuffle.GetAllOrgs(ctx) + log.Printf("[DEBUG] Got %d organizations to look into", len(activeOrgs)) + setUsers := false _ = setUsers if err != nil { @@ -3697,7 +3698,7 @@ func runInitEs(ctx context.Context) { for _, schedule := range schedules { if strings.ToLower(schedule.Environment) == "cloud" { - log.Printf("Skipping cloud schedule") + log.Printf("[DEBUG] Skipping cloud schedule") continue } @@ -3705,7 +3706,9 @@ func runInitEs(ctx context.Context) { //log.Printf("Schedule time: every %d seconds", schedule.Seconds) jobret, err := newscheduler.Every(schedule.Seconds).Seconds().NotImmediately().Run(job(schedule)) if err != nil { - log.Printf("Failed to schedule workflow: %s", err) + log.Printf("[ERROR] Failed to start schedule for workflow %s: %s", schedule.WorkflowId, err) + } else { + log.Printf("[DEBUG] Successfully started schedule for workflow %s", schedule.WorkflowId) } scheduledJobs[schedule.Id] = jobret @@ -4725,7 +4728,7 @@ func initHandlers() { log.Printf("[DEBUG] Initialized Shuffle database connection. Setting up environment.") if elasticConfig == "elasticsearch" { - time.Sleep(5 * time.Second) + time.Sleep(10 * time.Second) go runInitEs(ctx) } else { //go shuffle.runInit(ctx) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 32fb67ef..83656aa4 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -705,7 +705,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { // Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workflowExecutionId string, actionResult shuffle.ActionResult, resp http.ResponseWriter) { - log.Printf("[DEBUG] Running workflow execution transaction for %s", workflowExecutionId) + log.Printf("[DEBUG][%s] Running workflow execution update", workflowExecutionId) // Should start a tx for the execution here workflowExecution, err := shuffle.GetWorkflowExecution(ctx, workflowExecutionId) @@ -1063,10 +1063,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request } } - err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) - if err != nil { - log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err) - } err = imageCheckBuilder(execInfo.ImageNames) if err != nil { @@ -1573,6 +1569,11 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id } + err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) + if err != nil { + log.Printf("[ERROR] Failed setting workflow execution during init (2): %s", err) + } + var allEnvs []shuffle.Environment if len(workflowExecution.ExecutionOrg) > 0 { //log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg) diff --git a/frontend/package.json b/frontend/package.json index 0cb2e764..fbababec 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 81dacd85..05e57184 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -269,35 +269,8 @@ const EditWorkflow = (props) => { />
- { - 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 ? - + Usecases : null} + { + 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()); + }} + />
{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) }} > diff --git a/frontend/src/components/NewHeader.jsx b/frontend/src/components/NewHeader.jsx index 7b72f0bd..004b3132 100644 --- a/frontend/src/components/NewHeader.jsx +++ b/frontend/src/components/NewHeader.jsx @@ -1352,14 +1352,17 @@ const Header = (props) => { ); // - return !isMobile ? - isLoggedIn ? + // + /* + !isLoggedIn ?
{loginTextBrowser}
: + */ + return !isMobile ? { 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) => { )} - 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 (
- {data.name} + + {fieldname} {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("") }} diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index e10c6e5f..53f40b79 100755 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -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) }} > { title={"Autocompletes fields. Uses NAME of the action and previous values' results."} placement="top" > + {autoCompleting ? + + : + }
diff --git a/frontend/src/components/RuntimeDebugger.jsx b/frontend/src/components/RuntimeDebugger.jsx index 8bb82514..47462d2b 100644 --- a/frontend/src/components/RuntimeDebugger.jsx +++ b/frontend/src/components/RuntimeDebugger.jsx @@ -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) => ( - - - + + {params.row.result !== null && params.row.result !== undefined && params.row.result !== "" ? + params.row.result + : + null + } + + } > + + + + ), }, ] diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx index 5fb9b152..91db5f49 100644 --- a/frontend/src/components/ShuffleCodeEditor.jsx +++ b/frontend/src/components/ShuffleCodeEditor.jsx @@ -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) => { }} >
+ {/* { > Code Editor - { - - }} - > - - - - - - - { - autoFormat(localcodedata) - }} - > - - {isAiLoading ? - - : - - } - - -
- - } - - + */} { isFileEditor ? null : -
+
} - { + + }} + > + + + + + + + { + autoFormat(localcodedata) + }} + > + + {isAiLoading ? + + : + + } + + +
+ + } + + + +
{ - // 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, }} /> - +
{/*editorPopupOpen ? { -
+
{isFileEditor ? null :
{isMobile ? null : @@ -1564,7 +1576,7 @@ const CodeEditor = (props) => { Expected Output - { + { executeSingleAction(expOutput) }}> @@ -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", }} diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 1aab3b4c..ab9560fd 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -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" }} /> + {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" }} /> - + + {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 ( { updateAppAuthentication(data); }} + disabled={data.org_id !== selectedOrganization.id ? true : false} > - + {data.defined ? ( { editAuthenticationConfig(data.id); }} > @@ -3803,23 +3819,54 @@ If you're interested, please let me know a time that works for you, or set up a placement="top" > {}} + disabled={data.org_id !== selectedOrganization.id ? true : false} > )} { deleteAuthentication(data); }} > - + + + {selectedOrganization.id !== undefined && data.org_id !== selectedOrganization.id ? + + + + : + + { + changeDistribution(data, !isDistributed) + }} + /> + + } + ); })} @@ -4018,49 +4065,56 @@ If you're interested, please let me know a time that works for you, or set up a - { - if (environment.Type === "cloud") { - toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.") - return - } + + { + 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"); - } - }} - > - - - - } + 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"); + } + }} + > + + + + } /> { 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) => {
) })} + {visibleApps.length <= 4 ? ( +
{ + }} + > + + Click one of the relevant public apps below to Activate it for your organization. + + { + console.log("CLICKED") + }}> + + + + + +
+ ) : null}
) : apps.length > 0 ? (
{ }} > - 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. { 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 ?
- - {workflow.errors.length} Potential Workflow Issue{workflow.errors.length > 1 ? "s" : ""} + + {workflow.errors.length} Workflow Issue{workflow.errors.length > 1 ? "s" : ""} { 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={{ diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index 350853ee..c948dd3a 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -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) { diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 2bcd3b38..981bc7a0 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1982,16 +1982,16 @@ const Workflows = (props) => {
- {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} - : null} - - Edit {data.name} - - - } placement="bottom"> - + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Edit {data.name} + +
+ } placement="left"> +