Merge pull request #42 from frikky/dev

OpenAPI creator with Curl generator
This commit is contained in:
Frikky
2020-06-02 12:23:55 +09:00
committed by GitHub
13 changed files with 1275 additions and 590 deletions
+1
View File
@@ -13,3 +13,4 @@ BACKEND_PORT=5001
FRONTEND_PORT=3001
FRONTEND_PORT_HTTPS=3443
OUTER_HOSTNAME=shuffle-backend
DB_LOCATION=/etc/shuffle
+1
View File
@@ -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)
+272 -97
View File
@@ -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
}
}
+45 -20
View File
@@ -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)
+21 -9
View File
@@ -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
+1 -1
View File
@@ -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
+294 -255
View File
@@ -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",
+4 -2
View File
@@ -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",
+59 -47
View File
@@ -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 (
<div style={{marginTop: "30px"}}>
<b>Arguments</b>
@@ -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) => {
/>
<div style={{marginTop: "20px"}}>
Environment:
Environment
<Select
value={selectedActionEnvironment === undefined || selectedActionEnvironment.Name === undefined ? "" : selectedActionEnvironment.Name}
PaperProps={{
@@ -2624,10 +2636,11 @@ const AngularWorkflow = (props) => {
value={selectedActionName}
fullWidth
onChange={setNewSelectedAction}
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
style={{backgroundColor: inputColor, color: "white", height: 50}}
SelectDisplayProps={{
style: {
marginLeft: 10,
maxHeight: 200,
}
}}
>
@@ -2651,7 +2664,7 @@ const AngularWorkflow = (props) => {
})}
</Select>
<div style={{marginTop: "10px", borderColor: "white", borderWidth: "2px", marginBottom: 200}}>
<AppActionArguments key={"hey"} />
<AppActionArguments key={selectedAction.id} selectedAction={selectedAction} />
</div>
</div>
@@ -4118,7 +4131,7 @@ const AngularWorkflow = (props) => {
<div style={{marginTop: "20px", marginBottom: "7px", display: "flex"}}>
<div style={{width: "17px", height: "17px", borderRadius: 17 / 2, backgroundColor: "#f85a3e", marginRight: "10px"}}/>
<div style={{flex: "10"}}>
<b>Run how often (seconds)? </b>
<b>Interval (seconds) </b>
</div>
</div>
<TextField
@@ -4323,9 +4336,12 @@ const AngularWorkflow = (props) => {
}
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(
<div style={rightsidebarStyle}>
@@ -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(
<div style={rightsidebarStyle}>
@@ -4342,24 +4357,18 @@ const AngularWorkflow = (props) => {
</div>
)
} else if (selectedTrigger.trigger_type === "WEBHOOK") {
setRightSideBarOpen(true)
console.log("WEBHOOK")
return(
<div style={rightsidebarStyle}>
<WebhookSidebar />
</div>
)
} else if (selectedTrigger.trigger_type === "EMAIL") {
setRightSideBarOpen(true)
console.log("EMAIL")
return(
<div style={rightsidebarStyle}>
<EmailSidebar />
</div>
)
} else if (selectedTrigger.trigger_type === "USERINPUT") {
setRightSideBarOpen(true)
console.log("USER INPUT SIDEBAR")
return(
<div style={rightsidebarStyle}>
<UserinputSidebar />
@@ -4372,7 +4381,6 @@ const AngularWorkflow = (props) => {
return null
}
} else if (Object.getOwnPropertyNames(selectedEdge).length > 0) {
setRightSideBarOpen(true)
return(
<div style={rightsidebarStyle}>
<EdgeSidebar />
@@ -4478,7 +4486,7 @@ const AngularWorkflow = (props) => {
}
</div>
:
<div style={{padding: 25, }}>
<div style={{padding: 25, maxWidth: 365, overflowX: "hidden",}}>
<Breadcrumbs aria-label="breadcrumb" separator="" style={{color: "white", fontSize: 16}}>
<h2 style={{color: "rgba(255,255,255,0.5)", cursor: "pointer"}} onClick={() => {setExecutionModalView(0)}}>
<DirectionsRunIcon style={{marginRight: 10}} />
@@ -4489,7 +4497,7 @@ const AngularWorkflow = (props) => {
<h2>Executing Workflow</h2>
{executionData.execution_argument !== undefined && executionData.execution_argument.length > 0 ?
<div>
<h3>Execution Argument: </h3>{executionText}
<h3>Execution Argument: </h3>{executionData.execution_argument}
</div>
: null }
{executionData.status !== undefined && executionData.status.length > 0 ?
@@ -4514,7 +4522,7 @@ const AngularWorkflow = (props) => {
<div style={{display: "flex", marginTop: 10, marginBottom: 30,}}>
<b>Actions</b>
<div>
{executionData.status !== undefined && executionData.status !== "ABORTED" && executionData.status !== "FINISHED" ? <CircularProgress style={{marginLeft: 20}}/> : null}
{executionData.status !== undefined && executionData.status !== "ABORTED" && executionData.status !== "FINISHED" && executionData.status !== "FAILURE" ? <CircularProgress style={{marginLeft: 20}}/> : null}
</div>
</div>
{executionData.results === undefined || executionData.results === null || executionData.results.length === 0 && executionData.status === "EXECUTING" ?
@@ -4841,6 +4849,10 @@ const AngularWorkflow = (props) => {
return (
<div>
<Prompt
when={true}
message={unloadText}
/>
{loadedCheck}
</div>
)
+460 -96
View File
@@ -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" ?
<div>
<div style={{color: "white"}}>
<h4>
<a href="https://swagger.io/docs/specification/authentication/bearer-authentication/" style={{textDecoriation: "none", color: "#f85a3e"}}>
<a target="_blank" href="https://swagger.io/docs/specification/authentication/bearer-authentication/" style={{textDecoriation: "none", color: "#f85a3e"}}>
Bearer auth
</a>
</h4>
@@ -508,9 +706,9 @@ const AppCreator = (props) => {
// Basicauth
const basicAuth = authenticationOption === "Basic auth" ?
<div>
<div style={{color: "white"}}>
<h4>
<a href="https://swagger.io/docs/specification/authentication/basic-authentication/" style={{textDecoriation: "none", color: "#f85a3e"}}>
<a target="_blank" href="https://swagger.io/docs/specification/authentication/basic-authentication/" style={{textDecoriation: "none", color: "#f85a3e"}}>
Basic authentication
</a>
</h4>
@@ -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" ?
<div>
<div style={{color: "white"}}>
<h4>API key</h4>
<TextField
required
@@ -745,11 +943,14 @@ const AppCreator = (props) => {
const setActionField = (field, value) => {
currentAction[field] = value
setCurrentAction(currentAction)
//if (updater !== value) {
// setUpdater(value)
//}
}
const bodyInfo = actionBodyRequest.includes(currentActionMethod) ?
<div>
Body
Body - used as example in action argument
<TextField
required
style={{flex: "1", marginRight: "15px", backgroundColor: inputColor}}
@@ -780,10 +981,7 @@ const AppCreator = (props) => {
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 (
<div>
Required parameters: {values.join(", ")}
</div>
)
}
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 ?
<div>
Required parameters: {paths.join(", ")}
</div>
: 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) => {
<FormControl style={{backgroundColor: surfaceColor, color: "white",}}>
<DialogTitle><div style={{color: "white"}}>New action</div></DialogTitle>
<DialogContent>
<a href="/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more about app creation</a>
<Link target="_blank" to="/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}>Learn more about actions</Link>
<div style={{marginTop: "15px"}}/>
Name
<TextField
required
style={{flex: "1", marginTop: "5px", marginRight: "15px", backgroundColor: inputColor}}
style={{flex: "1", marginTop: 5, marginRight: 15, backgroundColor: inputColor}}
fullWidth={true}
placeholder="Name"
type="name"
@@ -891,7 +1154,19 @@ const AppCreator = (props) => {
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) => {
},
}}
/>
<div style={{marginTop: "10px"}}/>
<div style={{marginTop: 10}}/>
Description
<TextField
required
@@ -951,7 +1226,7 @@ const AppCreator = (props) => {
))}
</Select>
<div style={{marginTop: "15px"}} />
URL path
URL path / Curl statement
<TextField
required
style={{flex: "1", marginRight: "15px", marginTop: "5px", backgroundColor: inputColor}}
@@ -966,7 +1241,7 @@ const AppCreator = (props) => {
setUrlPath(e.target.value)
console.log(e.target.value)
}}
helperText={<div style={{color:"white", marginBottom: "2px",}}>The path to use. Must start with /. Add {"{variable}"} to have path variables</div>}
helperText={<div style={{color:"white", marginBottom: "2px",}}>The path to use. Must start with /. Use {"{variablename}"} to have path variables</div>}
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)
}}
/>
<UrlPathParameters />
{loopQueries}
@@ -983,7 +1313,7 @@ const AppCreator = (props) => {
addPathQuery()
}}>New query</Button>
<div/>
Headers
Headers - static for the action
<TextField
required
style={{flex: "1", marginRight: "15px", marginTop: "5px", backgroundColor: inputColor}}
@@ -1009,15 +1339,16 @@ const AppCreator = (props) => {
{bodyInfo}
</DialogContent>
<DialogActions>
<Button style={{borderRadius: "0px"}} onClick={() => {
setActionsModalOpen(false)}}>
Cancel
</Button>
<Button color="primary" variant="outlined" style={{borderRadius: "0px"}} onClick={() => {
<Button style={{borderRadius: "0px"}} onClick={() => {
setActionsModalOpen(false)}}>
Cancel
</Button>
<Button color="primary" variant="outlined" style={{borderRadius: "0px"}} onClick={() => {
const errors = getActionErrors()
addActionToView(errors)
setActionsModalOpen(false)
setUrlPathQueries([])
setUrlPath("")
}}>
Submit
</Button>
@@ -1029,7 +1360,7 @@ const AppCreator = (props) => {
<div style={{color: "white"}}>
<h2>Actions</h2>
Actions are the tasks performed by an app. Read more about actions and apps
<a href="/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}> here</a>.
<Link target="_blank" to="/docs/apps#actions" style={{textDecoration: "none", color: "#f85a3e"}}> here</Link>.
<div>
{loopActions}
<Button color="primary" style={{marginTop: "20px", borderRadius: "0px"}} variant="outlined" onClick={() => {
@@ -1044,7 +1375,7 @@ const AppCreator = (props) => {
"errors": [],
"method": actionNonBodyRequest[0],
})
setCurrentActionMethod(actionNonBodyRequest[0])
setCurrentActionMethod(actionNonBodyRequest[0])
setActionsModalOpen(true)
}}>New action</Button>
</div>
@@ -1054,7 +1385,7 @@ const AppCreator = (props) => {
<div style={{color: "white"}}>
<h2>Test</h2>
Test an action to see whether it performs in an expected way.
<a href="/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}>&nbsp;Click here to learn more about testing</a>.
<Link target="_blank" to="/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}>&nbsp;TBD: Click here to learn more about testing</Link>.
<div>
Test :)
</div>
@@ -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) => {
// <img src={file} id="logo" style={{width: "100%", height: "100%"}} />
const imageData = file.length > 0 ? file : fileBase64
const imageInfo = <img src={imageData} alt="Click to upload an image" id="logo" style={{width: 174, height: 174}} />
const imageInfo = <img src={imageData} alt="Click to upload an image" id="logo" style={{maxWidth: 174, maxHeight: 174,}} />
// Random names for type & autoComplete. Didn't research :^)
const landingpageDataBrowser =
<div style={{paddingBottom: 100, color: "white",}}>
<Breadcrumbs aria-label="breadcrumb" separator="" style={{color: "white",}}>
<Link to="/apps" style={{textDecoration: "none", color: "inherit",}}>
<h2 style={{color: "rgba(255,255,255,0.5)"}}>
<AppsIcon style={{marginRight: 10}} />
Apps
</h2>
</Link>
<h2>
{name}
</h2>
</Breadcrumbs>
<Paper style={boxStyle}>
<h2 style={{marginBottom: "10px", color: "white"}}>General information</h2>
<a href="/docs/apps#create" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</a>
<Link target="_blank" to="/docs/apps#create_openapi_app" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</Link>
<div style={{color: "white", flex: "1", display: "flex", flexDirection: "row"}}>
<Tooltip title="Click to edit the app's image" placement="bottom">
<div style={{flex: "1", margin: 10, border: "1px solid #f85a3e", cursor: "pointer", backgroundColor: inputColor, maxWidth: 174, maxHeight: 174}} onClick={() => {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={<div style={{color:"white", marginBottom: "2px",}}>Must start with http(s):// and CANT end with /. </div>}
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)
}}
/>
<FormControl style={{marginTop: "15px",}} variant="outlined">
<h5 style={{marginBottom: "10px", color: "white",}}>Authentication</h5>
@@ -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 => (
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data}>
@@ -1219,7 +1583,7 @@ const AppCreator = (props) => {
}}>
Save
</Button>
{errorCode}
{errorCode.length > 0 ? `Error: ${errorCode}` : null}
</Paper>
</div>
+112 -60
View File
@@ -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) => {
<Paper square style={paperAppStyle} onClick={() => {
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) => {
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
{data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ?
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}} onClick={() => {downloadApp(data)}}>
{/*
<Tooltip title={"Download"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
<CloudDownload />
</Tooltip>
*/}
</Grid>
: null}
</Paper>
)
}
@@ -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 ?
<Button
onClick={() => {downloadApp(selectedApp)}}
variant="outlined"
component="label"
color="primary"
style={{marginTop: 10, marginRight: 8}}
>
<CloudDownload />
</Button>
: null
var editButton = selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ?
<Link to={editUrl} style={{textDecoration: "none"}}>
<Button
variant="outlined"
component="label"
color="primary"
style={{marginTop: "10px"}}
style={{marginTop: 10, marginRight: 10,}}
>
Edit app
<EditIcon />
</Button></Link> : 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
</Button></Link> : null
@@ -322,7 +355,7 @@ const Apps = (props) => {
deleteApp(selectedApp.id)
}}
>
Delete app
<DeleteIcon />
</Button> : null
var imageline = selectedApp.large_image === undefined || selectedApp.large_image.length === 0 ?
@@ -343,6 +376,7 @@ const Apps = (props) => {
</div>
</div>
{activateButton}
{downloadButton}
{editButton}
{deleteButton}
<Divider style={{marginBottom: "10px", marginTop: "10px", backgroundColor: dividerColor}}/>
@@ -352,36 +386,42 @@ const Apps = (props) => {
<div style={{marginTop: 15, marginBottom: 15}}>
<b>Actions</b>
<Select
fullWidth
value={selectedAction}
onChange={(event) => {
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
{selectedApp.actions !== null && selectedApp.actions.length > 0 ?
<Select
fullWidth
value={selectedAction}
onChange={(event) => {
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 (
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data}>
{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 (
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data}>
{newActionname}
</MenuItem>
)
})}
</Select>
</MenuItem>
)
})}
</Select>
:
<div style={{marginTop: 10}}>
There are no actions defined for this app.
</div>
}
</div>
{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 ?
<div style={{maxWidth: 1366, margin: "auto",}}>
<div style={appViewStyle}>
<div style={{flex: "1", marginLeft: 10, marginRight: 10}}>
<h2>Upload</h2>
<div style={{marginTop: 20}}/>
<div>
<Breadcrumbs aria-label="breadcrumb" separator="" style={{color: "white",}}>
<Link to="/apps" style={{textDecoration: "none", color: "inherit",}}>
<h2 style={{color: "rgba(255,255,255,0.5)"}}>
<AppsIcon style={{marginRight: 10}} />
App upload
</h2>
</Link>
{selectedApp.activated && selectedApp.private_id !== undefined && selectedApp.private_id.length > 0 && selectedApp.generated ?
<Link to={`/apps/edit/${selectedApp.id}`} style={{textDecoration: "none", color: "inherit",}}>
<h2>
{selectedApp.name}
</h2>
</Link>
: null}
</Breadcrumbs>
<UploadView/>
</div>
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "100%", width: "1px", backgroundColor: dividerColor}}/>
@@ -484,7 +537,10 @@ const Apps = (props) => {
<FormControlLabel
style={{color: "white", marginBottom: "0px", marginTop: "10px"}}
label=<div style={{color: "white"}}>Search OpenAPI</div>
control={<Switch checked={searchBackend} onChange={() => {setSearchBackend(!searchBackend)}} />}
control={<Switch checked={searchBackend} onChange={() => {
handleSearchChange("")
setSearchBackend(!searchBackend)}
} />}
/>
<Button
variant="outlined"
@@ -514,7 +570,7 @@ const Apps = (props) => {
color="primary"
placeholder={"Search apps"}
onChange={(event) => {
handleSearchChange(event)
handleSearchChange(event.target.value)
}}
/>
<div style={{marginTop: 15}}>
@@ -956,11 +1012,7 @@ const Apps = (props) => {
</div>
// Maybe use gridview or something, idk
return (
<div>
{loadedCheck}
</div>
)
return loadedCheck
}
export default Apps
+3 -3
View File
@@ -886,15 +886,15 @@ const Workflows = (props) => {
<div style={emptyWorkflowStyle}>
<Paper style={boxStyle}>
<div>
<h2>Welcome to Shuffle!</h2>
<h2>Welcome to Shuffle</h2>
</div>
<div>
<p>
<b>Shuffle</b> is a flexible, easy to use, automation framework allowing users to integrate their services and devices to reduce the amount of manual labor required for those tasks. <Link to="/docs/workflows" style={{textDecoration: "none", color: "#f85a3e"}}>Click here for more information.</Link>
<b>Shuffle</b> is a flexible, easy to use, automation platform allowing users to integrate their services and devices freely. It's made to significantly reduce the amount of manual labor, and is focused on security applications. <Link to="/docs/about" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more.</Link>
</p>
</div>
<div>
If you want to jump straight into it, click the following button to create your first workflow:
If you want to jump straight into it, click here to create your first workflow:
</div>
<div>
<Button color="primary" style={{marginTop: "20px",}} variant="outlined" onClick={() => setModalOpen(true)}>New workflow</Button>
+2
View File
@@ -35,6 +35,8 @@ const data = [{
'shape': 'square',
'background-color': '#213243',
'border-color': '#81c784',
'background-width': '100%',
'background-height': '100%',
},
},
{