diff --git a/.env b/.env index 6af3f207..a07a0644 100644 --- a/.env +++ b/.env @@ -13,3 +13,4 @@ BACKEND_PORT=5001 FRONTEND_PORT=3001 FRONTEND_PORT_HTTPS=3443 OUTER_HOSTNAME=shuffle-backend +DB_LOCATION=/etc/shuffle diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index d15792a0..03a71e01 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -588,6 +588,7 @@ class AppBase: logging.basicConfig(format="{asctime} - {name} - {levelname}:{message}", style='{') logger = logging.getLogger(f"{cls.__name__}") logger.setLevel(logging.DEBUG) + print("Started execution!!") app = cls(redis=None, logger=logger, console_logger=logger) diff --git a/backend/go-app/codegen.go b/backend/go-app/codegen.go index abe03fa8..d1cf6ac0 100644 --- a/backend/go-app/codegen.go +++ b/backend/go-app/codegen.go @@ -4,17 +4,19 @@ import ( "archive/zip" "bytes" "context" + "encoding/json" "errors" "fmt" "io" "io/ioutil" "log" "os" + "strconv" "strings" "cloud.google.com/go/storage" "github.com/getkin/kin-openapi/openapi3" - "github.com/satori/go.uuid" + //"github.com/satori/go.uuid" "gopkg.in/yaml.v2" ) @@ -277,7 +279,13 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "header" { authenticationSetup = fmt.Sprintf("headers[\"%s\"] = apikey", swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name) } else if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "query" { - authenticationSetup = fmt.Sprintf("url+=f\"?%s={apikey}\"", swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name) + // This might suck lol + key := "?" + if strings.Contains(url, "?") { + key = "&" + } + + authenticationSetup = fmt.Sprintf("url+=f\"%s%s={apikey}\"", key, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name) } } } @@ -306,46 +314,75 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet bodyParameter := "" bodyAddin := "" + bodyFormatter := "" postParameters := []string{"post", "patch", "put"} for _, item := range postParameters { if method == item { bodyParameter = ", body=\"\"" - bodyAddin = ", json=body" + bodyAddin = ", data=body" + + // FIXME: Does JSON data work? + bodyFormatter = ` + if (body.startswith("{") and body.endswith("}")) or (body.startswith("[") and body.endswith("]")): + try: + body = json.dumps(body) + except: + pass + ` break } } // Extra param for url if it's changeable // Extra param for authentication scheme(s) - data := fmt.Sprintf(` async def %s(self%s%s%s%s%s): headers={} url=f"%s%s" %s %s + %s return requests.%s(url, headers=headers%s%s).text - `, functionname, authenticationParameter, urlParameter, parameterData, queryString, bodyParameter, urlInline, url, authenticationSetup, queryData, method, authenticationAddin, bodyAddin) + `, + functionname, + authenticationParameter, + urlParameter, + parameterData, + queryString, + bodyParameter, + urlInline, + url, + authenticationSetup, + queryData, + bodyFormatter, + method, + authenticationAddin, + bodyAddin, + ) //log.Println(data) //log.Println(functionname) return functionname, data } -func generateYaml(swagger *openapi3.Swagger, newmd5 string) (WorkflowApp, []string, error) { +func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, WorkflowApp, []string, error) { api := WorkflowApp{} //log.Printf("%#v", swagger.Info) if len(swagger.Info.Title) == 0 { - return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.") + return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.") } if len(swagger.Servers) == 0 { - return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") + return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'") } api.Name = swagger.Info.Title api.Description = swagger.Info.Description - api.ID = uuid.NewV4().String() + + // FIXME: Versioning issue? + api.ID = newmd5 + //uuid.NewV4().String() + api.IsValid = true api.Link = swagger.Servers[0].URL // host doesnt exist lol if strings.HasSuffix(api.Link, "/") { @@ -365,6 +402,20 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (WorkflowApp, []stri // Setting up security schemes extraParameters := []WorkflowAppActionParameter{} + if val, ok := swagger.Info.ExtensionProps.Extensions["x-logo"]; ok { + j, err := json.Marshal(&val) + if err == nil { + if j[0] == 0x22 && j[len(j)-1] == 0x22 { + j = j[1 : len(j)-1] + } + + //log.Printf("%s", j) + api.SmallImage = string(j) + api.LargeImage = string(j) + log.Printf("Set images!") + } + } + securitySchemes := swagger.Components.SecuritySchemes if securitySchemes != nil { //log.Printf("%#v", securitySchemes) @@ -456,53 +507,57 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (WorkflowApp, []stri // This is the python code to be generated // Could just as well be go at this point lol pythonFunctions := []string{} - for actualPath, path := range swagger.Paths { - - //log.Printf("%#v", path) - //log.Printf("%#v", actualPath) - // FIXME: Add everything from here: // https://godoc.org/github.com/getkin/kin-openapi/openapi3#PathItem - firstQuery := true if path.Get != nil { - action, curCode := handleGet(swagger, api, extraParameters, path, actualPath, firstQuery) + action, curCode := handleGet(swagger, api, extraParameters, path, actualPath) api.Actions = append(api.Actions, action) pythonFunctions = append(pythonFunctions, curCode) } if path.Connect != nil { - action, curCode := handleConnect(swagger, api, extraParameters, path, actualPath, firstQuery) + action, curCode := handleConnect(swagger, api, extraParameters, path, actualPath) api.Actions = append(api.Actions, action) pythonFunctions = append(pythonFunctions, curCode) } if path.Head != nil { - action, curCode := handleHead(swagger, api, extraParameters, path, actualPath, firstQuery) + action, curCode := handleHead(swagger, api, extraParameters, path, actualPath) api.Actions = append(api.Actions, action) pythonFunctions = append(pythonFunctions, curCode) } if path.Delete != nil { - action, curCode := handleDelete(swagger, api, extraParameters, path, actualPath, firstQuery) + action, curCode := handleDelete(swagger, api, extraParameters, path, actualPath) api.Actions = append(api.Actions, action) pythonFunctions = append(pythonFunctions, curCode) } if path.Post != nil { - action, curCode := handlePost(swagger, api, extraParameters, path, actualPath, firstQuery) + action, curCode := handlePost(swagger, api, extraParameters, path, actualPath) api.Actions = append(api.Actions, action) pythonFunctions = append(pythonFunctions, curCode) } if path.Patch != nil { - action, curCode := handlePatch(swagger, api, extraParameters, path, actualPath, firstQuery) + action, curCode := handlePatch(swagger, api, extraParameters, path, actualPath) api.Actions = append(api.Actions, action) pythonFunctions = append(pythonFunctions, curCode) } if path.Put != nil { - action, curCode := handlePut(swagger, api, extraParameters, path, actualPath, firstQuery) + action, curCode := handlePut(swagger, api, extraParameters, path, actualPath) api.Actions = append(api.Actions, action) pythonFunctions = append(pythonFunctions, curCode) } + + // Has to be here because its used differently above. + // FIXING this is done during export instead? + //log.Printf("OLDPATH: %s", actualPath) + //if strings.Contains(actualPath, "?") { + // actualPath = strings.Split(actualPath, "?")[0] + //} + + //log.Printf("NEWPATH: %s", actualPath) + //newPaths[actualPath] = path } - return api, pythonFunctions, nil + return swagger, api, pythonFunctions, nil } // FIXME - have this give a real version? @@ -637,6 +692,7 @@ func getRunner(classname string) string { return fmt.Sprintf(` # Run the actual thing after we've checked params def run(request): + print("Started execution!") action = request.get_json() print(action) print(type(action)) @@ -679,7 +735,7 @@ func fixFunctionName(functionName, actualPath string) string { return functionName } -func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { +func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string) (WorkflowAppAction, string) { // What to do with this, hmm functionName := fixFunctionName(path.Connect.Summary, actualPath) @@ -699,13 +755,13 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [ // Parameters: []WorkflowAppActionParameter{}, // FIXME - add data for POST stuff - firstQuery = true + firstQuery := true optionalQueries := []string{} parameters := []string{} optionalParameters := []WorkflowAppActionParameter{} if len(path.Connect.Parameters) > 0 { for _, param := range path.Connect.Parameters { - if param.Value.Schema == nil { + if param.Value.Schema == nil || param.Value.In == "header" { continue } curParam := WorkflowAppActionParameter{ @@ -718,6 +774,24 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [ }, } + // FIXME: Example & Multiline + if param.Value.Example != nil { + curParam.Example = param.Value.Example.(string) + + if param.Value.Name == "body" { + curParam.Value = param.Value.Example.(string) + } + } + if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok { + j, err := json.Marshal(&val) + if err == nil { + b, err := strconv.ParseBool(string(j)) + if err == nil { + curParam.Multiline = b + } + } + } + if param.Value.Required { action.Parameters = append(action.Parameters, curParam) } else { @@ -737,13 +811,16 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [ parameters = append(parameters, param.Value.Name) + if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) { + continue + } + if firstQuery { baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } else { baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } + firstQuery = false } } @@ -764,7 +841,7 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [ return action, curCode } -func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { +func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string) (WorkflowAppAction, string) { // What to do with this, hmm functionName := fixFunctionName(path.Get.Summary, actualPath) @@ -784,7 +861,7 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor // Parameters: []WorkflowAppActionParameter{}, // FIXME - add data for POST stuff - firstQuery = true + firstQuery := true optionalQueries := []string{} // FIXME - remove this when authentication is properly introduced @@ -793,8 +870,7 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor optionalParameters := []WorkflowAppActionParameter{} if len(path.Get.Parameters) > 0 { for _, param := range path.Get.Parameters { - //log.Printf("TYPE: %#v", param.Value.Schema) - if param.Value.Schema == nil { + if param.Value.Schema == nil || param.Value.In == "header" { continue } @@ -808,6 +884,24 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor }, } + // FIXME: Example & Multiline + if param.Value.Example != nil { + curParam.Example = param.Value.Example.(string) + + if param.Value.Name == "body" { + curParam.Value = param.Value.Example.(string) + } + } + if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok { + j, err := json.Marshal(&val) + if err == nil { + b, err := strconv.ParseBool(string(j)) + if err == nil { + curParam.Multiline = b + } + } + } + if param.Value.Required { action.Parameters = append(action.Parameters, curParam) } else { @@ -827,13 +921,17 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor parameters = append(parameters, param.Value.Name) + // Skipping simial + if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) { + continue + } + if firstQuery { baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } else { baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } + firstQuery = false } } @@ -854,7 +952,7 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor return action, curCode } -func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { +func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string) (WorkflowAppAction, string) { // What to do with this, hmm functionName := fixFunctionName(path.Head.Summary, actualPath) @@ -874,13 +972,13 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo // Parameters: []WorkflowAppActionParameter{}, // FIXME - add data for POST stuff - firstQuery = true + firstQuery := true optionalQueries := []string{} parameters := []string{} optionalParameters := []WorkflowAppActionParameter{} if len(path.Head.Parameters) > 0 { for _, param := range path.Head.Parameters { - if param.Value.Schema == nil { + if param.Value.Schema == nil || param.Value.In == "header" { continue } curParam := WorkflowAppActionParameter{ @@ -893,6 +991,24 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo }, } + // FIXME: Example & Multiline + if param.Value.Example != nil { + curParam.Example = param.Value.Example.(string) + + if param.Value.Name == "body" { + curParam.Value = param.Value.Example.(string) + } + } + if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok { + j, err := json.Marshal(&val) + if err == nil { + b, err := strconv.ParseBool(string(j)) + if err == nil { + curParam.Multiline = b + } + } + } + if param.Value.Required { action.Parameters = append(action.Parameters, curParam) } else { @@ -912,13 +1028,16 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo parameters = append(parameters, param.Value.Name) + if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) { + continue + } + if firstQuery { baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } else { baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } + firstQuery = false } } @@ -939,7 +1058,7 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo return action, curCode } -func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { +func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string) (WorkflowAppAction, string) { // What to do with this, hmm functionName := fixFunctionName(path.Delete.Summary, actualPath) @@ -959,13 +1078,13 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [] // Parameters: []WorkflowAppActionParameter{}, // FIXME - add data for POST stuff - firstQuery = true + firstQuery := true optionalQueries := []string{} parameters := []string{} optionalParameters := []WorkflowAppActionParameter{} if len(path.Delete.Parameters) > 0 { for _, param := range path.Delete.Parameters { - if param.Value.Schema == nil { + if param.Value.Schema == nil || param.Value.In == "header" { continue } curParam := WorkflowAppActionParameter{ @@ -978,6 +1097,24 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [] }, } + // FIXME: Example & Multiline + if param.Value.Example != nil { + curParam.Example = param.Value.Example.(string) + + if param.Value.Name == "body" { + curParam.Value = param.Value.Example.(string) + } + } + if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok { + j, err := json.Marshal(&val) + if err == nil { + b, err := strconv.ParseBool(string(j)) + if err == nil { + curParam.Multiline = b + } + } + } + if param.Value.Required { action.Parameters = append(action.Parameters, curParam) } else { @@ -997,13 +1134,16 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [] parameters = append(parameters, param.Value.Name) + if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) { + continue + } + if firstQuery { baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } else { baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } + firstQuery = false } } @@ -1024,7 +1164,7 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [] return action, curCode } -func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { +func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string) (WorkflowAppAction, string) { // What to do with this, hmm //log.Printf("PATH: %s", actualPath) functionName := fixFunctionName(path.Post.Summary, actualPath) @@ -1038,33 +1178,26 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo Parameters: extraParameters, } + if path.Post.RequestBody != nil { + log.Printf("RequestBody: %#v", path.Post.RequestBody) + } + action.Returns.Schema.Type = "string" baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath) - //log.Println(path.Parameters) - // Parameters: []WorkflowAppActionParameter{}, // FIXME - add data for POST stuff - firstQuery = true + firstQuery := true optionalQueries := []string{} parameters := []string{} - optionalParameters := []WorkflowAppActionParameter{ - WorkflowAppActionParameter{ - Name: "body", - Description: "The body to use", - Multiline: true, - Required: false, - Example: `{"username": "test"}`, - Schema: SchemaDefinition{ - Type: "string", - }, - }, - } + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Post.Parameters) > 0 { for _, param := range path.Post.Parameters { - if param.Value.Schema == nil { + if param.Value.Schema == nil || param.Value.In == "header" { continue } + curParam := WorkflowAppActionParameter{ Name: param.Value.Name, Description: param.Value.Description, @@ -1075,6 +1208,24 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo }, } + // FIXME: Example & Multiline + if param.Value.Example != nil { + curParam.Example = param.Value.Example.(string) + + if param.Value.Name == "body" { + curParam.Value = param.Value.Example.(string) + } + } + if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok { + j, err := json.Marshal(&val) + if err == nil { + b, err := strconv.ParseBool(string(j)) + if err == nil { + curParam.Multiline = b + } + } + } + if param.Value.Required { action.Parameters = append(action.Parameters, curParam) } else { @@ -1094,13 +1245,16 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo parameters = append(parameters, param.Value.Name) + if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) { + continue + } + if firstQuery { baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } else { baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } + firstQuery = false } } @@ -1121,7 +1275,7 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo return action, curCode } -func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { +func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string) (WorkflowAppAction, string) { // What to do with this, hmm functionName := fixFunctionName(path.Patch.Summary, actualPath) @@ -1141,24 +1295,13 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W // Parameters: []WorkflowAppActionParameter{}, // FIXME - add data for POST stuff - firstQuery = true + firstQuery := true optionalQueries := []string{} parameters := []string{} - optionalParameters := []WorkflowAppActionParameter{ - WorkflowAppActionParameter{ - Name: "body", - Description: "The body to use", - Multiline: true, - Required: false, - Example: `{"username": "test"}`, - Schema: SchemaDefinition{ - Type: "string", - }, - }, - } + optionalParameters := []WorkflowAppActionParameter{} if len(path.Patch.Parameters) > 0 { for _, param := range path.Patch.Parameters { - if param.Value.Schema == nil { + if param.Value.Schema == nil || param.Value.In == "header" { continue } curParam := WorkflowAppActionParameter{ @@ -1171,6 +1314,24 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W }, } + // FIXME: Example & Multiline + if param.Value.Example != nil { + curParam.Example = param.Value.Example.(string) + + if param.Value.Name == "body" { + curParam.Value = param.Value.Example.(string) + } + } + if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok { + j, err := json.Marshal(&val) + if err == nil { + b, err := strconv.ParseBool(string(j)) + if err == nil { + curParam.Multiline = b + } + } + } + if param.Value.Required { action.Parameters = append(action.Parameters, curParam) } else { @@ -1190,13 +1351,16 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W parameters = append(parameters, param.Value.Name) + if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) { + continue + } + if firstQuery { baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } else { baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } + firstQuery = false } } @@ -1217,7 +1381,7 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W return action, curCode } -func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, firstQuery bool) (WorkflowAppAction, string) { +func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string) (WorkflowAppAction, string) { // What to do with this, hmm functionName := fixFunctionName(path.Put.Summary, actualPath) @@ -1237,24 +1401,14 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor // Parameters: []WorkflowAppActionParameter{}, // FIXME - add data for POST stuff - firstQuery = true + firstQuery := true optionalQueries := []string{} parameters := []string{} - optionalParameters := []WorkflowAppActionParameter{ - WorkflowAppActionParameter{ - Name: "body", - Description: "The body to use", - Multiline: true, - Required: false, - Example: `{"username": "test"}`, - Schema: SchemaDefinition{ - Type: "string", - }, - }, - } + optionalParameters := []WorkflowAppActionParameter{} + if len(path.Put.Parameters) > 0 { for _, param := range path.Put.Parameters { - if param.Value.Schema == nil { + if param.Value.Schema == nil || param.Value.In == "header" { continue } curParam := WorkflowAppActionParameter{ @@ -1267,6 +1421,24 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor }, } + // FIXME: Example & Multiline + if param.Value.Example != nil { + curParam.Example = param.Value.Example.(string) + + if param.Value.Name == "body" { + curParam.Value = param.Value.Example.(string) + } + } + if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok { + j, err := json.Marshal(&val) + if err == nil { + b, err := strconv.ParseBool(string(j)) + if err == nil { + curParam.Multiline = b + } + } + } + if param.Value.Required { action.Parameters = append(action.Parameters, curParam) } else { @@ -1286,13 +1458,16 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor parameters = append(parameters, param.Value.Name) + if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) { + continue + } + if firstQuery { baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } else { baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name) - firstQuery = false } + firstQuery = false } } diff --git a/backend/go-app/main.go b/backend/go-app/main.go index dc73b5c4..bac7e31a 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1906,6 +1906,7 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { if err != nil { resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return } if count == 0 { @@ -2112,7 +2113,6 @@ func SetSession(ctx context.Context, Userdata User, value string) error { func setOpenApiDatastore(ctx context.Context, id string, data ParsedOpenApi) error { k := datastore.NameKey("openapi3", id, nil) if _, err := dbclient.Put(ctx, k, &data); err != nil { - log.Println(err) return err } @@ -5075,6 +5075,8 @@ func getOpenapi(resp http.ResponseWriter, request *http.Request) { return } + log.Printf("API LENGTH GET: %d, ID: %s", len(parsedApi.Body), id) + parsedApi.Success = true data, err := json.Marshal(parsedApi) if err != nil { @@ -5505,13 +5507,6 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) { // Test = client side with fetch? ctx := context.Background() - //client, err := storage.NewClient(ctx) - //if err != nil { - // log.Printf("Failed to create client (storage): %v", err) - // resp.WriteHeader(401) - // resp.Write([]byte(`{"success": false, "reason": "Failed creating client"}`)) - // return - //} swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(body) if err != nil { @@ -5534,7 +5529,7 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) { } //log.Printf("Should generate yaml") - api, pythonfunctions, err := generateYaml(swagger, newmd5) + swagger, api, pythonfunctions, err := generateYaml(swagger, newmd5) if err != nil { log.Printf("Failed building and generating yaml: %s", err) resp.WriteHeader(500) @@ -5542,12 +5537,28 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) { return } - api.Owner = user.Id - if len(test.Image) > 0 { - api.SmallImage = test.Image - api.LargeImage = test.Image + // FIXME: CHECK IF SAME NAME AS NORMAL APP + // Can't overwrite existing normal app + workflowApps, err := getAllWorkflowApps(ctx) + if err != nil { + log.Printf("Failed getting all workflow apps from database to verify: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Failed to verify existence"}`)) + return } + // Same name only? + lowerName := strings.ToLower(swagger.Info.Title) + for _, app := range workflowApps { + if app.Downloaded && !app.Generated && strings.ToLower(app.Name) == lowerName { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Normal app with name %s already exists. Delete it first."}`, swagger.Info.Title))) + return + } + } + + api.Owner = user.Id + err = dumpApi(basePath, api) if err != nil { log.Printf("Failed dumping yaml: %s", err) @@ -5569,7 +5580,7 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) { identifier = strings.Replace(identifier, " ", "-", -1) identifier = strings.Replace(identifier, "_", "-", -1) - log.Printf("Successfully uploaded %s to bucket. Proceeding to cloud function", identifier) + log.Printf("Successfully parsed %s. Proceeding to docker container", identifier) // Now that the baseline is setup, we need to make it into a cloud function // 1. Upload the API to datastore for use @@ -5684,24 +5695,38 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) { return } + log.Printf("DO I REACH HERE WHEN SAVING?") parsed := ParsedOpenApi{ - ID: api.ID, + ID: newmd5, Body: string(body), } + log.Printf("API LENGTH: %d, ID: %s", len(parsed.Body), newmd5) + // FIXME: Might cause versioning issues if we re-use the same!! + // FIXME: Need a way to track different versions of the same app properly. + // Hint: Save API.id somewhere, and use newmd5 to save latest version + err = setOpenApiDatastore(ctx, newmd5, parsed) + if err != nil { + log.Printf("Failed saving to datastore: %s", err) + resp.WriteHeader(500) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%"}`, err))) + } + + // Backup every single one setOpenApiDatastore(ctx, api.ID, parsed) - err = increaseStatisticsField(ctx, "total_apps_created", api.ID, 1) + + err = increaseStatisticsField(ctx, "total_apps_created", newmd5, 1) if err != nil { log.Printf("Failed to increase success execution stats: %s", err) } - err = increaseStatisticsField(ctx, "openapi_apps_created", api.ID, 1) + err = increaseStatisticsField(ctx, "openapi_apps_created", newmd5, 1) if err != nil { log.Printf("Failed to increase success execution stats: %s", err) } resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "id": "%s"}`, api.ID))) } func healthCheckHandler(resp http.ResponseWriter, request *http.Request) { @@ -5748,10 +5773,10 @@ func runInit(ctx context.Context) { } } + //log.Printf("Schedule time: every %d seconds", schedule.Seconds) jobret, err := newscheduler.Every(schedule.Seconds).Seconds().NotImmediately().Run(job) if err != nil { log.Printf("Failed to schedule workflow: %s", err) - // FIXME: what now? lol:w } scheduledJobs[schedule.Id] = jobret @@ -5837,7 +5862,7 @@ func init() { log.Printf("Running INIT process") dbclient, err = datastore.NewClient(ctx, gceProject) if err != nil { - log.Printf("DBclient error during init: %s", err) + panic(fmt.Sprintf("DBclient error during init: %s", err)) } go runInit(ctx) diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 07ae2c18..5ba6c7f5 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1360,10 +1360,11 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { break } - if app.Name == action.AppName && app.AppVersion == action.AppVersion { - curapp = app - break - } + // Has to NOT be generated + //if app.Name == action.AppName && app.AppVersion == action.AppVersion { + // curapp = app + // break + //} } // Check to see if the whole app is valid @@ -1381,7 +1382,6 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { curappaction = curAction break } - log.Println(action.Name, curAction.Name) } // Check to see if the action is valid @@ -2340,7 +2340,16 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("Schedulearg: %s", string(scheduleArg)) + // Clean up garbage. This might be wrong in some very specific use-cases + parsedBody := string(scheduleArg) + parsedBody = strings.Replace(parsedBody, "\\\"", "\"", -1) + if len(parsedBody) > 0 { + if string(parsedBody[0]) == `"` && string(parsedBody[len(parsedBody)-1]) == "\"" { + parsedBody = parsedBody[1 : len(parsedBody)-1] + } + } + + log.Printf("Schedulearg: %s", parsedBody) err = createSchedule( ctx, @@ -2348,7 +2357,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.ID, schedule.Name, schedule.Frequency, - scheduleArg, + []byte(parsedBody), ) // FIXME - real error message lol @@ -2736,6 +2745,9 @@ func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) { return } + //log.Printf("%#v", parsedApi) + log.Printf("API LEN: %d, ID: %s", len(parsedApi.Body), fileId) + //log.Printf("Parsed API: %#v", parsedApi) if len(parsedApi.ID) > 0 { parsedApi.Success = true @@ -3325,7 +3337,6 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, // Check the file filename := file.Name() if strings.Contains(filename, "yaml") || strings.Contains(filename, "yml") { - appCounter += 1 //log.Printf("File: %s", filename) //log.Printf("Found file: %s", filename) tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name()) @@ -3365,7 +3376,7 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, } //log.Printf("Should generate yaml") - api, _, err := generateYaml(swagger, parsedOpenApi.ID) + swagger, api, _, err := generateYaml(swagger, parsedOpenApi.ID) if err != nil { log.Printf("Failed building and generating yaml in loop (%s): %s", filename, err) continue @@ -3395,6 +3406,7 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, log.Printf("Failed setting workflowapp in loop: %s", err) continue } else { + appCounter += 1 log.Printf("Added %s:%s to the database from OpenAPI repo", api.Name, api.AppVersion) // Set OpenAPI datastore diff --git a/docker-compose.yml b/docker-compose.yml index e04cdbbe..0fbc7c41 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: - shuffle restart: unless-stopped volumes: - - /etc/shuffle:/etc/shuffle + - ${DB_LOCATION}:/etc/shuffle backend: #build: ./backend image: frikky/shuffle:backend diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 94f34ac5..a249ae8d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -960,9 +960,9 @@ "integrity": "sha512-b0JQb10Lie07iW2/9uKCQSrXif262d6zfYBstCLLJUk0JVA+7o/yLDg5p2+GkjgJbmodjHozIXs4Bi34RRhL8Q==" }, "@emotion/hash": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.3.tgz", - "integrity": "sha512-14ZVlsB9akwvydAdaEnVnvqu6J2P6ySv39hYyl/aoB6w/V+bXX0tay8cF6paqbgZsN2n5Xh15uF4pE+GvE+itw==" + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, "@emotion/is-prop-valid": { "version": "0.8.3", @@ -988,37 +988,87 @@ "integrity": "sha512-XiUPoS79r1G7PcpnNtq85TJ7inJWe0v+b5oZJZKb0pGHNIV6+UiNeQWiFGmuQ0aj7GEhnD/v9iqxIsjuRKtEnQ==" }, "@material-ui/core": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz", - "integrity": "sha512-REIj62+zEvTgI/C//YL4fZxrCVIySygmpZglsu/Nl5jPqy3CDjZv1F9ubBYorHqmRgeVPh64EghMMWqk4egmfg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.10.0.tgz", + "integrity": "sha512-yVlHe4b8AaoiTHhCOZeszHZ+T2iHU5DncdMGeNcQaaaO+q/Qrq0hxP3iFzTbgjRWnWwftEVQL668GRxcPJVRaQ==", "requires": { - "@babel/runtime": "^7.2.0", - "@material-ui/system": "^3.0.0-alpha.0", - "@material-ui/utils": "^3.0.0-alpha.2", - "@types/jss": "^9.5.6", - "@types/react-transition-group": "^2.0.8", - "brcast": "^3.0.1", - "classnames": "^2.2.5", - "csstype": "^2.5.2", - "debounce": "^1.1.0", - "deepmerge": "^3.0.0", - "dom-helpers": "^3.2.1", - "hoist-non-react-statics": "^3.2.1", - "is-plain-object": "^2.0.4", - "jss": "^9.8.7", - "jss-camel-case": "^6.0.0", - "jss-default-unit": "^8.0.2", - "jss-global": "^3.0.0", - "jss-nested": "^6.0.1", - "jss-props-sort": "^6.0.0", - "jss-vendor-prefixer": "^7.0.0", - "normalize-scroll-left": "^0.1.2", - "popper.js": "^1.14.1", - "prop-types": "^15.6.0", - "react-event-listener": "^0.6.2", - "react-transition-group": "^2.2.1", - "recompose": "0.28.0 - 0.30.0", - "warning": "^4.0.1" + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.10.0", + "@material-ui/system": "^4.9.14", + "@material-ui/types": "^5.1.0", + "@material-ui/utils": "^4.9.12", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.4", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "^1.16.1-lts", + "prop-types": "^15.7.2", + "react-is": "^16.8.0", + "react-transition-group": "^4.4.0" + }, + "dependencies": { + "csstype": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", + "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" + }, + "dom-helpers": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", + "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^2.6.7" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.1.tgz", + "integrity": "sha512-nQbbCbQc9u/rpg1XCxoMYQTbSMVZjCDxErQ1ClCn9Pvcmv1lGads19ep0a2VsEiIJeHqjZley6EQGEC3Yo1xMA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, + "react-transition-group": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.1.tgz", + "integrity": "sha512-nQbbCbQc9u/rpg1XCxoMYQTbSMVZjCDxErQ1ClCn9Pvcmv1lGads19ep0a2VsEiIJeHqjZley6EQGEC3Yo1xMA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } } }, "@material-ui/icons": { @@ -1030,84 +1080,62 @@ } }, "@material-ui/styles": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", - "integrity": "sha512-O0NSAECHK9f3DZK6wy56PZzp8b/7KSdfpJs8DSC7vnXUAoMPCTtchBKLzMtUsNlijiJFeJjSxNdQfjWXgyur5A==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz", + "integrity": "sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==", "requires": { "@babel/runtime": "^7.4.4", - "@emotion/hash": "^0.7.1", - "@material-ui/types": "^4.1.1", - "@material-ui/utils": "^4.1.0", - "clsx": "^1.0.2", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "^5.1.0", + "@material-ui/utils": "^4.9.6", + "clsx": "^1.0.4", "csstype": "^2.5.2", - "deepmerge": "^4.0.0", - "hoist-non-react-statics": "^3.2.1", - "jss": "^10.0.0", - "jss-plugin-camel-case": "^10.0.0", - "jss-plugin-default-unit": "^10.0.0", - "jss-plugin-global": "^10.0.0", - "jss-plugin-nested": "^10.0.0", - "jss-plugin-props-sort": "^10.0.0", - "jss-plugin-rule-value-function": "^10.0.0", - "jss-plugin-vendor-prefixer": "^10.0.0", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.0.3", + "jss-plugin-camel-case": "^10.0.3", + "jss-plugin-default-unit": "^10.0.3", + "jss-plugin-global": "^10.0.3", + "jss-plugin-nested": "^10.0.3", + "jss-plugin-props-sort": "^10.0.3", + "jss-plugin-rule-value-function": "^10.0.3", + "jss-plugin-vendor-prefixer": "^10.0.3", "prop-types": "^15.7.2" }, "dependencies": { - "@material-ui/utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.4.0.tgz", - "integrity": "sha512-UXoQVwArQEQWXxf2FPs0iJGT+MePQpKr0Qh0CPoLc1OdF0GSMTmQczcqCzwZkeHxHAOq/NkIKM1Pb/ih1Avicg==", + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "requires": { - "@babel/runtime": "^7.4.4", - "prop-types": "^15.7.2", - "react-is": "^16.8.6" - } - }, - "deepmerge": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.0.tgz", - "integrity": "sha512-/pED+kD8V9n15L1lon8DXEiWLQMW4tTiegn1kIWIQ+DBudOkFitz1cfjWQiSeKMPBQOknT3LpueyAmMVJ1Ho2g==" - }, - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" + "react-is": "^16.7.0" } } } }, "@material-ui/system": { - "version": "3.0.0-alpha.2", - "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz", - "integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==", + "version": "4.9.14", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz", + "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==", "requires": { - "@babel/runtime": "^7.2.0", - "deepmerge": "^3.0.0", - "prop-types": "^15.6.0", - "warning": "^4.0.1" + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.9.6", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" } }, "@material-ui/types": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-4.1.1.tgz", - "integrity": "sha512-AN+GZNXytX9yxGi0JOfxHrRTbhFybjUJ05rnsBVjcB+16e466Z0Xe5IxawuOayVZgTBNDxmPKo5j4V6OnMtaSQ==", - "requires": { - "@types/react": "*" - } + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==" }, "@material-ui/utils": { - "version": "3.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz", - "integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==", + "version": "4.9.12", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.9.12.tgz", + "integrity": "sha512-/0rgZPEOcZq5CFA4+4n6Q6zk7fi8skHhH2Bcra8R3epoJEYy5PL55LuMazPtPH1oKeRausDV/Omz4BbgFsn1HQ==", "requires": { - "@babel/runtime": "^7.2.0", - "prop-types": "^15.6.0", - "react-is": "^16.6.3" + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" } }, "@mrmlnc/readdir-enhanced": { @@ -1337,9 +1365,9 @@ } }, "@types/react-transition-group": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.2.tgz", - "integrity": "sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz", + "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==", "requires": { "@types/react": "*" } @@ -4788,11 +4816,27 @@ "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=" }, "css-vendor": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz", - "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", "requires": { + "@babel/runtime": "^7.8.3", "is-in-browser": "^1.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.1.tgz", + "integrity": "sha512-nQbbCbQc9u/rpg1XCxoMYQTbSMVZjCDxErQ1ClCn9Pvcmv1lGads19ep0a2VsEiIJeHqjZley6EQGEC3Yo1xMA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + } } }, "css-what": { @@ -5504,9 +5548,9 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" }, "deepmerge": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.2.1.tgz", - "integrity": "sha512-+hbDSzTqEW0fWgnlKksg7XAOtT+ddZS5lHZJ6f6MdixRs9wQy+50fm1uUCVb1IkvjLUYX/SfFO021ZNwriURTw==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" }, "default-gateway": { "version": "2.7.2", @@ -11052,23 +11096,14 @@ } }, "jss": { - "version": "9.8.7", - "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz", - "integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.1.1.tgz", + "integrity": "sha512-Xz3qgRUFlxbWk1czCZibUJqhVPObrZHxY3FPsjCXhDld4NOj1BgM14Ir5hVm+Qr6OLqVljjGvoMcCdXNOAbdkQ==", "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^2.6.5", "is-in-browser": "^1.1.3", - "symbol-observable": "^1.1.0", - "warning": "^3.0.0" - }, - "dependencies": { - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } + "tiny-warning": "^1.0.2" } }, "jss-camel-case": { @@ -11108,179 +11143,69 @@ } }, "jss-plugin-camel-case": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0.tgz", - "integrity": "sha512-yALDL00+pPR4FJh+k07A8FeDvfoPPuXU48HLy63enAubcVd3DnS+2rgqPXglHDGixIDVkCSXecl/l5GAMjzIbA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.1.1.tgz", + "integrity": "sha512-MDIaw8FeD5uFz1seQBKz4pnvDLnj5vIKV5hXSVdMaAVq13xR6SVTVWkIV/keyTs5txxTvzGJ9hXoxgd1WTUlBw==", "requires": { "@babel/runtime": "^7.3.1", "hyphenate-style-name": "^1.0.3", - "jss": "10.0.0" - }, - "dependencies": { - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - } + "jss": "10.1.1" } }, "jss-plugin-default-unit": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0.tgz", - "integrity": "sha512-sURozIOdCtGg9ap18erQ+ijndAfEGtTaetxfU3H4qwC18Bi+fdvjlY/ahKbuu0ASs7R/+WKCP7UaRZOjUDMcdQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.1.1.tgz", + "integrity": "sha512-UkeVCA/b3QEA4k0nIKS4uWXDCNmV73WLHdh2oDGZZc3GsQtlOCuiH3EkB/qI60v2MiCq356/SYWsDXt21yjwdg==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" - }, - "dependencies": { - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - } + "jss": "10.1.1" } }, "jss-plugin-global": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0.tgz", - "integrity": "sha512-80ofWKSQUo62bxLtRoTNe0kFPtHgUbAJeOeR36WEGgWIBEsXLyXOnD5KNnjPqG4heuEkz9eSLccjYST50JnI7Q==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.1.1.tgz", + "integrity": "sha512-VBG3wRyi3Z8S4kMhm8rZV6caYBegsk+QnQZSVmrWw6GVOT/Z4FA7eyMu5SdkorDlG/HVpHh91oFN56O4R9m2VA==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" - }, - "dependencies": { - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - } + "jss": "10.1.1" } }, "jss-plugin-nested": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0.tgz", - "integrity": "sha512-waxxwl/po1hN3azTyixKnr8ReEqUv5WK7WsO+5AWB0bFndML5Yqnt8ARZ90HEg8/P6WlqE/AB2413TkCRZE8bA==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.1.1.tgz", + "integrity": "sha512-ozEu7ZBSVrMYxSDplPX3H82XHNQk2DQEJ9TEyo7OVTPJ1hEieqjDFiOQOxXEj9z3PMqkylnUbvWIZRDKCFYw5Q==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0", + "jss": "10.1.1", "tiny-warning": "^1.0.2" - }, - "dependencies": { - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - } } }, "jss-plugin-props-sort": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0.tgz", - "integrity": "sha512-41mf22CImjwNdtOG3r+cdC8+RhwNm616sjHx5YlqTwtSJLyLFinbQC/a4PIFk8xqf1qpFH1kEAIw+yx9HaqZ3g==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.1.1.tgz", + "integrity": "sha512-g/joK3eTDZB4pkqpZB38257yD4LXB0X15jxtZAGbUzcKAVUHPl9Jb47Y7lYmiGsShiV4YmQRqG1p2DHMYoK91g==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" - }, - "dependencies": { - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - } + "jss": "10.1.1" } }, "jss-plugin-rule-value-function": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0.tgz", - "integrity": "sha512-Jw+BZ8JIw1f12V0SERqGlBT1JEPWax3vuZpMym54NAXpPb7R1LYHiCTIlaJUyqvIfEy3kiHMtgI+r2whGgRIxQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.1.1.tgz", + "integrity": "sha512-ClV1lvJ3laU9la1CUzaDugEcwnpjPTuJ0yGy2YtcU+gG/w9HMInD5vEv7xKAz53Bk4WiJm5uLOElSEshHyhKNw==", "requires": { "@babel/runtime": "^7.3.1", - "jss": "10.0.0" - }, - "dependencies": { - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - } + "jss": "10.1.1" } }, "jss-plugin-vendor-prefixer": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0.tgz", - "integrity": "sha512-qslqvL0MUbWuzXJWdUxpj6mdNUX8jr4FFTo3aZnAT65nmzWL7g8oTr9ZxmTXXgdp7ANhS1QWE7036/Q2isFBpw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.1.1.tgz", + "integrity": "sha512-09MZpQ6onQrhaVSF6GHC4iYifQ7+4YC/tAP6D4ZWeZotvCMq1mHLqNKRIaqQ2lkgANjlEot2JnVi1ktu4+L4pw==", "requires": { "@babel/runtime": "^7.3.1", - "css-vendor": "^2.0.6", - "jss": "10.0.0" - }, - "dependencies": { - "css-vendor": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz", - "integrity": "sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==", - "requires": { - "@babel/runtime": "^7.6.2", - "is-in-browser": "^1.0.2" - }, - "dependencies": { - "@babel/runtime": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.3.tgz", - "integrity": "sha512-kq6anf9JGjW8Nt5rYfEuGRaEAaH1mkv3Bbu6rYvLOpPh/RusSJXuKPEAoZ7L7gybZkchE8+NV5g9vKF4AGAtsA==", - "requires": { - "regenerator-runtime": "^0.13.2" - } - } - } - }, - "jss": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0.tgz", - "integrity": "sha512-TPpDFsiBjuERiL+dFDq8QCdiF9oDasPcNqCKLGCo/qED3fNYOQ8PX2lZhknyTiAt3tZrfOFbb0lbQ9lTjPZxsQ==", - "requires": { - "@babel/runtime": "^7.3.1", - "csstype": "^2.6.5", - "is-in-browser": "^1.1.3", - "tiny-warning": "^1.0.2" - } - } + "css-vendor": "^2.0.7", + "jss": "10.1.1" } }, "jss-props-sort": { @@ -11294,6 +11219,16 @@ "integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==", "requires": { "css-vendor": "^0.3.8" + }, + "dependencies": { + "css-vendor": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz", + "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=", + "requires": { + "is-in-browser": "^1.0.2" + } + } } }, "jsx-ast-utils": { @@ -11753,6 +11688,102 @@ "react-transition-group": "4.0.1" }, "dependencies": { + "@material-ui/core": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.3.tgz", + "integrity": "sha512-REIj62+zEvTgI/C//YL4fZxrCVIySygmpZglsu/Nl5jPqy3CDjZv1F9ubBYorHqmRgeVPh64EghMMWqk4egmfg==", + "requires": { + "@babel/runtime": "^7.2.0", + "@material-ui/system": "^3.0.0-alpha.0", + "@material-ui/utils": "^3.0.0-alpha.2", + "@types/jss": "^9.5.6", + "@types/react-transition-group": "^2.0.8", + "brcast": "^3.0.1", + "classnames": "^2.2.5", + "csstype": "^2.5.2", + "debounce": "^1.1.0", + "deepmerge": "^3.0.0", + "dom-helpers": "^3.2.1", + "hoist-non-react-statics": "^3.2.1", + "is-plain-object": "^2.0.4", + "jss": "^9.8.7", + "jss-camel-case": "^6.0.0", + "jss-default-unit": "^8.0.2", + "jss-global": "^3.0.0", + "jss-nested": "^6.0.1", + "jss-props-sort": "^6.0.0", + "jss-vendor-prefixer": "^7.0.0", + "normalize-scroll-left": "^0.1.2", + "popper.js": "^1.14.1", + "prop-types": "^15.6.0", + "react-event-listener": "^0.6.2", + "react-transition-group": "^2.2.1", + "recompose": "0.28.0 - 0.30.0", + "warning": "^4.0.1" + }, + "dependencies": { + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, + "@material-ui/system": { + "version": "3.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.2.tgz", + "integrity": "sha512-odmxQ0peKpP7RQBQ8koly06YhsPzcoVib1vByVPBH4QhwqBXuYoqlCjt02846fYspAqkrWzjxnWUD311EBbxOA==", + "requires": { + "@babel/runtime": "^7.2.0", + "deepmerge": "^3.0.0", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + } + }, + "@material-ui/utils": { + "version": "3.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-3.0.0-alpha.3.tgz", + "integrity": "sha512-rwMdMZptX0DivkqBuC+Jdq7BYTXwqKai5G5ejPpuEDKpWzi1Oxp+LygGw329FrKpuKeiqpcymlqJTjmy+quWng==", + "requires": { + "@babel/runtime": "^7.2.0", + "prop-types": "^15.6.0", + "react-is": "^16.6.3" + } + }, + "@types/react-transition-group": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.9.2.tgz", + "integrity": "sha512-5Fv2DQNO+GpdPZcxp2x/OQG/H19A01WlmpjVD9cKvVFmoVLOZ9LvBgSWG6pSXIU4og5fgbvGPaCV5+VGkWAEHA==", + "requires": { + "@types/react": "*" + } + }, + "jss": { + "version": "9.8.7", + "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz", + "integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==", + "requires": { + "is-in-browser": "^1.1.3", + "symbol-observable": "^1.1.0", + "warning": "^3.0.0" + }, + "dependencies": { + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", @@ -18083,6 +18114,14 @@ "pure-color": "^1.2.0" } }, + "react-beforeunload": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-beforeunload/-/react-beforeunload-2.2.1.tgz", + "integrity": "sha512-qydpYQuGUaB1C5EP2EFN0YjPo9kgV10Z4suIZ79GIhp0DWNJxHyuR8BiTViOBltTIn9PwlpSqX4fE3uprsdqKA==", + "requires": { + "prop-types": "^15.7.2" + } + }, "react-chartjs-2": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.8.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index d9e800a9..a7f6e0b1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,9 +4,9 @@ "version": "0.3.0", "private": true, "dependencies": { - "@material-ui/core": "^3.9.3", + "@material-ui/core": "^4.5.2", "@material-ui/icons": "^4.5.1", - "@material-ui/styles": "^4.5.0", + "@material-ui/styles": "^4.5.2", "@use-it/interval": "^0.1.3", "class-transformer": "^0.2.0", "create-react-app": "^2.0.3", @@ -32,6 +32,7 @@ "react": "^16.10.2", "react-alert": "^5.5.0", "react-alert-template-basic": "^1.0.0", + "react-beforeunload": "^2.2.1", "react-chartjs-2": "^2.8.0", "react-cookie": "^4.0.1", "react-cytoscapejs": "^1.2.0", @@ -50,6 +51,7 @@ "react-router-dom": "^4.3.1", "react-scripts": "^2.1.8", "reactstrap": "^7.1.0", + "shellwords": "^0.1.1", "simplebar": "^4.2.3", "styled-components": "^4.4.0", "websocket": "^1.0.30", diff --git a/frontend/src/AngularWorkflow.js b/frontend/src/AngularWorkflow.js index bc9d99fe..86fb1b6c 100644 --- a/frontend/src/AngularWorkflow.js +++ b/frontend/src/AngularWorkflow.js @@ -4,6 +4,7 @@ import { useInterval } from 'react-powerhooks'; import uuid from "uuid"; import {Link} from 'react-router-dom'; +import { Prompt } from 'react-router' import TextField from '@material-ui/core/TextField'; import Drawer from '@material-ui/core/Drawer'; import Button from '@material-ui/core/Button'; @@ -28,6 +29,7 @@ import Checkbox from '@material-ui/core/Checkbox'; import Breadcrumbs from '@material-ui/core/Breadcrumbs'; import CircularProgress from '@material-ui/core/CircularProgress'; import ReactJson from 'react-json-view' +import { useBeforeunload } from 'react-beforeunload'; import DirectionsRunIcon from '@material-ui/icons/DirectionsRun'; import PolymerIcon from '@material-ui/icons/Polymer'; @@ -169,6 +171,9 @@ const AngularWorkflow = (props) => { const [update, setUpdate] = useState(""); const [workflowExecutions, setWorkflowExecutions] = React.useState([]); + const unloadText = 'Are you sure you want to leave?' + useBeforeunload(() => unloadText) + const [elements, setElements] = useState([]) const { start, stop } = useInterval({ duration: 2500, @@ -176,7 +181,7 @@ const AngularWorkflow = (props) => { callback: () => { fetchUpdates() } - }); + }) const getWorkflowExecution = (id) => { fetch(globalUrl+"/api/v1/workflows/"+id+"/executions", { @@ -798,16 +803,17 @@ const AngularWorkflow = (props) => { } const onUnselect = (event) => { - //console.log("Unselect?") console.time("UNSELECT") // FIXME - check if they have value before overriding like this for no reason. // Would save a lot of time (400~ ms -> 30ms) - //setSelectedActionName({}) + //console.log("ACTION: ", selectedAction) + //console.log("APP: ", selectedApp) setSelectedAction({}) - setSelectedApp({}) - setSelectedTrigger({}) - setSelectedEdge({}) + //setSelectedApp({}) + //setSelectedTrigger({}) + //setSelectedEdge({}) + // setSelectedTriggerIndex(-1) //setSelectedActionEnvironment({}) //setSelectedEdge({}) @@ -821,10 +827,15 @@ const AngularWorkflow = (props) => { } const onEdgeSelect = (event) => { + setRightSideBarOpen(true) + const triggercheck = workflow.triggers.find(trigger => trigger.id === event.target.data()["source"]) if (triggercheck === undefined) { setSelectedEdgeIndex(workflow.branches.findIndex(data => data.id === event.target.data()["id"])) setSelectedEdge(event.target.data()) + + setSelectedAction({}) + setSelectedTrigger({}) } else { alert.info("Can't edit branches from triggers") } @@ -837,7 +848,7 @@ const AngularWorkflow = (props) => { if (data.type === "ACTION") { // FIXME - unselect //console.log(cy.elements('[_id!="${data._id}"]`)) - console.time('ACTIONSTART'); + // Does it choose the wrong action? const curaction = workflow.actions.find(a => a.id === data.id) if (!curaction || curaction === undefined) { //console.log("Action not found error") @@ -855,25 +866,24 @@ const AngularWorkflow = (props) => { env = environments[0] } - console.log("Selected: ", data.id) - console.log(curaction) - - setRequiresAuthentication(curapp.authentication.required) setSelectedApp(curapp) + setSelectedAction(curaction) setSelectedActionEnvironment(env) setSelectedActionName(curaction.name) - setSelectedAction(curaction) + setRequiresAuthentication(curapp.authentication.required) } else if (data.type === "TRIGGER") { //console.log("Should handle trigger "+data.triggertype) //console.log(data) setSelectedTriggerIndex(workflow.triggers.findIndex(a => a.id === data.id)) setSelectedTrigger(data) - setSelectedActionEnvironment(data.env) setSelectedActionName(data.name) + setSelectedActionEnvironment(data.env) } else { - console.log("Should handle type "+data.type) + alert.error("Can't handle "+data.type) } + + setRightSideBarOpen(true) } const onEdgeAdded = (event) => { @@ -2103,8 +2113,9 @@ const AngularWorkflow = (props) => { const setNewSelectedAction = (e) => { const newaction = selectedApp.actions.find(a => a.name === e.target.value) + // Does this one find the wrong one? selectedAction.name = newaction.name - selectedAction.parameters = newaction.parameters + selectedAction.parameters = JSON.parse(JSON.stringify(newaction.parameters)) // FIXME - this is broken sometimes lol //var env = environments.find(a => a.name === newaction.environment) @@ -2180,7 +2191,7 @@ const AngularWorkflow = (props) => { // Dropdown -> static, action, local env, global env // VALUE (JSON) // {data.name}, {data.description}, {data.required}, {data.schema.type} - const AppActionArguments = () => { + const AppActionArguments = (props) => { const [selectedActionParameters, setSelectedActionParameters] = React.useState([]) const [selectedVariableParameter, setSelectedVariableParameter] = React.useState() @@ -2189,7 +2200,10 @@ const AngularWorkflow = (props) => { if (requiresAuthentication) { console.log("ADD AUTHENTICATION FIELDS") } - setSelectedActionParameters(selectedAction.parameters) + + if (selectedAction.parameters !== null && selectedAction.parameters.length > 0) { + setSelectedActionParameters(selectedAction.parameters) + } } if ((selectedVariableParameter === null || selectedVariableParameter === undefined) && (workflow.workflow_variables !== null && workflow.workflow_variables.length > 0)) { @@ -2201,20 +2215,20 @@ const AngularWorkflow = (props) => { const changeActionParameter = (event, count) => { selectedActionParameters[count].value = event.target.value - selectedAction.parameters = selectedActionParameters + selectedAction.parameters[count].value = event.target.value setSelectedAction(selectedAction) + //setUpdate(event.target.value) } - const changeActionParameterVariable = (fieldvalue, count) => { setSelectedVariableParameter(fieldvalue) // this isn't updated anywhere in the workflow - setSelectedActionName({}) - setSelectedAction({}) - setSelectedTrigger({}) - setSelectedApp({}) - setSelectedEdge({}) + // setSelectedActionName({}) + // setSelectedAction({}) + // setSelectedTrigger({}) + // setSelectedApp({}) + // setSelectedEdge({}) selectedActionParameters[count].action_field = fieldvalue selectedAction.parameters = selectedActionParameters @@ -2222,6 +2236,7 @@ const AngularWorkflow = (props) => { setSelectedActionName(selectedActionName) setSelectedApp(selectedApp) setSelectedAction(selectedAction) + setUpdate(fieldvalue) } // Sets ACTION_RESULT things @@ -2236,11 +2251,6 @@ const AngularWorkflow = (props) => { selectedActionParameters[count].action_field = fieldvalue selectedAction.parameters = selectedActionParameters - setSelectedActionName({}) - setSelectedAction({}) - setSelectedTrigger({}) - setSelectedApp({}) - setSelectedEdge({}) // FIXME - check if startnode // Set value @@ -2248,6 +2258,7 @@ const AngularWorkflow = (props) => { setSelectedApp(selectedApp) setSelectedAction(selectedAction) + setUpdate(fieldvalue) } const changeActionParameterVariant = (data, count) => { @@ -2283,7 +2294,8 @@ const AngularWorkflow = (props) => { setSelectedAction(selectedAction) } - if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters) { + // FIXME: Issue #40 - selectedActionParameters not reset + if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) { return (
Arguments @@ -2300,7 +2312,7 @@ const AngularWorkflow = (props) => { multiline = true } - if (data.value.startsWith("{") && data.value.endsWith("}")) { + if (data.value !== undefined && data.value.startsWith("{") && data.value.endsWith("}")) { multiline = true } @@ -2577,7 +2589,7 @@ const AngularWorkflow = (props) => { />
- Environment: + Environment
- +
@@ -4118,7 +4131,7 @@ const AngularWorkflow = (props) => {
- Run how often (seconds)? + Interval (seconds)
{ } const RightSideBar = () => { + if (!rightSideBarOpen) { + return null + } + setLastSaved(false) if (Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0) { - setRightSideBarOpen(true) //console.time('ACTIONSTART'); return(
@@ -4334,7 +4350,6 @@ const AngularWorkflow = (props) => { ) } else if (Object.getOwnPropertyNames(selectedTrigger).length > 0) { if (selectedTrigger.trigger_type === "SCHEDULE") { - setRightSideBarOpen(true) console.log("SCHEDULE") return(
@@ -4342,24 +4357,18 @@ const AngularWorkflow = (props) => {
) } else if (selectedTrigger.trigger_type === "WEBHOOK") { - setRightSideBarOpen(true) - console.log("WEBHOOK") return(
) } else if (selectedTrigger.trigger_type === "EMAIL") { - setRightSideBarOpen(true) - console.log("EMAIL") return(
) } else if (selectedTrigger.trigger_type === "USERINPUT") { - setRightSideBarOpen(true) - console.log("USER INPUT SIDEBAR") return(
@@ -4372,7 +4381,6 @@ const AngularWorkflow = (props) => { return null } } else if (Object.getOwnPropertyNames(selectedEdge).length > 0) { - setRightSideBarOpen(true) return(
@@ -4478,7 +4486,7 @@ const AngularWorkflow = (props) => { }
: -
+

{setExecutionModalView(0)}}> @@ -4489,7 +4497,7 @@ const AngularWorkflow = (props) => {

Executing Workflow

{executionData.execution_argument !== undefined && executionData.execution_argument.length > 0 ?
-

Execution Argument:

{executionText} +

Execution Argument:

{executionData.execution_argument}
: null } {executionData.status !== undefined && executionData.status.length > 0 ? @@ -4514,7 +4522,7 @@ const AngularWorkflow = (props) => {
Actions
- {executionData.status !== undefined && executionData.status !== "ABORTED" && executionData.status !== "FINISHED" ? : null} + {executionData.status !== undefined && executionData.status !== "ABORTED" && executionData.status !== "FINISHED" && executionData.status !== "FAILURE" ? : null}
{executionData.results === undefined || executionData.results === null || executionData.results.length === 0 && executionData.status === "EXECUTING" ? @@ -4841,6 +4849,10 @@ const AngularWorkflow = (props) => { return (
+ {loadedCheck}
) diff --git a/frontend/src/AppCreator.js b/frontend/src/AppCreator.js index 24ab8353..29e98a56 100644 --- a/frontend/src/AppCreator.js +++ b/frontend/src/AppCreator.js @@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react'; import { makeStyles } from '@material-ui/styles'; import {BrowserView, MobileView} from "react-device-detect"; +import {Link} from 'react-router-dom'; import Paper from '@material-ui/core/Paper'; import Button from '@material-ui/core/Button'; import Divider from '@material-ui/core/Divider'; @@ -15,9 +16,12 @@ import DialogActions from '@material-ui/core/DialogActions'; import TextField from '@material-ui/core/TextField'; import Tooltip from '@material-ui/core/Tooltip'; import CheckCircleIcon from '@material-ui/icons/CheckCircle'; +import Breadcrumbs from '@material-ui/core/Breadcrumbs'; +import AppsIcon from '@material-ui/icons/Apps'; import ErrorOutline from '@material-ui/icons/ErrorOutline'; import { useAlert } from "react-alert"; +import words from "shellwords" const surfaceColor = "#27292D" const inputColor = "#383B40" @@ -39,6 +43,7 @@ const actionListStyle = { } const boxStyle = { + color: "white", flex: "1", marginLeft: "10px", marginRight: "10px", @@ -55,7 +60,119 @@ const useStyles = makeStyles({ notchedOutline: { borderColor: "#f85a3e !important" }, -}); +}) + + +const rewrite = (args) => { + return args.reduce(function(args, a){ + if (0 == a.indexOf('-X')) { + args.push('-X') + args.push(a.slice(2)) + } else { + args.push(a) + } + + return args + }, []) +} + +const parseField = (s) => { + return s.split(/: (.+)/) +} + +const isURL = (s) => { + return /^https?:\/\//.test(s) +} + +// Parses CURL to a real request +const parseCurl = (s) => { + //console.log("CURL: ", s) + + if (0 != s.indexOf('curl ')) { + console.log("Not curl start") + return "" + } + + var args = rewrite(words.split(s)) + var out = { method: 'GET', header: {} } + var state = '' + + args.forEach(function(arg){ + switch (true) { + case isURL(arg): + out.url = arg + break; + + case arg == '-A' || arg == '--user-agent': + state = 'user-agent' + break; + + case arg == '-H' || arg == '--header': + state = 'header' + break; + + case arg == '-d' || arg == '--data' || arg == '--data-ascii': + state = 'data' + break; + + case arg == '-u' || arg == '--user': + state = 'user' + break; + + case arg == '-I' || arg == '--head': + out.method = 'HEAD' + break; + + case arg == '-X' || arg == '--request': + state = 'method' + break; + + case arg == '-b' || arg =='--cookie': + state = 'cookie' + break; + + case arg == '--compressed': + out.header['Accept-Encoding'] = out.header['Accept-Encoding'] || 'deflate, gzip' + break; + + case !!arg: + switch (state) { + case 'header': + var field = parseField(arg) + out.header[field[0]] = field[1] + state = '' + break; + case 'user-agent': + out.header['User-Agent'] = arg + state = '' + break; + case 'data': + if (out.method == 'GET' || out.method == 'HEAD') out.method = 'POST' + out.header['Content-Type'] = out.header['Content-Type'] || 'application/x-www-form-urlencoded' + out.body = out.body + ? out.body + '&' + arg + : arg + state = '' + break; + case 'user': + out.header['Authorization'] = 'Basic ' + btoa(arg) + state = '' + break; + case 'method': + out.method = arg + state = '' + break; + case 'cookie': + out.header['Set-Cookie'] = arg + state = '' + break; + } + break; + } + }) + + return out +} // Should be different if logged in :| const AppCreator = (props) => { @@ -117,16 +234,16 @@ const AppCreator = (props) => { const [currentActionMethod, setCurrentActionMethod] = useState(actionNonBodyRequest[0]) const [currentAction, setCurrentAction] = useState({ - "name": "", - "description": "", - "url": "", - "headers": "", - "paths": [], - "queries": [], - "body": "", - "errors": [], - "method": actionNonBodyRequest[0], - }); + "name": "", + "description": "", + "url": "", + "headers": "", + "paths": [], + "queries": [], + "body": "", + "errors": [], + "method": actionNonBodyRequest[0], + }); @@ -144,13 +261,13 @@ const AppCreator = (props) => { const handleEditApp = () => { fetch(globalUrl+"/api/v1/apps/"+props.match.params.appid+"/config", { - method: 'GET', + method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, - credentials: "include", - }) + credentials: "include", + }) .then((response) => { if (response.status !== 200) { window.location.pathname = "/apps" @@ -159,14 +276,12 @@ const AppCreator = (props) => { return response.json() }) .then((responseJson) => { - setIsAppLoaded(true) + setIsAppLoaded(true) if (!responseJson.success) { alert.error("Failed to get the app") } else { const data = JSON.parse(responseJson.body) - console.log("LOADED IMAGE: ", data.image) - setFileBase64(data.image) - parseOpenapiData(data) + parseIncomingOpenapiData(data) } }) .catch(error => { @@ -184,13 +299,13 @@ const AppCreator = (props) => { } fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) .then((response) => { if (response.status !== 200) { throw new Error("NOT 200 :O") @@ -199,12 +314,12 @@ const AppCreator = (props) => { return response.json() }) .then((responseJson) => { - setIsAppLoaded(true) + setIsAppLoaded(true) if (!responseJson.success) { alert.error("Failed to verify") } else { const data = JSON.parse(responseJson.body) - parseOpenapiData(data) + parseIncomingOpenapiData(data) } }) .catch(error => { @@ -227,14 +342,18 @@ const AppCreator = (props) => { } // Sets the data up as it should be at later points - const parseOpenapiData = (data) => { + // This is the data FROM the database, not what's being saved + const parseIncomingOpenapiData = (data) => { setBasedata(data) - setName(data.info.title) setDescription(data.info.description) document.title = "Apps - "+data.info.title + if (data.info !== null && data.info !== undefined && data.info["x-logo"] !== undefined) { + setFileBase64(data.info["x-logo"]) + } + if (data.info.contact != undefined) { setContact(data.info.contact) } @@ -243,8 +362,6 @@ const AppCreator = (props) => { setBaseUrl(data.servers[0].url) } - console.log(data) - // This is annoying (: var securitySchemes = data.components.securityDefinitions if (securitySchemes === undefined) { @@ -255,6 +372,7 @@ const AppCreator = (props) => { securitySchemes = data.components.securitySchemes } + // FIXME: Have multiple authentication options? if (securitySchemes !== undefined) { console.log("Am I in here?") for (const [key, value] of Object.entries(securitySchemes)) { @@ -263,7 +381,6 @@ const AppCreator = (props) => { break } else if (value.type === "apiKey") { setAuthenticationOption("API key") - setParameterName(value.name) value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1); setParameterLocation(value.in) @@ -271,6 +388,8 @@ const AppCreator = (props) => { console.log("APIKEY SELECT: ", apikeySelection) alert.error("Might be error in setting up API key authentication") } + + setParameterName(value.name) break } else if (value.scheme === "basic") { setAuthenticationOption("Basic auth") @@ -279,6 +398,8 @@ const AppCreator = (props) => { } } + console.log(data) + // FIXME - headers? var newActions = [] for (let [path, pathvalue] of Object.entries(data.paths)) { @@ -295,8 +416,6 @@ const AppCreator = (props) => { "errors": [], } - //console.log(`${path}: ${method}`); - //console.log(methodvalue) for (var key in methodvalue.parameters) { const parameter = methodvalue.parameters[key] @@ -316,6 +435,16 @@ const AppCreator = (props) => { } else if (parameter.in === "path") { // FIXME - parse this to the URL too newaction.paths.push(parameter.name) + + // FIXME: This doesn't follow OpenAPI3 exactly. + // https://swagger.io/docs/specification/describing-request-body/ + // https://swagger.io/docs/specification/describing-parameters/ + // Need to split the data. + } else if (parameter.in === "body") { + console.log("BODY: ", parameter) + newaction.body = parameter.example + } else if (parameter.in === "header") { + newaction.headers += `${parameter.name}=${parameter.example}\n` } } @@ -323,12 +452,12 @@ const AppCreator = (props) => { } } - console.log(newActions) setActions(newActions) } + // Saving the app that's been configured. const submitApp = () => { - alert.info("Uploading private app " + name) + alert.info("Uploading and building app " + name) setErrorCode("") // Format the information @@ -343,6 +472,7 @@ const AppCreator = (props) => { "title": name, "description": description, "version": "1.0", + "x-logo": fileBase64, }, "servers": [{"url": baseUrl}], "host": host, @@ -353,7 +483,6 @@ const AppCreator = (props) => { "components": { "securitySchemes": {}, }, - "image": fileBase64, "id": props.match.params.appid, "securityDefinitions": {}, } @@ -368,7 +497,7 @@ const AppCreator = (props) => { data.info["contact"] = contact } - console.log("LOADED IMAGE: ", data.image) + //console.log("LOADED IMAGE: ", data.image) for (var key in actions) { const item = actions[key] @@ -440,6 +569,68 @@ const AppCreator = (props) => { //console.log(queryitem) } } + + if (item.body.length > 0) { + const required = false + newitem = { + "in": "body", + "name": "body", + "multiline": true, + "description": "Generated by shuffler.io OpenAPI", + "required": required, + "example": item.body, + "schema": { + "type": "string", + }, + } + + // FIXME - add application/json if JSON example? + data.paths[item.url][item.method.toLowerCase()]["requestBody"] = { + "description": "Generated by Shuffler.io", + "required": required, + "content": { + "example": { + "example": item.body, + }, + }, + } + + data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + } + + if (item.headers.length > 0) { + const required = false + + const headersSplit = item.headers.split("\n") + for (var key in headersSplit) { + const header = headersSplit[key] + var key = "" + var value = "" + if (header.length > 0 && header.includes("=")) { + const headersplit = header.split("=") + key = headersplit[0] + value = headersplit[1] + } else { + continue + } + + if (key.length > 0 && value.length > 0) { + newitem = { + "in": "header", + "name": key, + "multiline": false, + "description": "Header generated by shuffler.io OpenAPI", + "required": false, + "example": value, + "schema": { + "type": "string", + }, + } + + data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + } + } + } } if (authenticationOption === "API key") { @@ -461,8 +652,10 @@ const AppCreator = (props) => { } } + console.log("ACTIONS: ", data.paths) + fetch(globalUrl+"/api/v1/verify_openapi", { - method: 'POST', + method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', @@ -480,12 +673,17 @@ const AppCreator = (props) => { }) .then((responseJson) => { if (!responseJson.success) { - setErrorCode(responseJson.reason) - alert.error("Failed to verify: ") + if (responseJson.reason !== undefined) { + setErrorCode(responseJson.reason) + alert.error("Failed to verify: "+responseJson.reason) + } } else { - // Return? alert.success("Successfully uploaded openapi") - //window.location = "/apps" + if (window.location.pathname.includes("/new")) { + if (responseJson.id !== undefined && responseJson.id !== null) { + window.location = `/apps/edit/${responseJson.id}` + } + } } }) .catch(error => { @@ -495,9 +693,9 @@ const AppCreator = (props) => { } const bearerAuth = authenticationOption === "Bearer auth" ? -
+

- + Bearer auth

@@ -508,9 +706,9 @@ const AppCreator = (props) => { // Basicauth const basicAuth = authenticationOption === "Basic auth" ? -
+

- + Basic authentication

@@ -596,11 +794,11 @@ const AppCreator = (props) => { setActions(actions) } - console.log("Option: ", authenticationOption) - console.log("Location: ", parameterLocation) - console.log("Name: ", parameterName) + //console.log("Option: ", authenticationOption) + //console.log("Location: ", parameterLocation) + //console.log("Name: ", parameterName) const apiKey = authenticationOption === "API key" ? -
+

API key

{ const setActionField = (field, value) => { currentAction[field] = value setCurrentAction(currentAction) + //if (updater !== value) { + // setUpdater(value) + //} } const bodyInfo = actionBodyRequest.includes(currentActionMethod) ?
- Body + Body - used as example in action argument { currentAction.queries = urlPathQueries setUrlPathQueries([]) - console.log(actions) - console.log(currentAction.name) const actionIndex = actions.findIndex(data => data.name === currentAction.name) - console.log(actionIndex) if (actionIndex < 0) { actions.push(currentAction) } else { @@ -811,20 +1009,34 @@ const AppCreator = (props) => { errormessage.push("All queries must have a value") } - console.log(urlPathParameters) - // const [urlPathParameters, setUrlPathParameters] = useState([]); - return errormessage } const UrlPathParameters = () => { + var paths = [] + var queries = [] + if (urlPath.includes("{") && urlPath.includes("}")) { - var values = [] var tmpWord = "" var record = false + + var query = false for (var key in urlPath) { + if (urlPath[key] === "?") { + query = true + } + if (urlPath[key] === "}") { - values.push(tmpWord) + if (tmpWord === parameterName) { + tmpWord = "" + record = false + continue + } else if (query) { + queries.push(tmpWord) + } else { + paths.push(tmpWord) + } + tmpWord = "" record = false } @@ -833,24 +1045,74 @@ const AppCreator = (props) => { tmpWord += urlPath[key] } - if (urlPath[key] === "{" && urlPath[key-1] === "/") { + //if (urlPath[key] === "{" && urlPath[key-1] === "/") { + if (urlPath[key] === "{") { record = true } } - - if (!currentAction.paths === values) { - currentAction.paths = values - setCurrentAction(currentAction) - } - - return ( -
- Required parameters: {values.join(", ")} -
- ) } - return null + if (urlPath.includes("<") && urlPath.includes(">")) { + var tmpWord = "" + var record = false + + var query = false + for (var key in urlPath) { + if (urlPath[key] === "?") { + query = true + } + + if (urlPath[key] === ">") { + if (tmpWord === parameterName) { + tmpWord = "" + record = false + continue + } else if (query) { + queries.push(tmpWord) + } else { + paths.push(tmpWord) + } + + tmpWord = "" + record = false + } + + if (record) { + tmpWord += urlPath[key] + } + + //if (urlPath[key] === "{" && urlPath[key-1] === "/") { + if (urlPath[key] === "<") { + record = true + } + } + } + + if (currentAction.paths !== paths) { + setActionField("paths", paths) + } + + var tmpQueries = [] + + // No overlapping of names + for (var key in queries) { + const tmpquery = queries[key] + const found = tmpQueries.find(query => query.name === tmpquery) + if (found === undefined) { + tmpQueries.push({"name": queries[key], required: true}) + } + } + + // FIXME: Frontend isn't updating.. + if (JSON.stringify(tmpQueries) !== JSON.stringify(urlPathQueries)) { + setUrlPathQueries(tmpQueries) + } + + return paths.length > 0 ? +
+ Required parameters: {paths.join(", ")} +
+ : null } const newActionModal = @@ -858,6 +1120,7 @@ const AppCreator = (props) => { open={actionsModalOpen} fullWidth onClose={() => { + console.log("CLOSED?") setUrlPath("") setCurrentAction({ "name": "", @@ -878,12 +1141,12 @@ const AppCreator = (props) => {
New action
- Learn more about app creation + Learn more about actions
Name { margin="normal" variant="outlined" defaultValue={currentAction["name"]} - onChange={e => setActionField("name", e.target.value)} + onChange={e => { + setActionField("name", e.target.value) + }} + onBlur={e => { + // Fix basic issues in frontend. Python functions run a-zA-Z0-9_ + console.log(e.target.value) + const regex = /[A-Za-z0-9 _]/g; + const found = e.target.value.match(regex); + console.log("FOUND: ", found) + if (found !== null) { + setActionField("name", found.join("")) + } + }} key={currentAction} InputProps={{ classes: { @@ -902,7 +1177,7 @@ const AppCreator = (props) => { }, }} /> -
+
Description { ))}
- URL path + URL path / Curl statement { setUrlPath(e.target.value) console.log(e.target.value) }} - helperText={
The path to use. Must start with /. Add {"{variable}"} to have path variables
} + helperText={
The path to use. Must start with /. Use {"{variablename}"} to have path variables
} InputProps={{ classes: { notchedOutline: classes.notchedOutline, @@ -976,6 +1251,61 @@ const AppCreator = (props) => { color: "white", }, }} + onBlur={event => { + var parsedurl = event.target.value + if (parsedurl.startsWith("curl")) { + const request = parseCurl(event.target.value) + console.log(request) + if (request.method.toUpperCase() !== currentAction.Method) { + setCurrentActionMethod(request.method.toUpperCase()) + setActionField("method", request.method.toUpperCase()) + } + + if (request.header !== undefined && request.header !== null) { + var headers = [] + for (let [key, value] of Object.entries(request.header)) { + headers += key+"="+value+"\n" + } + + setActionField("headers", headers) + } + + if (request.body !== undefined && request.body !== null) { + setActionField("body", request.body) + } + + // Parse URL + parsedurl = request.url + } + + if (parsedurl.includes("<") && parsedurl.includes(">")) { + parsedurl = parsedurl.split("<").join("{") + parsedurl = parsedurl.split(">").join("}") + } + + if (parsedurl.startsWith("http") || parsedurl.startsWith("ftp")) { + if (parsedurl !== undefined && parsedurl.includes(parameterName)) { + // Remove <> etc. + // + + console.log("IT HAS THE PARAM NAME!") + const newurl = new URL(encodeURI(parsedurl)) + newurl.searchParams.delete(parameterName) + parsedurl = decodeURI(newurl.href) + } + + // Remove the base URL itself + if (parsedurl !== undefined && baseUrl !== undefined && baseUrl.length > 0 && parsedurl.includes(baseUrl)) { + parsedurl = parsedurl.replace(baseUrl, "") + } + + // Check URL query && headers + setActionField("url", parsedurl) + setUrlPath(parsedurl) + } + + //console.log("URL: ", request.url) + }} /> {loopQueries} @@ -983,7 +1313,7 @@ const AppCreator = (props) => { addPathQuery() }}>New query
- Headers + Headers - static for the action { {bodyInfo} - - + @@ -1029,7 +1360,7 @@ const AppCreator = (props) => {

Actions

Actions are the tasks performed by an app. Read more about actions and apps - here. + here.
{loopActions}
@@ -1054,7 +1385,7 @@ const AppCreator = (props) => {

Test

Test an action to see whether it performs in an expected way. -  Click here to learn more about testing. +  TBD: Click here to learn more about testing.
Test :)
@@ -1071,14 +1402,23 @@ const AppCreator = (props) => { if (file !== "") { const img = document.getElementById('logo') var canvas = document.createElement('canvas') + canvas.width = 174 + canvas.height = 174 var ctx = canvas.getContext('2d') img.onload = function() { // img, x, y, width, height - ctx.drawImage(img, 0, 0) + //ctx.drawImage(img, 174, 174) + console.log("IMG natural: ", img.naturalWidth, img.naturalHeight) + //ctx.drawImage(img, 0, 0, 174, 174) + ctx.drawImage(img, + 0, 0, img.width, img.height, + 0, 0, canvas.width, canvas.height + ) + const canvasUrl = canvas.toDataURL() - console.log(canvasUrl) if (canvasUrl !== fileBase64) { + console.log("SET URL TO: ", canvasUrl) setFileBase64(canvasUrl) } } @@ -1096,14 +1436,25 @@ const AppCreator = (props) => { // const imageData = file.length > 0 ? file : fileBase64 - const imageInfo = + const imageInfo = // Random names for type & autoComplete. Didn't research :^) const landingpageDataBrowser =
+ + +

+ + Apps +

+ +

+ {name} +

+

General information

- Click here to learn more about app creation + Click here to learn more about app creation
{upload.click()}}> @@ -1120,11 +1471,11 @@ const AppCreator = (props) => { fullWidth={true} placeholder="Name" type="name" - id="standard-required" + id="standard-required" margin="normal" variant="outlined" value={name} - onChange={e => setName(e.target.value)} + onChange={e => setName(e.target.value)} color="primary" InputProps={{ style:{ @@ -1187,6 +1538,19 @@ const AppCreator = (props) => { helperText={
Must start with http(s):// and CANT end with /.
} placeholder="https://api.example.com" onChange={e => setBaseUrl(e.target.value)} + onBlur={(event) => { + var tmpstring = event.target.value.trim() + if (tmpstring.endsWith("/")) { + tmpstring = tmpstring.slice(0, -1) + } + if (tmpstring.length > 4 && !tmpstring.startsWith("http") && !tmpstring.startsWith("ftp")) { + alert.error("URL must start with http(s)://") + } + + //if (authenticationOption === "No authentication" && + + setBaseUrl(tmpstring) + }} />
Authentication
@@ -1196,7 +1560,7 @@ const AppCreator = (props) => { setAuthenticationOption(e.target.value) }} value={authenticationOption} - style={{backgroundColor: inputColor, paddingLeft: "10px", color: "white", height: "50px"}} + style={{backgroundColor: inputColor, color: "white", height: "50px"}} > {authenticationOptions.map(data => ( @@ -1219,7 +1583,7 @@ const AppCreator = (props) => { }}> Save - {errorCode} + {errorCode.length > 0 ? `Error: ${errorCode}` : null}
diff --git a/frontend/src/Apps.js b/frontend/src/Apps.js index 69c5fd02..9198a301 100644 --- a/frontend/src/Apps.js +++ b/frontend/src/Apps.js @@ -2,6 +2,7 @@ import React, { useEffect} from 'react'; import { useInterval } from 'react-powerhooks'; +import AppsIcon from '@material-ui/icons/Apps'; import Grid from '@material-ui/core/Grid'; import Select from '@material-ui/core/Select'; import Paper from '@material-ui/core/Paper'; @@ -17,8 +18,12 @@ import Switch from '@material-ui/core/Switch'; import Input from '@material-ui/core/Input'; import YAML from 'yaml' import {Link} from 'react-router-dom'; +import Breadcrumbs from '@material-ui/core/Breadcrumbs'; import CloudDownload from '@material-ui/icons/CloudDownload'; +import EditIcon from '@material-ui/icons/Edit'; +import DeleteIcon from '@material-ui/icons/Delete'; + import { useAlert } from "react-alert"; import Dialog from '@material-ui/core/Dialog'; @@ -117,10 +122,12 @@ const Apps = (props) => { setFilteredApps(responseJson) if (responseJson.length > 0) { setSelectedApp(responseJson[0]) - if (responseJson[0].actions.length > 0) { + if (responseJson[0].actions !== null && responseJson[0].actions.length > 0) { setSelectedAction(responseJson[0].actions[0]) + } else { + setSelectedAction({}) } - } + } }) .catch(error => { alert.error(error.toString()) @@ -130,15 +137,15 @@ const Apps = (props) => { const downloadApp = (inputdata) => { const id = inputdata.id - alert.info("Preparing download.") + alert.info("Downloading..") fetch(globalUrl+"/api/v1/apps/"+id+"/config", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - credentials: "include", - }) + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) .then((response) => { if (response.status !== 200) { window.location.pathname = "/apps" @@ -150,12 +157,22 @@ const Apps = (props) => { if (!responseJson.success) { alert.error("Failed to download file") } else { - const data = YAML.stringify(YAML.parse(responseJson.body)) + const inputdata = YAML.parse(responseJson.body) + const newpaths = {} + Object.keys(inputdata["paths"]).forEach(function(key) { + newpaths[key.split("?")[0]] = inputdata.paths[key] + }) - var name = inputdata.name + inputdata.paths = newpaths + console.log("INPUT: ", inputdata) + var name = inputdata.info.title name = name.replace(/ /g, "_", -1) name = name.toLowerCase() + delete inputdata.id + delete inputdata.editing + + const data = YAML.stringify(inputdata) var blob = new Blob( [ data ], { type: 'application/octet-stream' }) @@ -222,9 +239,11 @@ const Apps = (props) => { { if (selectedApp.id !== data.id) { setSelectedApp(data) - if (data.actions.length > 0) { + if (data.actions !== undefined && data.actions !== null && data.actions.length > 0) { console.log(data.actions[0]) setSelectedAction(data.actions[0]) + } else { + setSelectedAction({}) } } }}> @@ -250,14 +269,15 @@ const Apps = (props) => { - + + + {data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ? {downloadApp(data)}}> - {/* - */} + : null} ) } @@ -290,15 +310,28 @@ const Apps = (props) => { const editUrl = "/apps/edit/"+selectedApp.id const activateUrl = "/apps/new?id="+selectedApp.id + + var downloadButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? + + : null + var editButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? : null var activateButton = selectedApp.generated && !selectedApp.activated ? @@ -307,7 +340,7 @@ const Apps = (props) => { variant="contained" component="label" color="primary" - style={{marginTop: "10px"}} + style={{marginTop: 10}} > Activate App : null @@ -322,7 +355,7 @@ const Apps = (props) => { deleteApp(selectedApp.id) }} > - Delete app + : null var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ? @@ -343,6 +376,7 @@ const Apps = (props) => {
{activateButton} + {downloadButton} {editButton} {deleteButton} @@ -352,36 +386,42 @@ const Apps = (props) => {
Actions - { + setSelectedAction(event.target.value) + }} + style={{backgroundColor: inputColor, color: "white", height: "50px"}} + SelectDisplayProps={{ + style: { + marginLeft: 10, + } + }} + > + {selectedApp.actions.map(data => { + var newActionname = data.label !== undefined && data.label.length > 0 ? data.label : data.name - // ROFL FIXME - loop - newActionname = newActionname.replace("_", " ") - newActionname = newActionname.replace("_", " ") - newActionname = newActionname.replace("_", " ") - newActionname = newActionname.replace("_", " ") - newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1) - return ( - - {newActionname} + // ROFL FIXME - loop + newActionname = newActionname.replace("_", " ") + newActionname = newActionname.replace("_", " ") + newActionname = newActionname.replace("_", " ") + newActionname = newActionname.replace("_", " ") + newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1) + return ( + + {newActionname} - - ) - })} - + + ) + })} + + : +
+ There are no actions defined for this app. +
+ }
{selectedAction.parameters !== undefined && selectedAction.parameters !== null ? @@ -453,8 +493,8 @@ const Apps = (props) => { ) } - const handleSearchChange = (event) => { - const searchfield = event.target.value.toLowerCase() + const handleSearchChange = (search) => { + const searchfield = search.toLowerCase() const newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield)) if ((newapps.length === 0 || searchBackend) && !appSearchLoading) { @@ -469,9 +509,22 @@ const Apps = (props) => { const appView = isLoggedIn ?
-
-

Upload

-
+
+ + +

+ + App upload +

+ + {selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ? + +

+ {selectedApp.name} +

+ + : null} +
@@ -484,7 +537,10 @@ const Apps = (props) => { Search OpenAPI
- control={ {setSearchBackend(!searchBackend)}} />} + control={ { + handleSearchChange("") + setSearchBackend(!searchBackend)} + } />} /> diff --git a/frontend/src/defaultCytoscapeStyle.js b/frontend/src/defaultCytoscapeStyle.js index 5621a377..4e3c0b24 100644 --- a/frontend/src/defaultCytoscapeStyle.js +++ b/frontend/src/defaultCytoscapeStyle.js @@ -35,6 +35,8 @@ const data = [{ 'shape': 'square', 'background-color': '#213243', 'border-color': '#81c784', + 'background-width': '100%', + 'background-height': '100%', }, }, {