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"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"cloud.google.com/go/storage"
@@ -365,6 +367,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)
@@ -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 {
action.Parameters = append(action.Parameters, curParam)
} 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 {
action.Parameters = append(action.Parameters, curParam)
} 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 {
action.Parameters = append(action.Parameters, curParam)
} 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 {
action.Parameters = append(action.Parameters, curParam)
} else {
@@ -1049,23 +1137,27 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
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{}
/*
WorkflowAppActionParameter{
Name: "body",
Description: "The body to use",
Multiline: true,
Required: false,
Example: `{"username": "test"}`,
Schema: SchemaDefinition{
Type: "string",
},
},
},
}
}
*/
if len(path.Post.Parameters) > 0 {
for _, param := range path.Post.Parameters {
if param.Value.Schema == nil {
continue
}
log.Printf("PARAM: %#v", param.Value)
curParam := WorkflowAppActionParameter{
Name: param.Value.Name,
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 {
action.Parameters = append(action.Parameters, curParam)
} 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 {
action.Parameters = append(action.Parameters, curParam)
} else {
@@ -1241,18 +1369,20 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
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{}
/*
WorkflowAppActionParameter{
Name: "body",
Description: "The body to use",
Multiline: true,
Required: false,
Example: `{"username": "test"}`,
Schema: SchemaDefinition{
Type: "string",
},
},
},
}
}
*/
if len(path.Put.Parameters) > 0 {
for _, param := range path.Put.Parameters {
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 {
action.Parameters = append(action.Parameters, curParam)
} else {
+1 -5
View File
@@ -5543,10 +5543,6 @@ func verifySwagger(resp http.ResponseWriter, request *http.Request) {
}
api.Owner = user.Id
if len(test.Image) > 0 {
api.SmallImage = test.Image
api.LargeImage = test.Image
}
err = dumpApi(basePath, api)
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)
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
+1
View File
@@ -2744,6 +2744,7 @@ func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) {
resp.Write([]byte(`{"success": false}`))
return
}
log.Printf("API: %#v", parsedApi)
//log.Printf("Parsed API: %#v", parsedApi)
if len(parsedApi.ID) > 0 {
+1
View File
@@ -51,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",
+340 -62
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';
@@ -18,6 +19,7 @@ import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import ErrorOutline from '@material-ui/icons/ErrorOutline';
import { useAlert } from "react-alert";
import words from "shellwords"
const surfaceColor = "#27292D"
const inputColor = "#383B40"
@@ -55,7 +57,118 @@ const useStyles = makeStyles({
notchedOutline: {
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 :|
const AppCreator = (props) => {
@@ -144,13 +257,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"
@@ -164,9 +277,7 @@ const AppCreator = (props) => {
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 +295,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 +310,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 +338,20 @@ 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
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) {
setContact(data.info.contact)
}
@@ -243,7 +360,7 @@ const AppCreator = (props) => {
setBaseUrl(data.servers[0].url)
}
console.log(data)
// This is annoying (:
var securitySchemes = data.components.securityDefinitions
@@ -295,9 +412,6 @@ const AppCreator = (props) => {
"errors": [],
}
//console.log(`${path}: ${method}`);
//console.log(methodvalue)
for (var key in methodvalue.parameters) {
const parameter = methodvalue.parameters[key]
if (parameter.in === "query") {
@@ -323,12 +437,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
@@ -336,6 +450,7 @@ const AppCreator = (props) => {
const host = splitBase[2]
const schemes = [splitBase[0]]
const basePath = "/"+(splitBase.slice(3, )).join("/")
console.log("IMAGE: ", fileBase64)
const data = {
"swagger": "3.0",
@@ -343,6 +458,7 @@ const AppCreator = (props) => {
"title": name,
"description": description,
"version": "1.0",
"x-logo": fileBase64,
},
"servers": [{"url": baseUrl}],
"host": host,
@@ -353,7 +469,6 @@ const AppCreator = (props) => {
"components": {
"securitySchemes": {},
},
"image": fileBase64,
"id": props.match.params.appid,
"securityDefinitions": {},
}
@@ -368,7 +483,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 +555,23 @@ const AppCreator = (props) => {
//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") {
@@ -462,7 +594,7 @@ const AppCreator = (props) => {
}
fetch(globalUrl+"/api/v1/verify_openapi", {
method: 'POST',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
@@ -495,9 +627,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 +640,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,9 +728,9 @@ 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 style={{color: "white"}}>
<h4>API key</h4>
@@ -818,13 +950,30 @@ const AppCreator = (props) => {
}
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 +982,75 @@ 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)
}
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 =
@@ -878,7 +1078,7 @@ 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
@@ -891,7 +1091,14 @@ const AppCreator = (props) => {
margin="normal"
variant="outlined"
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}
InputProps={{
classes: {
@@ -951,7 +1158,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 +1173,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 +1183,55 @@ 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.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}
@@ -1029,7 +1285,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={() => {
@@ -1054,7 +1310,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 +1327,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 +1361,14 @@ 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={{}} />
// Random names for type & autoComplete. Didn't research :^)
const landingpageDataBrowser =
<div style={{paddingBottom: 100, color: "white",}}>
<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 +1385,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 +1452,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 +1474,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}>
+42 -31
View File
@@ -117,10 +117,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())
@@ -188,6 +190,7 @@ const Apps = (props) => {
boxColor = "green"
}
console.log("IMG: ", data.large_image)
var imageline = data.large_image.length === 0 ?
<img alt={data.title} style={{width: 100, height: 100}} />
:
@@ -222,9 +225,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({})
}
}
}}>
@@ -352,36 +357,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 ?