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
+9 -6
View File
@@ -1965,6 +1965,7 @@ func executeCloudAction(action shuffle.CloudSyncJob, apikey string) error {
return err return err
} }
defer newresp.Body.Close()
respBody, err := ioutil.ReadAll(newresp.Body) respBody, err := ioutil.ReadAll(newresp.Body)
if err != nil { if err != nil {
return err 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)) 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) newresp, err := client.Do(req)
if err != nil { if err != nil {
//log.Printf("Failed request in org sync: %s", err) //log.Printf("Failed request in org sync: %s", err)
return err return err
} }
defer newresp.Body.Close()
respBody, err := ioutil.ReadAll(newresp.Body) respBody, err := ioutil.ReadAll(newresp.Body)
if err != nil { if err != nil {
log.Printf("[ERROR] Failed body read in job sync: %s", err) 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") log.Printf("[DEBUG] Getting organizations for Elasticsearch/Opensearch")
activeOrgs, err := shuffle.GetAllOrgs(ctx) activeOrgs, err := shuffle.GetAllOrgs(ctx)
log.Printf("[DEBUG] Got %d organizations to look into", len(activeOrgs))
setUsers := false setUsers := false
_ = setUsers _ = setUsers
if err != nil { if err != nil {
@@ -3697,7 +3698,7 @@ func runInitEs(ctx context.Context) {
for _, schedule := range schedules { for _, schedule := range schedules {
if strings.ToLower(schedule.Environment) == "cloud" { if strings.ToLower(schedule.Environment) == "cloud" {
log.Printf("Skipping cloud schedule") log.Printf("[DEBUG] Skipping cloud schedule")
continue continue
} }
@@ -3705,7 +3706,9 @@ func runInitEs(ctx context.Context) {
//log.Printf("Schedule time: every %d seconds", schedule.Seconds) //log.Printf("Schedule time: every %d seconds", schedule.Seconds)
jobret, err := newscheduler.Every(schedule.Seconds).Seconds().NotImmediately().Run(job(schedule)) jobret, err := newscheduler.Every(schedule.Seconds).Seconds().NotImmediately().Run(job(schedule))
if err != nil { 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 scheduledJobs[schedule.Id] = jobret
@@ -4725,7 +4728,7 @@ func initHandlers() {
log.Printf("[DEBUG] Initialized Shuffle database connection. Setting up environment.") log.Printf("[DEBUG] Initialized Shuffle database connection. Setting up environment.")
if elasticConfig == "elasticsearch" { if elasticConfig == "elasticsearch" {
time.Sleep(5 * time.Second) time.Sleep(10 * time.Second)
go runInitEs(ctx) go runInitEs(ctx)
} else { } else {
//go shuffle.runInit(ctx) //go shuffle.runInit(ctx)
+6 -5
View File
@@ -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 // 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) { 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 // Should start a tx for the execution here
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, workflowExecutionId) 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) err = imageCheckBuilder(execInfo.ImageNames)
if err != nil { if err != nil {
@@ -1573,6 +1569,11 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request
workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id 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 var allEnvs []shuffle.Environment
if len(workflowExecution.ExecutionOrg) > 0 { if len(workflowExecution.ExecutionOrg) > 0 {
//log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg) //log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg)
+1
View File
@@ -14,6 +14,7 @@
"@mui/styles": "^5.14.0", "@mui/styles": "^5.14.0",
"@mui/x-data-grid": "^5.17.11", "@mui/x-data-grid": "^5.17.11",
"@mui/x-date-pickers": "^6.11.1", "@mui/x-date-pickers": "^6.11.1",
"@uiw/codemirror-theme-vscode": "^4.21.20",
"@uiw/codemirror-themes": "^4.21.9", "@uiw/codemirror-themes": "^4.21.9",
"@uiw/react-codemirror": "^4.21.9", "@uiw/react-codemirror": "^4.21.9",
"@use-it/interval": "^1.0.0", "@use-it/interval": "^1.0.0",
+30 -29
View File
@@ -269,35 +269,8 @@ const EditWorkflow = (props) => {
/> />
</div> </div>
<div style={{display: "flex", marginTop: 10, }}> <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 ? {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> <InputLabel htmlFor="grouped-select-usecase">Usecases</InputLabel>
<Select <Select
defaultValue="" defaultValue=""
@@ -350,6 +323,33 @@ const EditWorkflow = (props) => {
</Select> </Select>
</FormControl> </FormControl>
: null} : 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> </div>
{showMoreClicked === true ? {showMoreClicked === true ?
@@ -365,7 +365,8 @@ const EditWorkflow = (props) => {
onChange={(e) => { onChange={(e) => {
console.log("Data: ", e.target.value) 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) setInnerWorkflow(innerWorkflow)
}} }}
> >
+5 -2
View File
@@ -1352,14 +1352,17 @@ const Header = (props) => {
); );
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/> // <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, }}> <div style={{minHeight: 68, maxHeight: 68, backgroundColor: theme.palette.backgroundColor, }}>
<BrowserView style={{position: "sticky", top: 0, }}> <BrowserView style={{position: "sticky", top: 0, }}>
{loginTextBrowser} {loginTextBrowser}
</BrowserView> </BrowserView>
</div> </div>
: :
*/
return !isMobile ?
<AppBar <AppBar
position="sticky" position="sticky"
color="transparent" color="transparent"
+36 -24
View File
@@ -112,8 +112,6 @@ const AuthenticationOauth2 = (props) => {
authenticationType.client_secret.length > 0 authenticationType.client_secret.length > 0
); );
console.log("AUTH: ", authenticationType)
const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : ""); const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : "");
const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : ""); const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "");
@@ -125,7 +123,7 @@ const AuthenticationOauth2 = (props) => {
const [offlineAccess, setOfflineAccess] = React.useState(true); const [offlineAccess, setOfflineAccess] = React.useState(true);
const allscopes = authenticationType.scope !== undefined && authenticationType.scope !== null ? authenticationType.scope : []; 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( const [manuallyConfigure, setManuallyConfigure] = React.useState(
defaultConfigSet ? false : true 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)) { 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") 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 // Find app.configuration=true fields in the app.paramters
var parsedFields = [{ var parsedFields = [{
"key": "client_id", "key": "client_id",
@@ -318,7 +333,7 @@ const AuthenticationOauth2 = (props) => {
}, },
{ {
"key": "token_uri", "key": "token_uri",
"value": authenticationType.token_uri, "value": tokenUri,
}] }]
if (authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0) { if (authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0) {
@@ -360,14 +375,14 @@ const AuthenticationOauth2 = (props) => {
"reference_workflow": workflowId, "reference_workflow": workflowId,
} }
setNewAppAuth(appAuthData) setNewAppAuth(appAuthData, true)
// Wait 1 second, then get app auth with update // Wait 1 second, then get app auth with update
// //if (getAppAuthentication !== undefined) {
if (getAppAuthentication !== undefined) { // setTimeout(() => {
setTimeout(() => { // getAppAuthentication(true, true, true);
getAppAuthentication(true, true, true); // }, 1000)
}, 1000) //}
}
return return
} }
@@ -397,8 +412,6 @@ const AuthenticationOauth2 = (props) => {
} }
const authentication_url = authenticationType.token_uri; 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 redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication`;
const workflowId = workflow !== undefined ? workflow.id : ""; 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}`; 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 = () => { const handleSubmitCheck = () => {
console.log("NEW AUTH: ", authenticationOption);
if (authenticationOption.label.length === 0) { if (authenticationOption.label.length === 0) {
authenticationOption.label = `Auth for ${selectedApp.name}`; authenticationOption.label = `Auth for ${selectedApp.name}`;
//toast("Label can't be empty") //toast("Label can't be empty")
@@ -673,7 +685,7 @@ const AuthenticationOauth2 = (props) => {
)} )}
</Button> </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 return autoAuthButton
} }
@@ -777,10 +789,14 @@ const AuthenticationOauth2 = (props) => {
setOauthUrl(data.value); 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 ( return (
<div key={index} style={{ marginTop: 10 }}> <div key={index} style={{ marginTop: 10 }}>
<LockOpenIcon style={{ marginRight: 10 }} /> <LockOpenIcon style={{ marginRight: 10 }} />
<b>{data.name}</b>
<b>{fieldname}</b>
{data.schema !== undefined && {data.schema !== undefined &&
data.schema !== null && data.schema !== null &&
@@ -793,6 +809,7 @@ const AuthenticationOauth2 = (props) => {
}} }}
defaultValue={"false"} defaultValue={"false"}
fullWidth fullWidth
label={fieldname}
onChange={(e) => { onChange={(e) => {
console.log("Value: ", e.target.value); console.log("Value: ", e.target.value);
authenticationOption.fields[data.name] = e.target.value; authenticationOption.fields[data.name] = e.target.value;
@@ -842,16 +859,11 @@ const AuthenticationOauth2 = (props) => {
: "text" : "text"
} }
color="primary" color="primary"
defaultValue={ defaultValue={defaultValue}
data.value !== undefined && data.value !== null
? data.value
: ""
}
placeholder={data.example} placeholder={data.example}
onChange={(event) => { onChange={(event) => {
authenticationOption.fields[data.name] = authenticationOption.fields[data.name] = event.target.value;
event.target.value; console.log("Setting oauth url: ", event.target.value);
console.log("Setting oauth url");
setOauthUrl(event.target.value); setOauthUrl(event.target.value);
//const [oauthUrl, setOauthUrl] = React.useState("") //const [oauthUrl, setOauthUrl] = React.useState("")
}} }}
+8
View File
@@ -181,6 +181,8 @@ const ParsedAction = (props) => {
const [fieldCount, setFieldCount] = React.useState(0); const [fieldCount, setFieldCount] = React.useState(0);
const [hiddenDescription, setHiddenDescription] = React.useState(true); const [hiddenDescription, setHiddenDescription] = React.useState(true);
const [autoCompleting, setAutocompleting] = React.useState(false);
useEffect(() => { useEffect(() => {
if (setLastSaved !== undefined) { if (setLastSaved !== undefined) {
@@ -2909,9 +2911,11 @@ const ParsedAction = (props) => {
marginLeft: 15, marginLeft: 15,
paddingRight: 0, paddingRight: 0,
}} }}
disabled={autoCompleting}
onClick={() => { onClick={() => {
// aiSubmit(aiMsg, undefined, undefined, newSelectedAction) // aiSubmit(aiMsg, undefined, undefined, newSelectedAction)
aiSubmit("Fill based on previous values", undefined, undefined, selectedAction) aiSubmit("Fill based on previous values", undefined, undefined, selectedAction)
setAutocompleting(true)
}} }}
> >
<Tooltip <Tooltip
@@ -2919,7 +2923,11 @@ const ParsedAction = (props) => {
title={"Autocompletes fields. Uses NAME of the action and previous values' results."} title={"Autocompletes fields. Uses NAME of the action and previous values' results."}
placement="top" placement="top"
> >
{autoCompleting ?
<CircularProgress style={{height: 20, width: 20, }} />
:
<AutoFixHighIcon style={{ color: "rgba(255,255,255,0.7)", height: 24, }} /> <AutoFixHighIcon style={{ color: "rgba(255,255,255,0.7)", height: 24, }} />
}
</Tooltip> </Tooltip>
</IconButton> </IconButton>
</div> </div>
+15 -5
View File
@@ -315,16 +315,26 @@ const RuntimeDebugger = (props) => {
) )
}, },
}, },
{ field: 'startTimestamp', headerName: 'Start time', width: 160, }, { field: 'startTimestamp', headerName: 'Start time (UTC)', width: 160, },
{ field: 'endTimestamp', headerName: 'End time', width: 160, }, { field: 'endTimestamp', headerName: 'End time (UTC)', width: 160, },
{ {
field: 'id', field: 'id',
headerName: 'Explore', headerName: 'Explore',
width: 65, width: 65,
renderCell: (params) => ( renderCell: (params) => (
<Link href={`/workflows/${params.row.workflow.id}?execution_id=${params.row.id}`} target="_blank" rel="noopener noreferrer"> <Tooltip arrow placement="right" title={
<OpenInNewIcon fontSize="small" /> <Typography variant="body2" style={{whiteSpace: "pre-line", }}>
</Link> {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 { NestedMenuItem } from "mui-nested-menu"
import { GetParsedPaths, FindJsonPath } from "../views/Apps.jsx"; import { GetParsedPaths, FindJsonPath } from "../views/Apps.jsx";
import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx"; import { SetJsonDotnotation } from "../views/AngularWorkflow.jsx";
import { vscodeDark, vscodeDarkInit } from '@uiw/codemirror-theme-vscode';
import { import {
FullscreenExit as FullscreenExitIcon, FullscreenExit as FullscreenExitIcon,
@@ -82,6 +83,7 @@ const pythonFilters = [
{"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``}, {"name": "Handle JSON", "value": `{% python %}\nimport json\njsondata = json.loads(r"""$nodename""")\n{% endpython %}`, "example": ``},
] ]
/*
const shuffleTheme = createTheme({ const shuffleTheme = createTheme({
theme: 'dark', theme: 'dark',
settings: { settings: {
@@ -110,6 +112,7 @@ const shuffleTheme = createTheme({
{ tag: t.attributeName, color: '#5c6166' }, { tag: t.attributeName, color: '#5c6166' },
], ],
}); });
*/
const CodeEditor = (props) => { const CodeEditor = (props) => {
const { const {
@@ -547,7 +550,7 @@ const CodeEditor = (props) => {
var code_lines = localcodedata.split('\n') var code_lines = localcodedata.split('\n')
for (var i = 0; i < code_lines.length; i++){ for (var i = 0; i < code_lines.length; i++){
var current_code_line = code_lines[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) 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]) const fixedVariable = fixVariable(variable_occurence[occ])
var correctVariable = availableVariables.includes(fixedVariable) var correctVariable = availableVariables.includes(fixedVariable)
if(!correctVariable) { 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"}) 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"}) 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) // console.log(correctVariables)
@@ -674,36 +678,33 @@ const CodeEditor = (props) => {
try { try {
for (var i = 0; i < found.length; i++) { for (var i = 0; i < found.length; i++) {
try { try {
// For found specifically, should replace .#\d with .# with regex
//found[i] = found[i].toLowerCase()
const fixedVariable = fixVariable(found[i]) const fixedVariable = fixVariable(found[i])
//var correctVariable = availableVariables.includes(fixedVariable)
//
var valuefound = false var valuefound = false
for (var j = 0; j < actionlist.length; j++) { for (var j = 0; j < actionlist.length; j++) {
if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){ if(fixedVariable.slice(1,).toLowerCase() !== actionlist[j].autocomplete.toLowerCase()){
valuefound = true continue
}
try { valuefound = true
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("[")) { try {
input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1); if (typeof actionlist[j].example === "object") {
} else { input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
input = input.replace(found[i], actionlist[j].example, -1)
} } else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) {
} catch (e) { input = input.replace(found[i], JSON.stringify(actionlist[j].example), -1);
} else {
input = input.replace(found[i], actionlist[j].example, -1) input = input.replace(found[i], actionlist[j].example, -1)
} }
} else { } catch (e) {
// Couldn't find the correct example value input = input.replace(found[i], actionlist[j].example, -1)
} }
} }
//if (!valuefound) {
// console.log("Couldn't find value "+fixedVariable)
//}
if (!valuefound && availableVariables.includes(fixedVariable)) { if (!valuefound && availableVariables.includes(fixedVariable)) {
var shouldbreak = false var shouldbreak = false
for (var k=0; k < actionlist.length; k++){ for (var k=0; k < actionlist.length; k++){
@@ -714,46 +715,48 @@ const CodeEditor = (props) => {
for (var key in parsedPaths) { for (var key in parsedPaths) {
const fullpath = "$"+actionlist[k].autocomplete.toLowerCase()+parsedPaths[key].autocomplete const fullpath = "$"+actionlist[k].autocomplete.toLowerCase()+parsedPaths[key].autocomplete
if (fullpath === fixedVariable) { if (fullpath !== fixedVariable) {
//if (actionlist[k].example === undefined) { continue
// actionlist[k].example = "TMP" }
//}
var new_input = "" //if (actionlist[k].example === undefined) {
try { // actionlist[k].example = "TMP"
new_input = FindJsonPath(fullpath, actionlist[k].example) //}
} catch (e) {
console.log("ERR IN INPUT: ", e)
}
//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") { console.log("Got output for: ", fullpath, new_input, actionlist[k].example, typeof new_input)
new_input = JSON.stringify(new_input)
if (typeof new_input === "object") {
new_input = JSON.stringify(new_input)
} else {
if (typeof new_input === "string") {
new_input = new_input
} else { } else {
if (typeof new_input === "string") { console.log("NO TYPE? ", typeof new_input)
new_input = new_input try {
} else { new_input = new_input.toString()
console.log("NO TYPE? ", typeof new_input) } catch (e) {
try { new_input = ""
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) { if (shouldbreak) {
@@ -766,7 +769,7 @@ const CodeEditor = (props) => {
} }
} }
} catch (e) { } 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" aria-labelledby="draggable-code-modal"
disableBackdropClick={true} disableBackdropClick={true}
disableEnforceFocus={true} disableEnforceFocus={true}
//style={{ pointerEvents: "none" }} //style={{ pointerEvents: "none" }}
hideBackdrop={true} hideBackdrop={true}
open={expansionModalOpen} open={expansionModalOpen}
onClose={() => { onClose={() => {
@@ -964,6 +967,7 @@ const CodeEditor = (props) => {
}} }}
> >
<div style={{display: "flex"}}> <div style={{display: "flex"}}>
{/*
<DialogTitle <DialogTitle
id="draggable-dialog-title" id="draggable-dialog-title"
style={{ style={{
@@ -974,59 +978,9 @@ const CodeEditor = (props) => {
> >
Code Editor Code Editor
</DialogTitle> </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 : { isFileEditor ? null :
<div style={{display: "flex"}}> <div style={{display: "flex", maxHeight: 40, }}>
<Button <Button
id="basic-button" id="basic-button"
aria-haspopup="true" aria-haspopup="true"
@@ -1035,7 +989,7 @@ const CodeEditor = (props) => {
variant="outlined" variant="outlined"
color="secondary" color="secondary"
style={{ style={{
textTransform: "none", textTransform: "none",
width: 100, width: 100,
}} }}
onClick={(event) => { onClick={(event) => {
@@ -1143,9 +1097,9 @@ const CodeEditor = (props) => {
variant="outlined" variant="outlined"
color="secondary" color="secondary"
style={{ style={{
textTransform: "none", textTransform: "none",
width: 130, width: 130,
marginLeft: 170, marginLeft: 20,
}} }}
onClick={(event) => { onClick={(event) => {
setMenuPosition({ setMenuPosition({
@@ -1393,28 +1347,86 @@ const CodeEditor = (props) => {
</Menu> </Menu>
</div> </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, borderRadius: theme.palette.borderRadius,
position: "relative", position: "relative",
paddingTop: 0,
minHeight: 548,
overflow: "hidden",
}}> }}>
<CodeMirror <CodeMirror
theme={vscodeDark}
value={localcodedata} value={localcodedata}
height={isFileEditor ? 450 : 525} height={isFileEditor ? 450 : 500}
width={isFileEditor ? 650 : 600} width={isFileEditor ? 650 : 600}
style={{ style={{
maxWidth: isFileEditor ? 450 : 500, maxWidth: isFileEditor ? 450 : 600,
maxHeight: 548,
minHeight: 548,
wordBreak: "break-word", wordBreak: "break-word",
marginTop: 0, marginTop: 0,
paddingTop: 0, paddingBottom: 10,
backgroundColor: "rgba(40,40,40,1)", overflow: "hidden",
minHeight: 470,
}} }}
onCursorActivity = {(value) => { onCursorActivity = {(value) => {
// console.log(value.getCursor()) console.log("CURSOR: ", value.getCursor())
setCurrentCharacter(value.getCursor().ch) setCurrentCharacter(value.getCursor().ch)
setCurrentLine(value.getCursor().line) setCurrentLine(value.getCursor().line)
// console.log(value.getCursor().ch, value.getCursor().line) // console.log(value.getCursor().ch, value.getCursor().line)
findIndex(value.getCursor().line, value.getCursor().ch) findIndex(value.getCursor().line, value.getCursor().ch)
highlight_variables(value) highlight_variables(value)
}} }}
onChange={(value, viewUpdate) => { onChange={(value, viewUpdate) => {
@@ -1422,20 +1434,19 @@ const CodeEditor = (props) => {
setlocalcodedata(value) setlocalcodedata(value)
expectedOutput(value) expectedOutput(value)
highlight_variables(value)
//if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){ //if(value.display.input.prevInput.startsWith('$') || value.display.input.prevInput.endsWith('$')){
// setEditorPopupOpen(true) // setEditorPopupOpen(true)
//} //}
}} }}
extensions={[]}//indentWithTab]}
theme={shuffleTheme}
options={{ options={{
styleSelectedText: true,
keyMap: 'sublime',
mode: validation === true ? "json" : "python", mode: validation === true ? "json" : "python",
lineWrapping: linewrap, lineWrapping: linewrap,
theme: vscodeDark,
}} }}
/> />
</span> </div>
{/*editorPopupOpen ? {/*editorPopupOpen ?
<Paper <Paper
@@ -1550,13 +1561,14 @@ const CodeEditor = (props) => {
</div> </div>
</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 : {isFileEditor ? null :
<div> <div>
{isMobile ? null : {isMobile ? null :
<DialogTitle <DialogTitle
style={{ style={{
paddingLeft: 10, paddingLeft: 10,
paddingTop: 0,
display: "flex", display: "flex",
}} }}
> >
@@ -1564,7 +1576,7 @@ const CodeEditor = (props) => {
Expected Output Expected Output
</span> </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) 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"> <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, borderRadius: theme.palette.borderRadius,
maxHeight: 500, maxHeight: 500,
minHeight: 500, minHeight: 500,
minWidth: 500, minWidth: 580,
maxWidth: 500, maxWidth: 580,
overflow: "auto", overflow: "auto",
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
}} }}
+108 -54
View File
@@ -16,6 +16,7 @@ import {
OutlinedInput, OutlinedInput,
Checkbox, Checkbox,
Card, Card,
Chip,
Tooltip, Tooltip,
FormControlLabel, FormControlLabel,
Typography, 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}` 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) => { const deleteAuthentication = (data) => {
toast("Deleting auth " + data.label); 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 = { const data = {
id: id, id: id,
action: "assign_everywhere", action: parentAction !== undefined && parentAction !== null ? parentAction : "assign_everywhere",
}; };
const url = globalUrl + "/api/v1/apps/authentication/" + id + "/config"; 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) => .then((response) =>
response.json().then((responseJson) => { response.json().then((responseJson) => {
if (responseJson["success"] === false) { if (responseJson["success"] === false) {
toast("Failed overwriting appauth in workflows"); toast("Failed overwriting appauth");
} else { } else {
toast("Successfully updated auth everywhere!"); toast("Successfully updated auth!");
setSelectedUserModalOpen(false); setSelectedUserModalOpen(false);
setTimeout(() => { setTimeout(() => {
getAppAuthentication(); 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 userId = user.id;
const data = { user_id: userId }; const data = { user_id: userId };
console.log(user, userdata) toast("Generating new API key")
var fetchdata = { var fetchdata = {
method: "POST", 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" }} style={{ minWidth: 300, maxWidth: 300, overflow: "hidden" }}
/> />
<ListItemText primary="Actions" /> <ListItemText primary="Actions" />
<ListItemText primary="Delegation" />
</ListItem> </ListItem>
{schedules === undefined || schedules === null {schedules === undefined || schedules === null
? 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" }} style={{ minWidth: 125, maxWidth: 125, overflow: "hidden" }}
/> />
<ListItemText <ListItemText
primary="Created" primary="Edited"
style={{ minWidth: 230, maxWidth: 230, overflow: "hidden" }} style={{ minWidth: 230, maxWidth: 230, overflow: "hidden" }}
/> />
<ListItemText primary="Actions" /> <ListItemText primary="Actions"
style={{ minWidth: 150, maxWidth: 150, }}
/>
<ListItemText primary="Distribution" />
</ListItem> </ListItem>
{authentication === undefined || authentication === null {authentication === undefined || authentication === null
? 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 ( return (
<ListItem key={index} style={{ backgroundColor: bgColor }}> <ListItem key={index} style={{ backgroundColor: bgColor }}>
<ListItemText <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, minWidth: 230,
overflow: "hidden", overflow: "hidden",
}} }}
primary={new Date(data.created * 1000).toISOString()} primary={new Date(data.edited * 1000).toISOString()}
/> />
<ListItemText> <ListItemText>
<IconButton <IconButton
onClick={() => { onClick={() => {
updateAppAuthentication(data); updateAppAuthentication(data);
}} }}
disabled={data.org_id !== selectedOrganization.id ? true : false}
> >
<EditIcon color="primary" /> <EditIcon color="secondary" />
</IconButton> </IconButton>
{data.defined ? ( {data.defined ? (
<Tooltip <Tooltip
color="primary" color="primary"
title="Set in EVERY workflow" title="Set in EVERY workflow in the organization"
placement="top" placement="top"
> >
<IconButton <IconButton
style={{ marginRight: 10 }} style={{ marginRight: 10 }}
disabled={data.defined === false} disabled={data.defined === false || data.org_id !== selectedOrganization.id ? true : false}
onClick={() => { onClick={() => {
editAuthenticationConfig(data.id); editAuthenticationConfig(data.id);
}} }}
> >
<SelectAllIcon <SelectAllIcon
color={data.defined ? "primary" : "secondary"} color={"secondary"}
/> />
</IconButton> </IconButton>
</Tooltip> </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" placement="top"
> >
<IconButton <IconButton
style={{ marginRight: 10 }} style={{}}
onClick={() => {}} onClick={() => {}}
disabled={data.org_id !== selectedOrganization.id ? true : false}
> >
<SelectAllIcon <SelectAllIcon
color={data.defined ? "primary" : "secondary"} color="secondary"
/> />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
<IconButton <IconButton
style={{marginLeft: 0, }}
disabled={data.org_id !== selectedOrganization.id ? true : false}
onClick={() => { onClick={() => {
deleteAuthentication(data); deleteAuthentication(data);
}} }}
> >
<DeleteIcon color="primary" /> <DeleteIcon color="secondary" />
</IconButton> </IconButton>
</ListItemText> </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> </ListItem>
); );
})} })}
@@ -4018,49 +4065,56 @@ If you're interested, please let me know a time that works for you, or set up a
<ListItemText <ListItemText
style={{ minWidth: 100, maxWidth: 100 }} style={{ minWidth: 100, maxWidth: 100 }}
primary={ primary={
<Tooltip <Tooltip
title={"Copy Orborus command"} title={"Copy Orborus command"}
style={{}} style={{}}
aria-label={"Copy orborus command"} aria-label={"Copy orborus command"}
> >
<IconButton <IconButton
style={{}} style={{}}
disabled={environment.Type === "cloud"} disabled={environment.Type === "cloud"}
onClick={() => { onClick={() => {
if (environment.Type === "cloud") { if (environment.Type === "cloud") {
toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.") toast("No Orborus necessary for environment cloud. Create and use a different environment to run executions on-premises.")
return return
} }
const elementName = "copy_element_shuffle"; if (props.userdata.active_org === undefined || props.userdata.active_org === null) {
const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth toast("No active organization yet. Are you logged in?")
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` return
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;
}
navigator.clipboard.writeText(commandData); const elementName = "copy_element_shuffle";
copyText.select(); const auth = environment.auth === "" ? 'cb5st3d3Z!3X3zaJ*Pc' : environment.auth
copyText.setSelectionRange( const newUrl = globalUrl === "https://shuffler.io" ? "https://shuffle-backend-stbuwivzoq-nw.a.run.app" : globalUrl
0,
99999
); /* For mobile devices */
/* Copy the text inside the text field */ 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`
document.execCommand("copy"); 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,
<FileCopyIcon disabled={environment.Type === "cloud"} style={{ color: environment.Type === "cloud" ? "rgba(255,255,255,0.2)" : "rgba(255,255,255,0.8)" }} /> 99999
</IconButton> ); /* For mobile devices */
</Tooltip>
} /* 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 <ListItemText
+115 -34
View File
@@ -501,6 +501,7 @@ const AngularWorkflow = (defaultprops) => {
const [selectedAction, setSelectedAction] = React.useState({}); const [selectedAction, setSelectedAction] = React.useState({});
const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({}); const [selectedActionEnvironment, setSelectedActionEnvironment] = React.useState({});
const [streamDisabled, setStreamDisabled] = React.useState(false);
const [executionRequest, setExecutionRequest] = React.useState({}); const [executionRequest, setExecutionRequest] = React.useState({});
const [executionRunning, setExecutionRunning] = React.useState(false); const [executionRunning, setExecutionRunning] = React.useState(false);
@@ -551,10 +552,18 @@ const AngularWorkflow = (defaultprops) => {
props.userdata.active_org !== undefined props.userdata.active_org !== undefined
? props.userdata.active_org.cloud_sync === true ? props.userdata.active_org.cloud_sync === true
: false; : false;
const isCloud = const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
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(() => { useEffect(() => {
console.log("In useeffect for workflow: ", workflow) 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", { fetch(globalUrl + "/api/v1/apps/authentication", {
method: "PUT", method: "PUT",
headers: { headers: {
@@ -937,9 +946,14 @@ const AngularWorkflow = (defaultprops) => {
}) })
.then((responseJson) => { .then((responseJson) => {
if (!responseJson.success) { if (!responseJson.success) {
toast("Failed to set app auth: " + responseJson.reason); toast("Error: " + responseJson.reason);
} else { } else {
getAppAuthentication(true, false); if (refresh === true) {
getAppAuthentication(true, true, true);
} else {
getAppAuthentication(true, false);
}
setAuthenticationModalOpen(false); setAuthenticationModalOpen(false);
// Needs a refresh with the new authentication.. // Needs a refresh with the new authentication..
@@ -1003,16 +1017,15 @@ const AngularWorkflow = (defaultprops) => {
} }
setExecutionModalView(1); setExecutionModalView(1);
start();
setExecutionRequest({ setExecutionRequest({
execution_id: execution.execution_id, execution_id: execution.execution_id,
authorization: execution.authorization, authorization: execution.authorization,
}); });
const newitem = removeParam("execution_id", cursearch); start();
navigate(curpath + newitem)
//props.history.push(curpath + newitem); //const newitem = removeParam("execution_id", cursearch);
//navigate(curpath + newitem)
} else { } else {
console.log("Couldn't find execution for execution ID. Retrying as user to get ", tmpView) 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); setExecutionRequest(cur_execution);
start(); start();
const newitem = removeParam("execution_id", cursearch); //const newitem = removeParam("execution_id", cursearch);
navigate(curpath + newitem) //navigate(curpath + newitem)
setTimeout(() => { setTimeout(() => {
stop() stop()
@@ -1310,12 +1323,28 @@ const AngularWorkflow = (defaultprops) => {
const sendStreamRequest = (body) => { const sendStreamRequest = (body) => {
//console.log("Stream not activated yet.") //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 // Session may be important here huh
body.user_id = userdata.id 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", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -1337,7 +1366,7 @@ const AngularWorkflow = (defaultprops) => {
}) })
.catch((error) => { .catch((error) => {
console.log("Stream send error: ", error.toString()) console.log("Stream send error: ", error.toString())
//toast(error.toString()); setStreamDisabled(true)
}) })
} }
@@ -1872,6 +1901,7 @@ const AngularWorkflow = (defaultprops) => {
setSelectedAction(selectedAction); setSelectedAction(selectedAction);
setWorkflow(workflow); setWorkflow(workflow);
saveWorkflow(workflow); saveWorkflow(workflow);
toast("Added and updated authentication!"); toast("Added and updated authentication!");
shouldClose = true shouldClose = true
} else { } else {
@@ -2084,7 +2114,9 @@ const AngularWorkflow = (defaultprops) => {
} }
const onChunkedResponseError = (err) => { const onChunkedResponseError = (err) => {
console.error(err) if (streamDisabled) {
return
}
} }
@@ -2570,14 +2602,30 @@ const AngularWorkflow = (defaultprops) => {
} }
const startWorkflowStream = async (workflowId) => { 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) { while (true) {
if (streamDisabled) {
break
}
// Wait 1 second before next request just in case of timeouts // Wait 1 second before next request just in case of timeouts
await new Promise(r => setTimeout(r, 1000)); await new Promise(r => setTimeout(r, 1000));
await fetchWithTimeout(`${globalUrl}/api/v1/workflows/${workflowId}/stream`, { await fetchWithTimeout(url, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -6261,7 +6309,6 @@ const AngularWorkflow = (defaultprops) => {
} }
insertedNodes = insertedNodes.concat(newedges); insertedNodes = insertedNodes.concat(newedges);
setWorkflow(inputworkflow); setWorkflow(inputworkflow);
// Reset view for cytoscape // Reset view for cytoscape
@@ -6271,6 +6318,8 @@ const AngularWorkflow = (defaultprops) => {
} else { } else {
setElements(insertedNodes); setElements(insertedNodes);
} }
console.log("Setupgraph done 2!")
}; };
const removeNode = (nodeId) => { const removeNode = (nodeId) => {
@@ -6576,6 +6625,7 @@ const AngularWorkflow = (defaultprops) => {
} }
// preview: true, // preview: true,
console.log("In POST graph setup 2")
cy.fit(null, 200); cy.fit(null, 200);
cy.on("boxselect", "node", (e) => { cy.on("boxselect", "node", (e) => {
@@ -6624,6 +6674,7 @@ const AngularWorkflow = (defaultprops) => {
document.title = "Workflow - " + workflow.name; document.title = "Workflow - " + workflow.name;
console.log("In POST graph setup 3")
startWorkflowStream(props.match.params.key); startWorkflowStream(props.match.params.key);
registerKeys(); registerKeys();
@@ -7516,14 +7567,30 @@ const AngularWorkflow = (defaultprops) => {
description = app.actions[actionIndex].description description = app.actions[actionIndex].description
} }
const parsedEnvironments = var parsedEnvironments =
environments === null || environments === [] environments === null || environments === []
? "cloud" ? "cloud"
: environments[defaultEnvironmentIndex] === undefined : environments[defaultEnvironmentIndex] === undefined
? "cloud" ? "cloud"
: environments[defaultEnvironmentIndex].Name; : 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 = { const newAppData = {
name: app.actions[actionIndex].name, name: app.actions[actionIndex].name,
label: actionLabel, label: actionLabel,
@@ -8139,6 +8206,25 @@ const AngularWorkflow = (defaultprops) => {
</div> </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> </div>
) : apps.length > 0 ? ( ) : apps.length > 0 ? (
<div <div
@@ -8148,7 +8234,7 @@ const AngularWorkflow = (defaultprops) => {
}} }}
> >
<Typography variant="body1" color="textSecondary"> <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> </Typography>
<InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => { <InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
console.log("CLICKED") console.log("CLICKED")
@@ -8364,7 +8450,6 @@ const AngularWorkflow = (defaultprops) => {
} }
} }
console.log("NEW ACTION: ", newSelectedAction);
setSelectedAction(newSelectedAction); setSelectedAction(newSelectedAction);
setUpdate(Math.random()); setUpdate(Math.random());
@@ -13259,10 +13344,6 @@ const AngularWorkflow = (defaultprops) => {
"user": "Anonymous", "user": "Anonymous",
"user_id": "user_id", "user_id": "user_id",
"color": "blue", "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 ? const showErrors = !isMobile && !workflow.public && workflow.errors !== undefined && workflow.errors !== null && workflow.errors.length > 0 ?
<div <div
style={{ style={{
border: "1px solid rgba(255,255,255,0.3)", border: "1px solid rgba(255,255,255,0.1)",
position: "absolute", position: "absolute",
bottom: 130, bottom: 130,
left: leftBarSize+20, left: leftBarSize+20,
color: "white", color: "white",
padding: 5, padding: 10,
borderRadius: theme.palette.borderRadius, borderRadius: theme.palette.borderRadius,
}} }}
> >
<Typography variant="body22"> <Typography variant="body22">
<WarningIcon style={{color: "yellow", marginRight: 5, height: 15, width: 15, }} /> <WarningIcon style={{marginRight: 5, height: 15, width: 15, }} />
<b>{workflow.errors.length} Potential Workflow Issue{workflow.errors.length > 1 ? "s" : ""}</b> <b>{workflow.errors.length} Workflow Issue{workflow.errors.length > 1 ? "s" : ""}</b>
</Typography> </Typography>
<Typography <Typography
variant="body2" variant="body2"
+8 -2
View File
@@ -413,7 +413,9 @@ const AppCreator = (defaultprops) => {
const [oauth2Scopes, setOauth2Scopes] = useState([]); const [oauth2Scopes, setOauth2Scopes] = useState([]);
const [oauth2Type, setOauth2Type] = useState("delegated"); const [oauth2Type, setOauth2Type] = useState("delegated");
const [oauth2GrantType, setOauth2GrantType] = useState("client_credentials");
//client_credentials
const [oauth2GrantType, setOauth2GrantType] = useState("");
const defaultAuth = { const defaultAuth = {
name: "", name: "",
type: "header", type: "header",
@@ -1757,7 +1759,7 @@ const AppCreator = (defaultprops) => {
// Kind of fucked up, but it works for now? // 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) { 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]) //console.log("FLOW2: ", value[flowkey][basekey])
@@ -5988,6 +5990,10 @@ const AppCreator = (defaultprops) => {
fullWidth fullWidth
onChange={(e) => { onChange={(e) => {
setOauth2Type(e.target.value); setOauth2Type(e.target.value);
if (e.target.value === "application" && oauth2GrantType === "") {
setOauth2GrantType("client_credentials")
}
}} }}
value={oauth2Type} value={oauth2Type}
style={{ style={{
+2 -2
View File
@@ -368,14 +368,14 @@ const Docs = (defaultprops) => {
} }
const markdownStyle = { const markdownStyle = {
color: "rgba(255, 255, 255, 0.65)", color: "rgba(255, 255, 255, 0.90)",
overflow: "hidden", overflow: "hidden",
paddingBottom: 100, paddingBottom: 100,
margin: "auto", margin: "auto",
maxWidth: "100%", maxWidth: "100%",
minWidth: "100%", minWidth: "100%",
overflow: "hidden", overflow: "hidden",
fontSize: isMobile ? "1.3rem" : "1.0rem", fontSize: isMobile ? "1.3rem" : "1.1rem",
}; };
function OuterLink(props) { function OuterLink(props) {
+10 -10
View File
@@ -1982,16 +1982,16 @@ const Workflows = (props) => {
</div> </div>
</Tooltip> </Tooltip>
<Tooltip arrow title={ <Tooltip arrow title={
<span style={{}}> <div style={{width: "100%", minWidth: 250, maxWidth: 310, }}>
{data.image !== undefined && data.image !== null && data.image.length > 0 ? {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, }} /> <img src={data.image} alt={data.name} style={{backgroundColor: theme.palette.surfaceColor, maxWidth: 300, minWidth: 250, borderRadius: theme.palette.borderRadius, }} />
: null} : null}
<Typography> <Typography>
Edit {data.name} Edit {data.name}
</Typography> </Typography>
</span> </div>
} placement="bottom"> } placement="left">
<Typography <Typography
variant="body1" variant="body1"
style={{ style={{
marginBottom: 0, marginBottom: 0,