diff --git a/app/mainapp/graphql/private/generated.go b/app/mainapp/graphql/private/generated.go index 4616adf4f..8ddb50b2e 100644 --- a/app/mainapp/graphql/private/generated.go +++ b/app/mainapp/graphql/private/generated.go @@ -93,6 +93,7 @@ type ComplexityRoot struct { AddSecretToWorkerGroup func(childComplexity int, environmentID string, workerGroup string, secret string) int AddUpdatePipelineFlow func(childComplexity int, input *PipelineFlowInput, environmentID string, pipelineID string) int AddUserToEnvironment func(childComplexity int, userID string, environmentID string) int + CodeEditorRun func(childComplexity int, environmentID string, nodeID string, pipelineID string, path string) int CreateAccessGroup func(childComplexity int, environmentID string, name string, description *string) int CreateSecret func(childComplexity int, input *AddSecretsInput) int CreateUser func(childComplexity int, input *AddUsersInput) int @@ -119,7 +120,7 @@ type ComplexityRoot struct { UpdateDeleteSecret func(childComplexity int, secret string, environmentID string) int UpdateDeleteUser func(childComplexity int, userid string) int UpdateEnvironment func(childComplexity int, input *UpdateEnvironment) int - UpdateFilesNode func(childComplexity int, input []*FilesNodeInput) int + UpdateFilesNode func(childComplexity int, input *FilesNodeInput) int UpdateMe func(childComplexity int, input *AddUpdateMeInput) int UpdatePermissionToAccessGroup func(childComplexity int, environmentID string, resource string, resourceID string, access string, accessGroupID string) int UpdatePermissionToUser func(childComplexity int, environmentID string, resource string, resourceID string, access string, userID string) int @@ -129,6 +130,7 @@ type ComplexityRoot struct { UpdateSecretValue func(childComplexity int, secret string, value string, environmentID string) int UpdateUser func(childComplexity int, input *UpdateUsersInput) int UpdateUserToAccessGroup func(childComplexity int, environmentID string, userID string, accessGroupID string) int + UploadFileNode func(childComplexity int, environmentID string, nodeID string, pipelineID string, file graphql.Upload) int } Permissions struct { @@ -371,7 +373,9 @@ type MutationResolver interface { UpdatePermissionToAccessGroup(ctx context.Context, environmentID string, resource string, resourceID string, access string, accessGroupID string) (string, error) UpdateUserToAccessGroup(ctx context.Context, environmentID string, userID string, accessGroupID string) (string, error) RemoveUserFromAccessGroup(ctx context.Context, userID string, accessGroupID string, environmentID string) (string, error) - UpdateFilesNode(ctx context.Context, input []*FilesNodeInput) (string, error) + UpdateFilesNode(ctx context.Context, input *FilesNodeInput) (string, error) + UploadFileNode(ctx context.Context, environmentID string, nodeID string, pipelineID string, file graphql.Upload) (string, error) + CodeEditorRun(ctx context.Context, environmentID string, nodeID string, pipelineID string, path string) (string, error) UpdateMe(ctx context.Context, input *AddUpdateMeInput) (*models.Users, error) UpdateChangeMyPassword(ctx context.Context, password string) (*string, error) PipelinePermissionsToUser(ctx context.Context, environmentID string, resourceID string, access []string, userID string) (string, error) @@ -700,6 +704,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.AddUserToEnvironment(childComplexity, args["user_id"].(string), args["environment_id"].(string)), true + case "Mutation.codeEditorRun": + if e.complexity.Mutation.CodeEditorRun == nil { + break + } + + args, err := ec.field_Mutation_codeEditorRun_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CodeEditorRun(childComplexity, args["environmentID"].(string), args["nodeID"].(string), args["pipelineID"].(string), args["path"].(string)), true + case "Mutation.createAccessGroup": if e.complexity.Mutation.CreateAccessGroup == nil { break @@ -1022,7 +1038,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.UpdateFilesNode(childComplexity, args["input"].([]*FilesNodeInput)), true + return e.complexity.Mutation.UpdateFilesNode(childComplexity, args["input"].(*FilesNodeInput)), true case "Mutation.updateMe": if e.complexity.Mutation.UpdateMe == nil { @@ -1132,6 +1148,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdateUserToAccessGroup(childComplexity, args["environmentID"].(string), args["user_id"].(string), args["access_group_id"].(string)), true + case "Mutation.uploadFileNode": + if e.complexity.Mutation.UploadFileNode == nil { + break + } + + args, err := ec.field_Mutation_uploadFileNode_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UploadFileNode(childComplexity, args["environmentID"].(string), args["nodeID"].(string), args["pipelineID"].(string), args["file"].(graphql.Upload)), true + case "Permissions.Access": if e.complexity.Permissions.Access == nil { break @@ -2722,7 +2750,9 @@ extend type Mutation { removeUserFromAccessGroup(user_id: String!, access_group_id: String!, environmentID: String!): String! } `, BuiltIn: false}, - {Name: "resolvers/code_editor.graphqls", Input: `type CodeFolders { + {Name: "resolvers/code_editor.graphqls", Input: `scalar Upload + +type CodeFolders { folderID: String! parentID: String! folderName: String! @@ -2757,7 +2787,21 @@ extend type Mutation { + **Route**: Private + **Permissions**: admin_platform, platform_environment, specific_pipeline[write] """ - updateFilesNode(input:[FilesNodeInput]!): String! + updateFilesNode(input:FilesNodeInput): String! + + """ + Upload a node file. + + **Route**: Private + + **Permissions**: admin_platform, platform_environment, specific_pipeline[write] + """ + uploadFileNode(environmentID: String!, nodeID: String!, pipelineID: String!, file:Upload!): String! + + """ + Upload a node file. + + **Route**: Private + + **Permissions**: admin_platform, platform_environment, specific_pipeline[write] + """ + codeEditorRun(environmentID: String!, nodeID: String!, pipelineID: String!, path:String!): String! } `, BuiltIn: false}, {Name: "resolvers/me.graphqls", Input: `input AddUpdateMeInput { @@ -3627,6 +3671,48 @@ func (ec *executionContext) field_Mutation_addUserToEnvironment_args(ctx context return args, nil } +func (ec *executionContext) field_Mutation_codeEditorRun_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["environmentID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("environmentID")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["environmentID"] = arg0 + var arg1 string + if tmp, ok := rawArgs["nodeID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nodeID")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["nodeID"] = arg1 + var arg2 string + if tmp, ok := rawArgs["pipelineID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("pipelineID")) + arg2, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["pipelineID"] = arg2 + var arg3 string + if tmp, ok := rawArgs["path"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("path")) + arg3, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["path"] = arg3 + return args, nil +} + func (ec *executionContext) field_Mutation_createAccessGroup_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -4236,10 +4322,10 @@ func (ec *executionContext) field_Mutation_updateEnvironment_args(ctx context.Co func (ec *executionContext) field_Mutation_updateFilesNode_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} - var arg0 []*FilesNodeInput + var arg0 *FilesNodeInput if tmp, ok := rawArgs["input"]; ok { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) - arg0, err = ec.unmarshalNFilesNodeInput2ᚕᚖdataplaneᚋmainappᚋgraphqlᚋprivateᚐFilesNodeInput(ctx, tmp) + arg0, err = ec.unmarshalOFilesNodeInput2ᚖdataplaneᚋmainappᚋgraphqlᚋprivateᚐFilesNodeInput(ctx, tmp) if err != nil { return nil, err } @@ -4491,6 +4577,48 @@ func (ec *executionContext) field_Mutation_updateUser_args(ctx context.Context, return args, nil } +func (ec *executionContext) field_Mutation_uploadFileNode_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["environmentID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("environmentID")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["environmentID"] = arg0 + var arg1 string + if tmp, ok := rawArgs["nodeID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nodeID")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["nodeID"] = arg1 + var arg2 string + if tmp, ok := rawArgs["pipelineID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("pipelineID")) + arg2, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["pipelineID"] = arg2 + var arg3 graphql.Upload + if tmp, ok := rawArgs["file"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("file")) + arg3, err = ec.unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx, tmp) + if err != nil { + return nil, err + } + } + args["file"] = arg3 + return args, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -6663,7 +6791,91 @@ func (ec *executionContext) _Mutation_updateFilesNode(ctx context.Context, field fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().UpdateFilesNode(rctx, args["input"].([]*FilesNodeInput)) + return ec.resolvers.Mutation().UpdateFilesNode(rctx, args["input"].(*FilesNodeInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation_uploadFileNode(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_uploadFileNode_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UploadFileNode(rctx, args["environmentID"].(string), args["nodeID"].(string), args["pipelineID"].(string), args["file"].(graphql.Upload)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation_codeEditorRun(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_codeEditorRun_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().CodeEditorRun(rctx, args["environmentID"].(string), args["nodeID"].(string), args["pipelineID"].(string), args["path"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -16324,6 +16536,26 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, innerFunc) + if out.Values[i] == graphql.Null { + invalids++ + } + case "uploadFileNode": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_uploadFileNode(ctx, field) + } + + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, innerFunc) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "codeEditorRun": + innerFunc := func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_codeEditorRun(ctx, field) + } + + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, innerFunc) + if out.Values[i] == graphql.Null { invalids++ } @@ -19492,23 +19724,6 @@ func (ec *executionContext) marshalNCodeFolders2ᚕᚖdataplaneᚋmainappᚋdata return ret } -func (ec *executionContext) unmarshalNFilesNodeInput2ᚕᚖdataplaneᚋmainappᚋgraphqlᚋprivateᚐFilesNodeInput(ctx context.Context, v interface{}) ([]*FilesNodeInput, error) { - var vSlice []interface{} - if v != nil { - vSlice = graphql.CoerceList(v) - } - var err error - res := make([]*FilesNodeInput, len(vSlice)) - for i := range vSlice { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalOFilesNodeInput2ᚖdataplaneᚋmainappᚋgraphqlᚋprivateᚐFilesNodeInput(ctx, vSlice[i]) - if err != nil { - return nil, err - } - } - return res, nil -} - func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v interface{}) (float64, error) { res, err := graphql.UnmarshalFloatContext(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -19857,6 +20072,21 @@ func (ec *executionContext) marshalNTime2ᚖtimeᚐTime(ctx context.Context, sel return res } +func (ec *executionContext) unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, v interface{}) (graphql.Upload, error) { + res, err := graphql.UnmarshalUpload(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, sel ast.SelectionSet, v graphql.Upload) graphql.Marshaler { + res := graphql.MarshalUpload(v) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + } + return res +} + func (ec *executionContext) marshalNWorkerTasks2ᚕᚖdataplaneᚋmainappᚋgraphqlᚋprivateᚐWorkerTasksᚄ(ctx context.Context, sel ast.SelectionSet, v []*WorkerTasks) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup diff --git a/app/mainapp/graphql/private/resolvers/code_editor.graphqls b/app/mainapp/graphql/private/resolvers/code_editor.graphqls index 7fac50776..51d454d10 100644 --- a/app/mainapp/graphql/private/resolvers/code_editor.graphqls +++ b/app/mainapp/graphql/private/resolvers/code_editor.graphqls @@ -1,3 +1,5 @@ +scalar Upload + type CodeFolders { folderID: String! parentID: String! @@ -33,5 +35,19 @@ extend type Mutation { + **Route**: Private + **Permissions**: admin_platform, platform_environment, specific_pipeline[write] """ - updateFilesNode(input:[FilesNodeInput]!): String! + updateFilesNode(input:FilesNodeInput): String! + + """ + Upload a node file. + + **Route**: Private + + **Permissions**: admin_platform, platform_environment, specific_pipeline[write] + """ + uploadFileNode(environmentID: String!, nodeID: String!, pipelineID: String!, file:Upload!): String! + + """ + Run script. + + **Route**: Private + + **Permissions**: admin_platform, platform_environment, specific_pipeline[write] + """ + codeEditorRun(environmentID: String!, nodeID: String!, pipelineID: String!, path:String!): String! } diff --git a/app/mainapp/graphql/private/resolvers/code_editor.resolvers.go b/app/mainapp/graphql/private/resolvers/code_editor.resolvers.go index 3f7e64256..f97590b58 100644 --- a/app/mainapp/graphql/private/resolvers/code_editor.resolvers.go +++ b/app/mainapp/graphql/private/resolvers/code_editor.resolvers.go @@ -4,6 +4,7 @@ package privateresolvers // will be copied through when generating and any unknown code will be moved to the end. import ( + "bufio" "context" permissions "dataplane/mainapp/auth_permissions" "dataplane/mainapp/database" @@ -11,21 +12,23 @@ import ( privategraphql "dataplane/mainapp/graphql/private" "dataplane/mainapp/logging" "errors" + "log" "os" + "github.com/99designs/gqlgen/graphql" "gorm.io/gorm/clause" ) -func (r *mutationResolver) UpdateFilesNode(ctx context.Context, input []*privategraphql.FilesNodeInput) (string, error) { +func (r *mutationResolver) UpdateFilesNode(ctx context.Context, input *privategraphql.FilesNodeInput) (string, error) { currentUser := ctx.Value("currentUser").(string) platformID := ctx.Value("platformID").(string) // ----- Permissions perms := []models.Permissions{ {Subject: "user", SubjectID: currentUser, Resource: "admin_platform", ResourceID: platformID, Access: "write", EnvironmentID: "d_platform"}, - {Subject: "user", SubjectID: currentUser, Resource: "platform_environment", ResourceID: platformID, Access: "write", EnvironmentID: input[0].EnvironmentID}, - {Subject: "user", SubjectID: currentUser, Resource: "environment_edit_all_pipelines", ResourceID: platformID, Access: "write", EnvironmentID: input[0].EnvironmentID}, - {Subject: "user", SubjectID: currentUser, Resource: "specific_pipeline", ResourceID: input[0].PipelineID, Access: "write", EnvironmentID: input[0].EnvironmentID}, + {Subject: "user", SubjectID: currentUser, Resource: "platform_environment", ResourceID: platformID, Access: "write", EnvironmentID: input.EnvironmentID}, + {Subject: "user", SubjectID: currentUser, Resource: "environment_edit_all_pipelines", ResourceID: platformID, Access: "write", EnvironmentID: input.EnvironmentID}, + {Subject: "user", SubjectID: currentUser, Resource: "specific_pipeline", ResourceID: input.PipelineID, Access: "write", EnvironmentID: input.EnvironmentID}, } permOutcome, _, _, _ := permissions.MultiplePermissionChecks(perms) @@ -36,32 +39,85 @@ func (r *mutationResolver) UpdateFilesNode(ctx context.Context, input []*private // ----- Add node files to database - for _, p := range input { - - f := &models.CodeFolders{ - EnvironmentID: p.EnvironmentID, - PipelineID: p.PipelineID, - NodeID: p.NodeID, - FolderID: p.FolderID, - ParentID: p.ParentID, - FolderName: p.FolderName, - FType: p.FType, - Level: "node", - Active: p.Active, - } + f := &models.CodeFolders{ + EnvironmentID: input.EnvironmentID, + PipelineID: input.PipelineID, + NodeID: input.NodeID, + FolderID: input.FolderID, + ParentID: input.ParentID, + FolderName: input.FolderName, + FType: input.FType, + Level: "node", + Active: input.Active, + } - err := database.DBConn.Clauses(clause.OnConflict{UpdateAll: true}).Where("folder_id = ?", p.FolderID). - Create(&f).Error + err := database.DBConn.Clauses(clause.OnConflict{UpdateAll: true}).Where("folder_id = ?", input.FolderID). + Create(&f).Error - if err != nil { - if os.Getenv("debug") == "true" { - logging.PrintSecretsRedact(err) - } - return "", errors.New("update files node database error.") + if err != nil { + if os.Getenv("debug") == "true" { + logging.PrintSecretsRedact(err) } + return "", errors.New("update files node database error.") + } + + return "Success", nil +} + +func (r *mutationResolver) UploadFileNode(ctx context.Context, environmentID string, nodeID string, pipelineID string, file graphql.Upload) (string, error) { + currentUser := ctx.Value("currentUser").(string) + platformID := ctx.Value("platformID").(string) + + // ----- Permissions + perms := []models.Permissions{ + {Subject: "user", SubjectID: currentUser, Resource: "admin_platform", ResourceID: platformID, Access: "write", EnvironmentID: "d_platform"}, + {Subject: "user", SubjectID: currentUser, Resource: "platform_environment", ResourceID: platformID, Access: "write", EnvironmentID: environmentID}, + {Subject: "user", SubjectID: currentUser, Resource: "environment_edit_all_pipelines", ResourceID: platformID, Access: "write", EnvironmentID: environmentID}, + {Subject: "user", SubjectID: currentUser, Resource: "specific_pipeline", ResourceID: pipelineID, Access: "write", EnvironmentID: environmentID}, + } + + permOutcome, _, _, _ := permissions.MultiplePermissionChecks(perms) + + if permOutcome == "denied" { + return "", errors.New("Requires permissions.") + } + // Save to code-files + save, err := os.Create("../../code-files/" + file.Filename) + if err != nil { + log.Fatal(err) } + writer := bufio.NewWriter(save) + + p := make([]byte, file.Size) + file.File.Read(p) + writer.Write(p) + writer.Flush() + + return "Success", nil +} + +func (r *mutationResolver) CodeEditorRun(ctx context.Context, environmentID string, nodeID string, pipelineID string, path string) (string, error) { + currentUser := ctx.Value("currentUser").(string) + platformID := ctx.Value("platformID").(string) + + // ----- Permissions + perms := []models.Permissions{ + {Subject: "user", SubjectID: currentUser, Resource: "admin_platform", ResourceID: platformID, Access: "write", EnvironmentID: "d_platform"}, + {Subject: "user", SubjectID: currentUser, Resource: "platform_environment", ResourceID: platformID, Access: "write", EnvironmentID: environmentID}, + {Subject: "user", SubjectID: currentUser, Resource: "environment_edit_all_pipelines", ResourceID: platformID, Access: "write", EnvironmentID: environmentID}, + {Subject: "user", SubjectID: currentUser, Resource: "specific_pipeline", ResourceID: pipelineID, Access: "write", EnvironmentID: environmentID}, + } + + permOutcome, _, _, _ := permissions.MultiplePermissionChecks(perms) + + if permOutcome == "denied" { + return "", errors.New("Requires permissions.") + } + + log.Println("Path: ", path) + return "Success", nil } diff --git a/app/mainapp/routes/routes.go b/app/mainapp/routes/routes.go index 1d6e335cf..e5f99510a 100644 --- a/app/mainapp/routes/routes.go +++ b/app/mainapp/routes/routes.go @@ -275,6 +275,19 @@ func Setup(port string) *fiber.App { worker.RoomUpdates(c, environment, subject, id) })) + // Download code files + app.Get("/app/private/code-files/:filename", func(c *fiber.Ctx) error { + filename := string(c.Params("filename")) + dat, err := os.ReadFile("../../code-files/" + filename) + if err != nil { + if os.Getenv("debug") == "true" { + logging.PrintSecretsRedact(err) + } + return err + } + return c.SendString(string(dat)) + }) + // Check healthz app.Get("/healthz", func(c *fiber.Ctx) error { return c.SendString("Hello 👋! Healthy 🍏") diff --git a/frontend/src/components/CustomNodesContent/BashNode/index.jsx b/frontend/src/components/CustomNodesContent/BashNode/index.jsx index 0cc96f017..9a32e2eb7 100644 --- a/frontend/src/components/CustomNodesContent/BashNode/index.jsx +++ b/frontend/src/components/CustomNodesContent/BashNode/index.jsx @@ -86,7 +86,7 @@ const BashNode = (props) => { - {isEditorPage ? : } + {isEditorPage ? : } diff --git a/frontend/src/components/CustomNodesContent/PythonNode/index.jsx b/frontend/src/components/CustomNodesContent/PythonNode/index.jsx index 04c50acd7..fd1ca2ba0 100644 --- a/frontend/src/components/CustomNodesContent/PythonNode/index.jsx +++ b/frontend/src/components/CustomNodesContent/PythonNode/index.jsx @@ -86,7 +86,7 @@ const PythonNode = (props) => { - {isEditorPage ? : } + {isEditorPage ? : } diff --git a/frontend/src/components/EditorPage/EditorColumn/index.jsx b/frontend/src/components/EditorPage/EditorColumn/index.jsx index cf3a7223a..02bb059e4 100644 --- a/frontend/src/components/EditorPage/EditorColumn/index.jsx +++ b/frontend/src/components/EditorPage/EditorColumn/index.jsx @@ -1,5 +1,5 @@ import { Box, Chip, Grid, IconButton, Typography, useTheme } from '@mui/material'; -import { forwardRef, useRef } from 'react'; +import { forwardRef, useEffect, useRef } from 'react'; import Editor from '@monaco-editor/react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPlayCircle, faTimes } from '@fortawesome/free-solid-svg-icons'; @@ -7,6 +7,12 @@ import { useGlobalEditorState } from '../../../pages/Editor'; import { Downgraded } from '@hookstate/core'; import { useState } from 'react'; import CustomDragHandle from '../../CustomDragHandle'; +import { useUploadFileNode } from '../../../graphql/uploadFileNode'; +import { useSnackbar } from 'notistack'; +import { useGlobalAuthState } from '../../../Auth/UserAuth'; +import { useCodeEditorRun } from '../../../graphql/codeEditorRun'; + +const codeFilesEndpoint = process.env.REACT_APP_CODE_ENDPOINT_PRIVATE; const EditorColumn = forwardRef(({ children, ...rest }, ref) => { // Editor state @@ -15,12 +21,21 @@ const EditorColumn = forwardRef(({ children, ...rest }, ref) => { // Theme hook const theme = useTheme(); + const { enqueueSnackbar } = useSnackbar(); + + const authState = useGlobalAuthState(); + const jwt = authState.authToken.get(); + // Global editor state const EditorGlobal = useGlobalEditorState(); // Ref const editorRef = useRef(null); + // Graphql hook + const uploadFileNode = useUploadFileNodeHook(rest.pipeline); + const codeEditorRun = useCodeEditorRunHook(rest.pipeline); + const handleEditorOnMount = (editor) => { editorRef.current = editor; setEditorInstance(editor); @@ -87,13 +102,66 @@ const EditorColumn = forwardRef(({ children, ...rest }, ref) => { } } */ + // Handle tab change + useEffect(() => { + // If no selection, return + if (!EditorGlobal.selectedFile.value) return; + + // // If file is newly created, return + // if (!EditorGlobal.selectedFile.diffValue.value) { + // return; + // } + + // If selected file already has content, return + if (EditorGlobal.selectedFile.content.value) { + return; + } + if ( + EditorGlobal.selectedFile.content.value && + EditorGlobal.selectedFile.diffValue.value && + EditorGlobal.selectedFile.content.value === EditorGlobal.selectedFile.diffValue.value + ) { + return; + } + + fetch(`${codeFilesEndpoint}/${EditorGlobal.parentID.value}_${EditorGlobal.parentName.value}_${EditorGlobal.selectedFile.name.value}`) + .then(async (response) => { + if (response.status !== 200) { + const error = (response && response.statusText) || response.status; + return Promise.reject(error); + } + let fileContent = await response.text(); + EditorGlobal.selectedFile.content.set(fileContent); + }) + .catch((error) => { + enqueueSnackbar("Can't get file: " + error, { variant: 'error' }); + }); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [EditorGlobal.selectedFile?.id?.value]); + + // Handle ctrl+s + useEffect(() => { + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [jwt]); + const handleKeyDown = (e) => { + if (e.keyCode === 83 && e.ctrlKey) { + e.preventDefault(); + if (!EditorGlobal.selectedFile.name.value) return; + uploadFileNode(); + } + }; + return (
{ } label="Play" + onClick={() => codeEditorRun()} sx={{ mr: 0, bgcolor: 'primary.main', color: '#fff', fontWeight: 600 }} /> @@ -153,10 +222,16 @@ const EditorColumn = forwardRef(({ children, ...rest }, ref) => { defaultLanguage={EditorGlobal.selectedFile.get()?.language} path={EditorGlobal.selectedFile.get()?.name} defaultValue={EditorGlobal.selectedFile.get()?.content} + value={EditorGlobal.selectedFile.get()?.diffValue || EditorGlobal.selectedFile.get()?.content} theme={theme.palette.mode === 'dark' ? 'vs-dark' : 'customTheme'} height="100%" saveViewState onChange={handleEditorChange} + options={{ + minimap: { + enabled: false, + }, + }} /> ) : ( @@ -171,3 +246,68 @@ const EditorColumn = forwardRef(({ children, ...rest }, ref) => { }); export default EditorColumn; + +// ----- Custom hook +export const useUploadFileNodeHook = (pipeline) => { + const environmentID = pipeline.environmentID; + const pipelineID = pipeline.pipelineID; + const nodeID = pipeline.nodeID; + + // Global editor state + const EditorGlobal = useGlobalEditorState(); + + // GraphQL hook + const uploadFileNode = useUploadFileNode(); + + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + // Upload file + return async () => { + const file = new File( + [EditorGlobal.selectedFile.diffValue.value], + `${EditorGlobal.parentID.value}_${EditorGlobal.parentName.value}_${EditorGlobal.selectedFile.name.value}`, + { + type: 'text/plain', + } + ); + const response = await uploadFileNode({ environmentID, pipelineID, nodeID, file }); + + if (response.status) { + enqueueSnackbar("Can't get files: " + (response.r || response.error), { variant: 'error' }); + } else if (response.errors) { + response.errors.map((err) => enqueueSnackbar(err.message, { variant: 'error' })); + } else { + enqueueSnackbar('File saved.', { variant: 'success' }); + EditorGlobal.selectedFile.isEditing.set(false); + } + }; +}; + +const useCodeEditorRunHook = (pipeline) => { + const environmentID = pipeline.environmentID; + const pipelineID = pipeline.pipelineID; + const nodeID = pipeline.nodeID; + + // Global editor state + const EditorGlobal = useGlobalEditorState(); + + // GraphQL hook + const codeEditorRun = useCodeEditorRun(); + + const { enqueueSnackbar } = useSnackbar(); + + // Run script + return async () => { + const path = `${EditorGlobal.parentID.value}_${EditorGlobal.parentName.value}_${EditorGlobal.selectedFile.name.value}`; + + const response = await codeEditorRun({ environmentID, pipelineID, nodeID, path }); + + if (response.status) { + enqueueSnackbar("Can't get files: " + (response.r || response.error), { variant: 'error' }); + } else if (response.errors) { + response.errors.map((err) => enqueueSnackbar(err.message, { variant: 'error' })); + } else { + enqueueSnackbar('Success', { variant: 'success' }); + } + }; +}; diff --git a/frontend/src/components/EditorPage/FileManager/index.jsx b/frontend/src/components/EditorPage/FileManager/index.jsx index d0e801a04..a4f9e2d96 100644 --- a/frontend/src/components/EditorPage/FileManager/index.jsx +++ b/frontend/src/components/EditorPage/FileManager/index.jsx @@ -14,6 +14,8 @@ import { findNodeById, findNodeByName, getParentId, getPath, isFolder, removeByI import CustomDragHandle from '../../CustomDragHandle'; import { Downgraded } from '@hookstate/core'; import { useGetFilesNode } from '../../../graphql/getFilesNode'; +import { useUpdateFilesNode } from '../../../graphql/updateFilesNode'; +import { useUploadFileNodeHook } from '../EditorColumn'; const MOCK_ROOT_ID = 'all'; @@ -27,7 +29,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { const [workerGroups, setWorkerGroups] = useState([]); const [selected, setSelected] = useState(null); const [expanded, setExpanded] = useState([]); - // const [data, setData] = useState(MOCK_DATA); + const [data, setData] = useState([]); // File/folder creation states const [isAddingNew, setIsAddingNew] = useState(false); @@ -42,7 +44,17 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { const editingFileRef = useRef(); // Graphql hook - const getFilesNode = useGetFilesNodeHook(rest.pipeline, rest.setData); + const getFilesNode = useGetFilesNodeHook(rest.pipeline, setData); + const updateFilesNode = useUpdateFilesNodeHook(rest.pipeline.environmentID, rest.pipeline.pipelineID, rest.pipeline.nodeID); + const uploadFileNode = useUploadFileNodeHook(rest.pipeline); + + // Set parent name and id for upload file names + useEffect(() => { + Editor.parentName.set(data.name); + Editor.parentID.set(data.id); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data.id, data.name]); // Check if selected file changed useEffect(() => { @@ -83,14 +95,15 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { }; // Custom GraphQL hook - const getWorkerGroups = useGetWorkerGroupsHook(Environment.name.get(), setWorkerGroups); + const getWorkerGroups = useGetWorkerGroupsHook(Environment.id.get(), setWorkerGroups); // Get workers and files on load useEffect(() => { + if (!Environment.id.get()) return; getWorkerGroups(); getFilesNode(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [Environment.id.get()]); // Handle ESC key press when adding a new file const escFunction = useCallback((event) => { @@ -132,7 +145,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { // Check if enter has being pressed if (e.charCode === 13) { const check = checkFileName(newFileName); - const alreadyExistsFile = findNodeByName(rest.data?.children, newFileName); + const alreadyExistsFile = findNodeByName(data?.children, newFileName); if (!check) return; if (alreadyExistsFile) { @@ -159,18 +172,25 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { // Push new file to root children //Find the ID first - const currentSelectedElement = findNodeById(rest.data.children, selected); // - if (selected === rest.data.id) { - rest.data.children.push(newFileMock); + const currentSelectedElement = findNodeById(data.children, selected); + if (selected === data.id) { + // If top level folder + let newData = { ...data }; + newData.children.push(newFileMock); + setData(newData); + selectAndOpenNewFile(newFileMock); + uploadFileNode(); } else if (currentSelectedElement && currentSelectedElement.children) { - currentSelectedElement.children.push(newFileMock); + // If lower level folder + currentSelectedElement.children.push(newFileMock); // ??? selectAndOpenNewFile(newFileMock); + uploadFileNode(); } else { + alert('Click on the folder you would like to create the file in.'); return; } - // Success snack - enqueueSnackbar(`File ${newFileName} created!`, { variant: 'success' }); + updateFilesNode(newFileMock, `File ${newFileName} created!`); // Set values to default setNewFileName(''); @@ -198,7 +218,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { // Check if enter has being pressed if (e.charCode === 13) { const check = checkFolderName(newFolderName); - const alreadyExistsFolder = findNodeByName(rest.data?.children, newFolderName); + const alreadyExistsFolder = findNodeByName(data?.children, newFolderName); if (!check) return; if (alreadyExistsFolder) { @@ -222,9 +242,11 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { // Push new file to root children //Find the ID first - const currentSelectedElement = findNodeById(rest.data.children, selected); - if (selected === rest.data.id) { - rest.data.children.push(newFolderMock); + const currentSelectedElement = findNodeById(data.children, selected); + if (selected === data.id) { + let newData = { ...data }; + newData.children.push(newFolderMock); + setData(newData); } else if (currentSelectedElement && currentSelectedElement.children) { currentSelectedElement.children.push(newFolderMock); selectAndOpenNewFile(newFolderMock); @@ -232,8 +254,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { return; } - // Success snack - enqueueSnackbar(`Folder ${newFolderName} created!`, { variant: 'success' }); + updateFilesNode(newFolderMock, `Folder ${newFolderName} created!`); // Set values to default setNewFolderName(''); @@ -254,12 +275,12 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { const handleEditKeyPress = (e, nodes) => { if (e.charCode === 13) { - const folder = isFolder(selected, rest.data); - const elementToChange = findNodeById(rest.data.children, selected); + const folder = isFolder(selected, data); + const elementToChange = findNodeById(data.children, selected); if (folder) { const check = checkFolderName(tmpFileName); - const alreadyExistsFolder = findNodeByName(rest.data?.children, tmpFileName); + const alreadyExistsFolder = findNodeByName(data?.children, tmpFileName); if (!check) { setIsEditing(false); @@ -275,7 +296,8 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { if (elementToChange) { elementToChange.name = tmpFileName; - enqueueSnackbar(`Changed folder name to ${tmpFileName}!`, { variant: 'success' }); + + updateFilesNode(elementToChange, `Changed folder name to ${tmpFileName}!`); setTmpFileName(null); setIsEditing(false); @@ -283,7 +305,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { } } else { const check = checkFileName(tmpFileName); - const alreadyExistsFile = findNodeByName(rest.data?.children, tmpFileName); + const alreadyExistsFile = findNodeByName(data?.children, tmpFileName); if (!check) { setIsEditing(false); @@ -299,7 +321,8 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { if (elementToChange) { elementToChange.name = tmpFileName; - enqueueSnackbar(`Changed file name to ${tmpFileName}!`, { variant: 'success' }); + + updateFilesNode(elementToChange, `Changed file name to ${tmpFileName}!`); setTmpFileName(null); setIsEditing(false); @@ -313,9 +336,9 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { // ####### Delete ######## const handleDeleteIconClick = () => { // Check if it's file or folder - const isTryingToDeleteFolder = isFolder(selected, rest.data); + const isTryingToDeleteFolder = isFolder(selected, data); - const current = findNodeById(rest.data.children, selected); + const current = findNodeById(data.children, selected); let message = `Are you sure you want to delete - ${current?.name}?`; if (current?.children && current?.children.length > 0) { @@ -325,9 +348,9 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { const askForConfirmation = window.confirm(message); if (askForConfirmation === true) { - const removed = removeById(rest.data.children, selected); - const newData = { ...rest.data, children: removed }; - rest.setData(newData); + const removed = removeById(data.children, selected); + const newData = { ...data, children: removed }; + setData(newData); // Remove child from tabs if (current?.children && current?.children.length > 0) { @@ -351,12 +374,12 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { // ####### Utils ######## // Checks if selected folder is expanded const expandFolder = () => { - if (!selected || selected === rest.data.id) { - if (expanded?.filter((id) => id === rest.data.id).length > 0) { + if (!selected || selected === data.id) { + if (expanded?.filter((id) => id === data.id).length > 0) { return; } else { - setExpanded([...expanded, rest.data.id]); - setSelected(rest.data.id); + setExpanded([...expanded, data.id]); + setSelected(data.id); } } else { if (expanded?.filter((id) => id === selected).length > 0) { @@ -369,16 +392,16 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { // Check if it is folder of file const checkLevel = () => { - if (!selected || selected === rest.data.id) return; + if (!selected || selected === data.id) return; - const selectedInfo = findNodeById(rest.data.children, selected); + const selectedInfo = findNodeById(data.children, selected); if (selectedInfo) { if (selectedInfo.children) { // Is folder\ return false; } else { // Is file - const closestParentID = getParentId(rest.data.children, selected); + const closestParentID = getParentId(data.children, selected); closestParentID && setSelected(closestParentID); } } @@ -495,7 +518,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { /> } onClick={() => handleFileClick(nodes)}> - + @@ -547,7 +570,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { Editor.tabs.set((prevTabs) => [...prevTabs, file]); // Set file path - const path = getPath(rest.data.children, selected); + const path = getPath(data.children, selected); Editor.currentPath.set(path); }; @@ -557,6 +580,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { option.WorkerGroup} + sx={{ '& fieldset': { borderRadius: 0 } }} renderInput={(params) => } /> @@ -565,7 +589,6 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { sx={{ backgroundColor: 'background.main', border: '1px solid #D3D3D3', - borderRadius: '7px', position: 'absolute', mt: 1, top: 40, @@ -604,7 +627,7 @@ const FileManagerColumn = forwardRef(({ children, ...rest }, ref) => { p: '0 5px', }}> - {renderTree(rest.data)} + {renderTree(data)} @@ -731,6 +754,12 @@ export const useGetFilesNodeHook = (pipeline, setData) => { function prepareForFrontEnd(data) { data = JSON.parse(JSON.stringify(data).replaceAll('folderID', 'id').replaceAll('folderName', 'name')); + data.forEach((a) => (a.fType === 'folder' ? (a.children = []) : null)); + + if (data.length === 1) { + data[0].children = []; + return data[0]; + } let parentIDs = data.map((a) => a.id); const top = data.filter((a) => !parentIDs.includes(a.parentID))[0]; @@ -774,7 +803,39 @@ function prepareForFrontEnd(data) { } } } - recursive2(top.children); return top; } + +// ----- Custom hook +export const useUpdateFilesNodeHook = (environmentID, pipelineID, nodeID) => { + // GraphQL hook + const updateFilesNode = useUpdateFilesNode(); + + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + // Get files + return async (data, msg) => { + const input = { + folderID: data.id, + parentID: data.parentID, + environmentID, + pipelineID, + nodeID, + folderName: data.name, + fType: data.fType, + active: true, + }; + + const response = await updateFilesNode({ input }); + + if (response.r === 'error') { + closeSnackbar(); + enqueueSnackbar("Can't get files: " + response.msg, { variant: 'error' }); + } else if (response.errors) { + response.errors.map((err) => enqueueSnackbar(err.message, { variant: 'error' })); + } else { + // enqueueSnackbar(msg, { variant: 'success' }); + } + }; +}; diff --git a/frontend/src/components/EditorPage/LogsColumn/index.jsx b/frontend/src/components/EditorPage/LogsColumn/index.jsx index 49059a6fa..bab71ded7 100644 --- a/frontend/src/components/EditorPage/LogsColumn/index.jsx +++ b/frontend/src/components/EditorPage/LogsColumn/index.jsx @@ -12,7 +12,6 @@ const LogsColumn = forwardRef(({ children, ...rest }, ref) => { sx={{ backgroundColor: 'background.main', border: '1px solid #D3D3D3', - borderRadius: '7px', height: '100%', ml: 0.8, }}> diff --git a/frontend/src/components/MoreInfoContent/ProcessTypeNodeItem/index.jsx b/frontend/src/components/MoreInfoContent/ProcessTypeNodeItem/index.jsx index 508536627..d15bb014e 100644 --- a/frontend/src/components/MoreInfoContent/ProcessTypeNodeItem/index.jsx +++ b/frontend/src/components/MoreInfoContent/ProcessTypeNodeItem/index.jsx @@ -10,7 +10,7 @@ const ProcessTypeNodeItem = (props) => { const handleCodeClick = () => { history.push({ pathname: `/editor/${FlowState.pipelineInfo.attach(Downgraded).get()?.pipelineID}`, - state: { ...FlowState.pipelineInfo.attach(Downgraded).get(), nodeID: props.nodeId }, + state: { ...FlowState.pipelineInfo.attach(Downgraded).get(), nodeID: props.nodeId, workerGroup: props.workerGroup }, }); props.handleCloseMenu(); diff --git a/frontend/src/enviroment/env.build b/frontend/src/enviroment/env.build index a4f24a8b9..d4d8ee075 100644 --- a/frontend/src/enviroment/env.build +++ b/frontend/src/enviroment/env.build @@ -1,6 +1,7 @@ REACT_APP_DATAPLANE_ENDPOINT="" REACT_APP_GRAPHQL_ENDPOINT_PUBLIC=/app/public/graphql REACT_APP_GRAPHQL_ENDPOINT_PRIVATE=/app/private/graphql +REACT_APP_CODE_ENDPOINT_PRIVATE=/app/private/code-files REACT_APP_WEBSOCKET_ENDPOINT=/app/ws/workerstats REACT_APP_WEBSOCKET_ROOMS_ENDPOINT=/app/ws/rooms REACT_APP_DATAPLANE_ENV=build diff --git a/frontend/src/enviroment/env.development b/frontend/src/enviroment/env.development index fa7194d12..8e591b26f 100644 --- a/frontend/src/enviroment/env.development +++ b/frontend/src/enviroment/env.development @@ -1,6 +1,7 @@ REACT_APP_DATAPLANE_ENDPOINT=http://localhost:9000 REACT_APP_GRAPHQL_ENDPOINT_PUBLIC=http://localhost:9000/app/public/graphql REACT_APP_GRAPHQL_ENDPOINT_PRIVATE=http://localhost:9000/app/private/graphql +REACT_APP_CODE_ENDPOINT_PRIVATE=http://localhost:9000/app/private/code-files REACT_APP_WEBSOCKET_ENDPOINT=ws://localhost:9000/app/ws/workerstats REACT_APP_WEBSOCKET_ROOMS_ENDPOINT=ws://localhost:9000/app/ws/rooms REACT_APP_DATAPLANE_ENV=development diff --git a/frontend/src/graphql/codeEditorRun.js b/frontend/src/graphql/codeEditorRun.js new file mode 100644 index 000000000..d023fecda --- /dev/null +++ b/frontend/src/graphql/codeEditorRun.js @@ -0,0 +1,32 @@ +import { gql, GraphQLClient } from 'graphql-request'; +import { useGlobalAuthState } from '../Auth/UserAuth'; + +const graphlqlEndpoint = process.env.REACT_APP_GRAPHQL_ENDPOINT_PRIVATE; + +const query = gql` + mutation codeEditorRun($environmentID: String!, $nodeID: String!, $pipelineID: String!, $path: String!) { + codeEditorRun(environmentID: $environmentID, nodeID: $nodeID, pipelineID: $pipelineID, path: $path) + } +`; + +export const useCodeEditorRun = () => { + const authState = useGlobalAuthState(); + const jwt = authState.authToken.get(); + + const headers = { + Authorization: 'Bearer ' + jwt, + }; + + const client = new GraphQLClient(graphlqlEndpoint, { + headers, + }); + + return async (input) => { + try { + const res = await client.request(query, input); + return res?.codeEditorRun; + } catch (error) { + return JSON.parse(JSON.stringify(error, undefined, 2)).response; + } + }; +}; diff --git a/frontend/src/graphql/updateFilesNode.js b/frontend/src/graphql/updateFilesNode.js index f5352be29..c4435dc67 100644 --- a/frontend/src/graphql/updateFilesNode.js +++ b/frontend/src/graphql/updateFilesNode.js @@ -4,7 +4,7 @@ import { useGlobalAuthState } from '../Auth/UserAuth'; const graphlqlEndpoint = process.env.REACT_APP_GRAPHQL_ENDPOINT_PRIVATE; const query = gql` - mutation updateFilesNode($input: [FilesNodeInput]!) { + mutation updateFilesNode($input: FilesNodeInput) { updateFilesNode(input: $input) } `; diff --git a/frontend/src/graphql/uploadFileNode.js b/frontend/src/graphql/uploadFileNode.js new file mode 100644 index 000000000..4905db602 --- /dev/null +++ b/frontend/src/graphql/uploadFileNode.js @@ -0,0 +1,32 @@ +import { gql, GraphQLClient } from 'graphql-request'; +import { useGlobalAuthState } from '../Auth/UserAuth'; + +const graphlqlEndpoint = process.env.REACT_APP_GRAPHQL_ENDPOINT_PRIVATE; + +const query = gql` + mutation uploadFileNode($environmentID: String!, $nodeID: String!, $pipelineID: String!, $file: Upload!) { + uploadFileNode(environmentID: $environmentID, nodeID: $nodeID, pipelineID: $pipelineID, file: $file) + } +`; + +export const useUploadFileNode = () => { + const authState = useGlobalAuthState(); + const jwt = authState.authToken.get(); + + const headers = { + Authorization: 'Bearer ' + jwt, + }; + + const client = new GraphQLClient(graphlqlEndpoint, { + headers, + }); + + return async (input) => { + try { + const res = await client.request(query, input); + return res?.uploadFileNode; + } catch (error) { + return JSON.parse(JSON.stringify(error, undefined, 2)).response; + } + }; +}; diff --git a/frontend/src/pages/Editor.jsx b/frontend/src/pages/Editor.jsx index 4e291a9f5..4f091dbdf 100644 --- a/frontend/src/pages/Editor.jsx +++ b/frontend/src/pages/Editor.jsx @@ -11,8 +11,6 @@ import { useEffect, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { useHistory } from 'react-router-dom'; import { Downgraded } from '@hookstate/core'; -import { useUpdateFilesNode } from '../graphql/updateFilesNode'; -import { useSnackbar } from 'notistack'; const ResponsiveGridLayout = WidthProvider(Responsive); @@ -23,19 +21,17 @@ export const globalEditorState = createState({ tabs: [], editor: null, currentPath: [], + parentID: null, + parentName: null, }); export const useGlobalEditorState = () => useHookState(globalEditorState); const PipelineEditor = () => { - const [data, setData] = useState([]); - // Hooks const history = useHistory(); const EditorGlobal = useGlobalEditorState(); const { state: pipeline } = useLocation(); - // Graphql hook - const updateFilesNode = useUpdateFilesNodeHook(pipeline.environmentID, pipeline.pipelineID, pipeline.nodeID); const editorRef = useRef(null); @@ -75,7 +71,6 @@ const PipelineEditor = () => { return true; }); } - updateFilesNode(data); }; return ( @@ -95,9 +90,9 @@ const PipelineEditor = () => { Code {'>'} {pipeline?.name} - + */} @@ -117,9 +112,9 @@ const PipelineEditor = () => { layouts={layouts} breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} cols={{ lg: 12, md: 6, sm: 3, xs: 2, xxs: 2 }}> - - - + + {/* */} + @@ -129,63 +124,3 @@ const PipelineEditor = () => { }; export default PipelineEditor; - -// ----- Custom hook -export const useUpdateFilesNodeHook = (environmentID, pipelineID, nodeID) => { - // GraphQL hook - const updateFilesNode = useUpdateFilesNode(); - - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - - // Get files - return async (data) => { - const input = prepareFilesNodeForBackend(data, environmentID, pipelineID, nodeID); - // return; - const response = await updateFilesNode({ input }); - - if (response.r === 'error') { - closeSnackbar(); - enqueueSnackbar("Can't get files: " + response.msg, { variant: 'error' }); - } else if (response.errors) { - response.errors.map((err) => enqueueSnackbar(err.message, { variant: 'error' })); - } else { - enqueueSnackbar('Success', { variant: 'success' }); - } - }; -}; - -function prepareFilesNodeForBackend(files, environmentID, pipelineID, nodeID) { - let array = []; - array.push({ - folderID: files.id, - parentID: files.parentID, - environmentID, - pipelineID, - nodeID, - folderName: files.name, - fType: files.fType, - active: true, - }); - - function recursive(arr) { - for (const key of arr) { - if (key.children) { - recursive(key.children); - } - array.push({ - folderID: key.id, - parentID: key.parentID, - environmentID, - pipelineID, - nodeID, - folderName: key.name, - fType: key.fType, - active: true, - }); - } - return array; - } - recursive(files.children); - - return array; -} diff --git a/frontend/src/utils/editorLayouts.js b/frontend/src/utils/editorLayouts.js index 5f950e80e..624db7fa0 100644 --- a/frontend/src/utils/editorLayouts.js +++ b/frontend/src/utils/editorLayouts.js @@ -4,35 +4,35 @@ // 4 - Logs export const lgLayout = [ - { i: '1', x: 0, y: 0, w: 2, h: 2 }, + { i: '1', x: 0, y: 0, w: 2, h: 4 }, { i: '2', x: 0, y: 2, w: 2, h: 1.5 }, - { i: '3', x: 2, y: 0, w: 5, h: 3.5 }, - { i: '4', x: 7, y: 0, w: 5, h: 3.5 }, + { i: '3', x: 2, y: 0, w: 5, h: 4 }, + { i: '4', x: 7, y: 0, w: 5, h: 4 }, ]; export const mdLayout = [ - { i: '1', x: 0, y: 0, w: 2, h: 2 }, + { i: '1', x: 0, y: 0, w: 2, h: 4 }, { i: '2', x: 0, y: 2, w: 2, h: 2 }, { i: '3', x: 2, y: 0, w: 2, h: 4 }, { i: '4', x: 6, y: 0, w: 2, h: 4 }, ]; export const smLayout = [ - { i: '1', x: 0, y: 0, w: 1, h: 2 }, + { i: '1', x: 0, y: 0, w: 1, h: 4 }, { i: '2', x: 0, y: 2, w: 1, h: 2 }, { i: '3', x: 1, y: 0, w: 1, h: 4 }, { i: '4', x: 2, y: 0, w: 1, h: 4 }, ]; export const xsLayout = [ - { i: '1', x: 0, y: 0, w: 2, h: 2 }, + { i: '1', x: 0, y: 0, w: 2, h: 4 }, { i: '2', x: 0, y: 2, w: 2, h: 2 }, { i: '3', x: 2, y: 0, w: 5, h: 4 }, { i: '4', x: 6, y: 0, w: 5, h: 4 }, ]; export const xxsLayout = [ - { i: '1', x: 0, y: 0, w: 2, h: 2 }, + { i: '1', x: 0, y: 0, w: 2, h: 4 }, { i: '2', x: 0, y: 2, w: 2, h: 2 }, { i: '3', x: 2, y: 0, w: 5, h: 4 }, { i: '4', x: 6, y: 0, w: 5, h: 4 },