Updated OpenAPI app creation process

This commit is contained in:
frikky
2020-05-30 11:49:23 +02:00
parent 186559ef13
commit 8fe0244401
6 changed files with 555 additions and 120 deletions
+170 -22
View File
@@ -4,12 +4,14 @@ import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strconv"
"strings" "strings"
"cloud.google.com/go/storage" "cloud.google.com/go/storage"
@@ -365,6 +367,20 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (WorkflowApp, []stri
// Setting up security schemes // Setting up security schemes
extraParameters := []WorkflowAppActionParameter{} 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 securitySchemes := swagger.Components.SecuritySchemes
if securitySchemes != nil { if securitySchemes != nil {
//log.Printf("%#v", securitySchemes) //log.Printf("%#v", securitySchemes)
@@ -719,6 +735,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 { if param.Value.Required {
action.Parameters = append(action.Parameters, curParam) action.Parameters = append(action.Parameters, curParam)
} else { } else {
@@ -809,6 +843,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 { if param.Value.Required {
action.Parameters = append(action.Parameters, curParam) action.Parameters = append(action.Parameters, curParam)
} else { } else {
@@ -894,6 +946,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 { if param.Value.Required {
action.Parameters = append(action.Parameters, curParam) action.Parameters = append(action.Parameters, curParam)
} else { } else {
@@ -979,6 +1049,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 { if param.Value.Required {
action.Parameters = append(action.Parameters, curParam) action.Parameters = append(action.Parameters, curParam)
} else { } else {
@@ -1049,23 +1137,27 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
firstQuery = true firstQuery = true
optionalQueries := []string{} optionalQueries := []string{}
parameters := []string{} parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{ optionalParameters := []WorkflowAppActionParameter{}
WorkflowAppActionParameter{ /*
Name: "body", WorkflowAppActionParameter{
Description: "The body to use", Name: "body",
Multiline: true, Description: "The body to use",
Required: false, Multiline: true,
Example: `{"username": "test"}`, Required: false,
Schema: SchemaDefinition{ Example: `{"username": "test"}`,
Type: "string", Schema: SchemaDefinition{
Type: "string",
},
}, },
}, }
} */
if len(path.Post.Parameters) > 0 { if len(path.Post.Parameters) > 0 {
for _, param := range path.Post.Parameters { for _, param := range path.Post.Parameters {
if param.Value.Schema == nil { if param.Value.Schema == nil {
continue continue
} }
log.Printf("PARAM: %#v", param.Value)
curParam := WorkflowAppActionParameter{ curParam := WorkflowAppActionParameter{
Name: param.Value.Name, Name: param.Value.Name,
Description: param.Value.Description, Description: param.Value.Description,
@@ -1076,6 +1168,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 { if param.Value.Required {
action.Parameters = append(action.Parameters, curParam) action.Parameters = append(action.Parameters, curParam)
} else { } else {
@@ -1172,6 +1282,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 { if param.Value.Required {
action.Parameters = append(action.Parameters, curParam) action.Parameters = append(action.Parameters, curParam)
} else { } else {
@@ -1241,18 +1369,20 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
firstQuery = true firstQuery = true
optionalQueries := []string{} optionalQueries := []string{}
parameters := []string{} parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{ optionalParameters := []WorkflowAppActionParameter{}
WorkflowAppActionParameter{ /*
Name: "body", WorkflowAppActionParameter{
Description: "The body to use", Name: "body",
Multiline: true, Description: "The body to use",
Required: false, Multiline: true,
Example: `{"username": "test"}`, Required: false,
Schema: SchemaDefinition{ Example: `{"username": "test"}`,
Type: "string", Schema: SchemaDefinition{
Type: "string",
},
}, },
}, }
} */
if len(path.Put.Parameters) > 0 { if len(path.Put.Parameters) > 0 {
for _, param := range path.Put.Parameters { for _, param := range path.Put.Parameters {
if param.Value.Schema == nil { if param.Value.Schema == nil {
@@ -1268,6 +1398,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 { if param.Value.Required {
action.Parameters = append(action.Parameters, curParam) action.Parameters = append(action.Parameters, curParam)
} else { } else {
+1 -5
View File
@@ -5543,10 +5543,6 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) {
} }
api.Owner = user.Id api.Owner = user.Id
if len(test.Image) > 0 {
api.SmallImage = test.Image
api.LargeImage = test.Image
}
err = dumpApi(basePath, api) err = dumpApi(basePath, api)
if err != nil { if err != nil {
@@ -5569,7 +5565,7 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) {
identifier = strings.Replace(identifier, " ", "-", -1) identifier = strings.Replace(identifier, " ", "-", -1)
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 // Now that the baseline is setup, we need to make it into a cloud function
// 1. Upload the API to datastore for use // 1. Upload the API to datastore for use
+1
View File
@@ -2744,6 +2744,7 @@ func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(`{"success": false}`)) resp.Write([]byte(`{"success": false}`))
return return
} }
log.Printf("API: %#v", parsedApi)
//log.Printf("Parsed API: %#v", parsedApi) //log.Printf("Parsed API: %#v", parsedApi)
if len(parsedApi.ID) > 0 { if len(parsedApi.ID) > 0 {
+1
View File
@@ -51,6 +51,7 @@
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
"react-scripts": "^2.1.8", "react-scripts": "^2.1.8",
"reactstrap": "^7.1.0", "reactstrap": "^7.1.0",
"shellwords": "^0.1.1",
"simplebar": "^4.2.3", "simplebar": "^4.2.3",
"styled-components": "^4.4.0", "styled-components": "^4.4.0",
"websocket": "^1.0.30", "websocket": "^1.0.30",
+340 -62
View File
@@ -2,6 +2,7 @@ import React, {useState, useEffect} from 'react';
import { makeStyles } from '@material-ui/styles'; import { makeStyles } from '@material-ui/styles';
import {BrowserView, MobileView} from "react-device-detect"; import {BrowserView, MobileView} from "react-device-detect";
import {Link} from 'react-router-dom';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Divider from '@material-ui/core/Divider'; import Divider from '@material-ui/core/Divider';
@@ -18,6 +19,7 @@ import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import ErrorOutline from '@material-ui/icons/ErrorOutline'; import ErrorOutline from '@material-ui/icons/ErrorOutline';
import { useAlert } from "react-alert"; import { useAlert } from "react-alert";
import words from "shellwords"
const surfaceColor = "#27292D" const surfaceColor = "#27292D"
const inputColor = "#383B40" const inputColor = "#383B40"
@@ -55,7 +57,118 @@ const useStyles = makeStyles({
notchedOutline: { notchedOutline: {
borderColor: "#f85a3e !important" borderColor: "#f85a3e !important"
}, },
}); })
function 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
}, [])
}
function parseField(s) {
return s.split(/: (.+)/)
}
function isURL(s) {
return /^https?:\/\//.test(s)
}
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 :| // Should be different if logged in :|
const AppCreator = (props) => { const AppCreator = (props) => {
@@ -144,13 +257,13 @@ const AppCreator = (props) => {
const handleEditApp = () => { const handleEditApp = () => {
fetch(globalUrl+"/api/v1/apps/"+props.match.params.appid+"/config", { fetch(globalUrl+"/api/v1/apps/"+props.match.params.appid+"/config", {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
}, },
credentials: "include", credentials: "include",
}) })
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
window.location.pathname = "/apps" window.location.pathname = "/apps"
@@ -164,9 +277,7 @@ const AppCreator = (props) => {
alert.error("Failed to get the app") alert.error("Failed to get the app")
} else { } else {
const data = JSON.parse(responseJson.body) const data = JSON.parse(responseJson.body)
console.log("LOADED IMAGE: ", data.image) parseIncomingOpenapiData(data)
setFileBase64(data.image)
parseOpenapiData(data)
} }
}) })
.catch(error => { .catch(error => {
@@ -184,13 +295,13 @@ const AppCreator = (props) => {
} }
fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), { fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
}, },
credentials: "include", credentials: "include",
}) })
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
throw new Error("NOT 200 :O") throw new Error("NOT 200 :O")
@@ -199,12 +310,12 @@ const AppCreator = (props) => {
return response.json() return response.json()
}) })
.then((responseJson) => { .then((responseJson) => {
setIsAppLoaded(true) setIsAppLoaded(true)
if (!responseJson.success) { if (!responseJson.success) {
alert.error("Failed to verify") alert.error("Failed to verify")
} else { } else {
const data = JSON.parse(responseJson.body) const data = JSON.parse(responseJson.body)
parseOpenapiData(data) parseIncomingOpenapiData(data)
} }
}) })
.catch(error => { .catch(error => {
@@ -227,14 +338,20 @@ const AppCreator = (props) => {
} }
// Sets the data up as it should be at later points // 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) setBasedata(data)
setName(data.info.title) setName(data.info.title)
setDescription(data.info.description) setDescription(data.info.description)
document.title = "Apps - "+data.info.title document.title = "Apps - "+data.info.title
console.log(data.info["x-logo"])
if (data.info !== null && data.info !== undefined && data.info["x-logo"] !== undefined) {
setFileBase64(data.info["x-logo"])
console.log("IMG: ", data.inf0["x-logo"])
}
if (data.info.contact != undefined) { if (data.info.contact != undefined) {
setContact(data.info.contact) setContact(data.info.contact)
} }
@@ -243,7 +360,7 @@ const AppCreator = (props) => {
setBaseUrl(data.servers[0].url) setBaseUrl(data.servers[0].url)
} }
console.log(data)
// This is annoying (: // This is annoying (:
var securitySchemes = data.components.securityDefinitions var securitySchemes = data.components.securityDefinitions
@@ -295,9 +412,6 @@ const AppCreator = (props) => {
"errors": [], "errors": [],
} }
//console.log(`${path}: ${method}`);
//console.log(methodvalue)
for (var key in methodvalue.parameters) { for (var key in methodvalue.parameters) {
const parameter = methodvalue.parameters[key] const parameter = methodvalue.parameters[key]
if (parameter.in === "query") { if (parameter.in === "query") {
@@ -323,12 +437,12 @@ const AppCreator = (props) => {
} }
} }
console.log(newActions)
setActions(newActions) setActions(newActions)
} }
// Saving the app that's been configured.
const submitApp = () => { const submitApp = () => {
alert.info("Uploading private app " + name) alert.info("Uploading and building app " + name)
setErrorCode("") setErrorCode("")
// Format the information // Format the information
@@ -336,6 +450,7 @@ const AppCreator = (props) => {
const host = splitBase[2] const host = splitBase[2]
const schemes = [splitBase[0]] const schemes = [splitBase[0]]
const basePath = "/"+(splitBase.slice(3, )).join("/") const basePath = "/"+(splitBase.slice(3, )).join("/")
console.log("IMAGE: ", fileBase64)
const data = { const data = {
"swagger": "3.0", "swagger": "3.0",
@@ -343,6 +458,7 @@ const AppCreator = (props) => {
"title": name, "title": name,
"description": description, "description": description,
"version": "1.0", "version": "1.0",
"x-logo": fileBase64,
}, },
"servers": [{"url": baseUrl}], "servers": [{"url": baseUrl}],
"host": host, "host": host,
@@ -353,7 +469,6 @@ const AppCreator = (props) => {
"components": { "components": {
"securitySchemes": {}, "securitySchemes": {},
}, },
"image": fileBase64,
"id": props.match.params.appid, "id": props.match.params.appid,
"securityDefinitions": {}, "securityDefinitions": {},
} }
@@ -368,7 +483,7 @@ const AppCreator = (props) => {
data.info["contact"] = contact data.info["contact"] = contact
} }
console.log("LOADED IMAGE: ", data.image) //console.log("LOADED IMAGE: ", data.image)
for (var key in actions) { for (var key in actions) {
const item = actions[key] const item = actions[key]
@@ -440,6 +555,23 @@ const AppCreator = (props) => {
//console.log(queryitem) //console.log(queryitem)
} }
} }
if (item.body.length > 0) {
console.log("HANDLE BODY!")
newitem = {
"in": "body",
"name": "body",
"multiline": true,
"description": "Generated by shuffler.io OpenAPI",
"required": false,
"example": item.body,
"schema": {
"type": "string",
},
}
data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
}
} }
if (authenticationOption === "API key") { if (authenticationOption === "API key") {
@@ -462,7 +594,7 @@ const AppCreator = (props) => {
} }
fetch(globalUrl+"/api/v1/verify_openapi", { fetch(globalUrl+"/api/v1/verify_openapi", {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json', 'Accept': 'application/json',
@@ -495,9 +627,9 @@ const AppCreator = (props) => {
} }
const bearerAuth = authenticationOption === "Bearer auth" ? const bearerAuth = authenticationOption === "Bearer auth" ?
<div> <div style={{color: "white"}}>
<h4> <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 Bearer auth
</a> </a>
</h4> </h4>
@@ -508,9 +640,9 @@ const AppCreator = (props) => {
// Basicauth // Basicauth
const basicAuth = authenticationOption === "Basic auth" ? const basicAuth = authenticationOption === "Basic auth" ?
<div> <div style={{color: "white"}}>
<h4> <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 Basic authentication
</a> </a>
</h4> </h4>
@@ -596,9 +728,9 @@ const AppCreator = (props) => {
setActions(actions) setActions(actions)
} }
console.log("Option: ", authenticationOption) //console.log("Option: ", authenticationOption)
console.log("Location: ", parameterLocation) //console.log("Location: ", parameterLocation)
console.log("Name: ", parameterName) //console.log("Name: ", parameterName)
const apiKey = authenticationOption === "API key" ? const apiKey = authenticationOption === "API key" ?
<div style={{color: "white"}}> <div style={{color: "white"}}>
<h4>API key</h4> <h4>API key</h4>
@@ -818,13 +950,30 @@ const AppCreator = (props) => {
} }
const UrlPathParameters = () => { const UrlPathParameters = () => {
var paths = []
var queries = []
if (urlPath.includes("{") && urlPath.includes("}")) { if (urlPath.includes("{") && urlPath.includes("}")) {
var values = []
var tmpWord = "" var tmpWord = ""
var record = false var record = false
var query = false
for (var key in urlPath) { for (var key in urlPath) {
if (urlPath[key] === "?") {
query = true
}
if (urlPath[key] === "}") { if (urlPath[key] === "}") {
values.push(tmpWord) if (tmpWord === parameterName) {
tmpWord = ""
record = false
continue
} else if (query) {
queries.push(tmpWord)
} else {
paths.push(tmpWord)
}
tmpWord = "" tmpWord = ""
record = false record = false
} }
@@ -833,24 +982,75 @@ const AppCreator = (props) => {
tmpWord += urlPath[key] tmpWord += urlPath[key]
} }
if (urlPath[key] === "{" && urlPath[key-1] === "/") { //if (urlPath[key] === "{" && urlPath[key-1] === "/") {
if (urlPath[key] === "{") {
record = true 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)
}
console.log("QUERIES: ", queries)
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 = const newActionModal =
@@ -878,7 +1078,7 @@ const AppCreator = (props) => {
<FormControl style={{backgroundColor: surfaceColor, color: "white",}}> <FormControl style={{backgroundColor: surfaceColor, color: "white",}}>
<DialogTitle><div style={{color: "white"}}>New action</div></DialogTitle> <DialogTitle><div style={{color: "white"}}>New action</div></DialogTitle>
<DialogContent> <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"}}/> <div style={{marginTop: "15px"}}/>
Name Name
<TextField <TextField
@@ -891,7 +1091,14 @@ const AppCreator = (props) => {
margin="normal" margin="normal"
variant="outlined" variant="outlined"
defaultValue={currentAction["name"]} defaultValue={currentAction["name"]}
onChange={e => setActionField("name", e.target.value)} onChange={e => {
// Fix basic issues in frontend. Python functions run a-zA-Z0-9_
const regex = /[A-Z-a-z0-9 _]/g;
const found = e.target.value.match(regex);
if (found !== null) {
setActionField("name", found.join(""))
}
}}
key={currentAction} key={currentAction}
InputProps={{ InputProps={{
classes: { classes: {
@@ -951,7 +1158,7 @@ const AppCreator = (props) => {
))} ))}
</Select> </Select>
<div style={{marginTop: "15px"}} /> <div style={{marginTop: "15px"}} />
URL path URL path / Curl statement
<TextField <TextField
required required
style={{flex: "1", marginRight: "15px", marginTop: "5px", backgroundColor: inputColor}} style={{flex: "1", marginRight: "15px", marginTop: "5px", backgroundColor: inputColor}}
@@ -966,7 +1173,7 @@ const AppCreator = (props) => {
setUrlPath(e.target.value) setUrlPath(e.target.value)
console.log(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={{ InputProps={{
classes: { classes: {
notchedOutline: classes.notchedOutline, notchedOutline: classes.notchedOutline,
@@ -976,6 +1183,55 @@ const AppCreator = (props) => {
color: "white", 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.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 /> <UrlPathParameters />
{loopQueries} {loopQueries}
@@ -1029,7 +1285,7 @@ const AppCreator = (props) => {
<div style={{color: "white"}}> <div style={{color: "white"}}>
<h2>Actions</h2> <h2>Actions</h2>
Actions are the tasks performed by an app. Read more about actions and apps 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> <div>
{loopActions} {loopActions}
<Button color="primary" style={{marginTop: "20px", borderRadius: "0px"}} variant="outlined" onClick={() => { <Button color="primary" style={{marginTop: "20px", borderRadius: "0px"}} variant="outlined" onClick={() => {
@@ -1054,7 +1310,7 @@ const AppCreator = (props) => {
<div style={{color: "white"}}> <div style={{color: "white"}}>
<h2>Test</h2> <h2>Test</h2>
Test an action to see whether it performs in an expected way. 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> <div>
Test :) Test :)
</div> </div>
@@ -1071,14 +1327,23 @@ const AppCreator = (props) => {
if (file !== "") { if (file !== "") {
const img = document.getElementById('logo') const img = document.getElementById('logo')
var canvas = document.createElement('canvas') var canvas = document.createElement('canvas')
canvas.width = 174
canvas.height = 174
var ctx = canvas.getContext('2d') var ctx = canvas.getContext('2d')
img.onload = function() { img.onload = function() {
// img, x, y, width, height // 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() const canvasUrl = canvas.toDataURL()
console.log(canvasUrl)
if (canvasUrl !== fileBase64) { if (canvasUrl !== fileBase64) {
console.log("SET URL TO: ", canvasUrl)
setFileBase64(canvasUrl) setFileBase64(canvasUrl)
} }
} }
@@ -1096,14 +1361,14 @@ const AppCreator = (props) => {
// <img src={file} id="logo" style={{width: "100%", height: "100%"}} /> // <img src={file} id="logo" style={{width: "100%", height: "100%"}} />
const imageData = file.length > 0 ? file : fileBase64 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={{}} />
// Random names for type & autoComplete. Didn't research :^) // Random names for type & autoComplete. Didn't research :^)
const landingpageDataBrowser = const landingpageDataBrowser =
<div style={{paddingBottom: 100, color: "white",}}> <div style={{paddingBottom: 100, color: "white",}}>
<Paper style={boxStyle}> <Paper style={boxStyle}>
<h2 style={{marginBottom: "10px", color: "white"}}>General information</h2> <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"}}> <div style={{color: "white", flex: "1", display: "flex", flexDirection: "row"}}>
<Tooltip title="Click to edit the app's image" placement="bottom"> <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()}}> <div style={{flex: "1", margin: 10, border: "1px solid #f85a3e", cursor: "pointer", backgroundColor: inputColor, maxWidth: 174, maxHeight: 174}} onClick={() => {upload.click()}}>
@@ -1120,11 +1385,11 @@ const AppCreator = (props) => {
fullWidth={true} fullWidth={true}
placeholder="Name" placeholder="Name"
type="name" type="name"
id="standard-required" id="standard-required"
margin="normal" margin="normal"
variant="outlined" variant="outlined"
value={name} value={name}
onChange={e => setName(e.target.value)} onChange={e => setName(e.target.value)}
color="primary" color="primary"
InputProps={{ InputProps={{
style:{ style:{
@@ -1187,6 +1452,19 @@ const AppCreator = (props) => {
helperText={<div style={{color:"white", marginBottom: "2px",}}>Must start with http(s):// and CANT end with /. </div>} helperText={<div style={{color:"white", marginBottom: "2px",}}>Must start with http(s):// and CANT end with /. </div>}
placeholder="https://api.example.com" placeholder="https://api.example.com"
onChange={e => setBaseUrl(e.target.value)} 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"> <FormControl style={{marginTop: "15px",}} variant="outlined">
<h5 style={{marginBottom: "10px", color: "white",}}>Authentication</h5> <h5 style={{marginBottom: "10px", color: "white",}}>Authentication</h5>
@@ -1196,7 +1474,7 @@ const AppCreator = (props) => {
setAuthenticationOption(e.target.value) setAuthenticationOption(e.target.value)
}} }}
value={authenticationOption} value={authenticationOption}
style={{backgroundColor: inputColor, paddingLeft: "10px", color: "white", height: "50px"}} style={{backgroundColor: inputColor, color: "white", height: "50px"}}
> >
{authenticationOptions.map(data => ( {authenticationOptions.map(data => (
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data}> <MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data}>
+42 -31
View File
@@ -117,10 +117,12 @@ const Apps = (props) => {
setFilteredApps(responseJson) setFilteredApps(responseJson)
if (responseJson.length > 0) { if (responseJson.length > 0) {
setSelectedApp(responseJson[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]) setSelectedAction(responseJson[0].actions[0])
} else {
setSelectedAction({})
} }
} }
}) })
.catch(error => { .catch(error => {
alert.error(error.toString()) alert.error(error.toString())
@@ -188,6 +190,7 @@ const Apps = (props) => {
boxColor = "green" boxColor = "green"
} }
console.log("IMG: ", data.large_image)
var imageline = data.large_image.length === 0 ? var imageline = data.large_image.length === 0 ?
<img alt={data.title} style={{width: 100, height: 100}} /> <img alt={data.title} style={{width: 100, height: 100}} />
: :
@@ -222,9 +225,11 @@ const Apps = (props) => {
<Paper square style={paperAppStyle} onClick={() => { <Paper square style={paperAppStyle} onClick={() => {
if (selectedApp.id !== data.id) { if (selectedApp.id !== data.id) {
setSelectedApp(data) setSelectedApp(data)
if (data.actions.length > 0) { if (data.actions !== undefined && data.actions !== null && data.actions.length > 0) {
console.log(data.actions[0]) console.log(data.actions[0])
setSelectedAction(data.actions[0]) setSelectedAction(data.actions[0])
} else {
setSelectedAction({})
} }
} }
}}> }}>
@@ -352,36 +357,42 @@ const Apps = (props) => {
<div style={{marginTop: 15, marginBottom: 15}}> <div style={{marginTop: 15, marginBottom: 15}}>
<b>Actions</b> <b>Actions</b>
<Select {selectedApp.actions !== null && selectedApp.actions.length > 0 ?
fullWidth <Select
value={selectedAction} fullWidth
onChange={(event) => { value={selectedAction}
setSelectedAction(event.target.value) onChange={(event) => {
}} setSelectedAction(event.target.value)
style={{backgroundColor: inputColor, color: "white", height: "50px"}} }}
SelectDisplayProps={{ style={{backgroundColor: inputColor, color: "white", height: "50px"}}
style: { SelectDisplayProps={{
marginLeft: 10, style: {
} marginLeft: 10,
}} }
> }}
{selectedApp.actions.map(data => { >
var newActionname = data.label !== undefined && data.label.length > 0 ? data.label : data.name {selectedApp.actions.map(data => {
var newActionname = data.label !== undefined && data.label.length > 0 ? data.label : data.name
// ROFL FIXME - loop // ROFL FIXME - loop
newActionname = newActionname.replace("_", " ") newActionname = newActionname.replace("_", " ")
newActionname = newActionname.replace("_", " ") newActionname = newActionname.replace("_", " ")
newActionname = newActionname.replace("_", " ") newActionname = newActionname.replace("_", " ")
newActionname = newActionname.replace("_", " ") newActionname = newActionname.replace("_", " ")
newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1) newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1)
return ( return (
<MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data}> <MenuItem style={{backgroundColor: inputColor, color: "white"}} value={data}>
{newActionname} {newActionname}
</MenuItem> </MenuItem>
) )
})} })}
</Select> </Select>
:
<div style={{marginTop: 10}}>
There are no actions defined for this app.
</div>
}
</div> </div>
{selectedAction.parameters !== undefined && selectedAction.parameters !== null ? {selectedAction.parameters !== undefined && selectedAction.parameters !== null ?