Merge pull request #59 from frikky/dev

Loads of bugfixes and feature improvements for workflows and apps
This commit is contained in:
Frikky
2020-06-13 23:24:50 +09:00
committed by GitHub
18 changed files with 1140 additions and 419 deletions
+7 -3
View File
@@ -3,12 +3,16 @@
**It's in BETA** - [Get in touch](https://shuffler.io/contact), send a mail to [frikky@shuffler.io](mailto:frikky@shuffler.io) or poke me on twitter [@frikkylikeme](https://twitter.com/frikkylikeme)
![Example Shuffle webhook integration](shuffle_webhook.png)
![Example Shuffle webhook integration](https://github.com/frikky/Shuffle/blob/master/frontend/src/assets/img/shuffle_webhook.png)
## Try it
* Self-hosted: Check out the [installation guide](https://github.com/frikky/shuffle/blob/master/install-guide.md).
## Getting started
* Self-hosted: Check out the [installation guide](https://github.com/frikky/shuffle/blob/master/install-guide.md) and [getting started](https://shuffler.io/docs/getting_started)
* Cloud: Register at https://shuffler.io/register and get cooking (there are some differences!)
## Blogposts
[1. Introducing Shuffle](https://medium.com/security-operation-capybara/introducing-shuffle-an-open-source-soar-platform-part-1-58a529de7d12)
[2. Getting started with Shuffle](https://medium.com/security-operation-capybara/getting-started-with-shuffle-an-open-source-soar-platform-part-2-1d7c67a64244)
## Related repositories
* Apps: https://github.com/frikky/shuffle-apps
* Workflows: https://github.com/frikky/shuffle-workflows (empty)
+214 -20
View File
@@ -242,6 +242,7 @@ func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) {
return appPath, nil
}
// This function generates the python code that's being used.
func makePythoncode(swagger *openapi3.Swagger, name, url, method string, parameters, optionalQueries []string) (string, string) {
method = strings.ToLower(method)
queryString := ""
@@ -259,9 +260,6 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
}
}
// How to add authentication?
// I think it should be like:
// async def(self, auth, baseurl, data):
// api.Authentication.Parameters[0].Value = "BearerAuth"
authenticationParameter := ""
authenticationSetup := ""
@@ -270,14 +268,15 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
if swagger.Components.SecuritySchemes != nil {
if swagger.Components.SecuritySchemes["BearerAuth"] != nil {
authenticationParameter = ", apikey"
authenticationSetup = "headers[\"Authorization\"] = f\"Bearer {apikey}\""
authenticationSetup = "if apikey != \" \": headers[\"Authorization\"] = f\"Bearer {apikey}\""
} else if swagger.Components.SecuritySchemes["BasicAuth"] != nil {
authenticationParameter = ", username, password"
authenticationAddin = ", auth=(username, password)"
} else if swagger.Components.SecuritySchemes["ApiKeyAuth"] != nil {
authenticationParameter = ", apikey"
if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "header" {
authenticationSetup = fmt.Sprintf("headers[\"%s\"] = apikey", swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
// This is a way to bypass apikeys by passing " "
authenticationSetup = fmt.Sprintf(`if apikey != " ": headers["%s"] = apikey`, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
} else if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "query" {
// This might suck lol
key := "?"
@@ -285,7 +284,7 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
key = "&"
}
authenticationSetup = fmt.Sprintf("url+=f\"%s%s={apikey}\"", key, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
authenticationSetup = fmt.Sprintf("if apikey != \" \": url+=f\"%s%s={apikey}\"", key, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
}
}
}
@@ -301,6 +300,23 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
urlInline = "{url}"
}
// Specific check for SSL verification
// This is critical for onprem stuff.
verifyParam := ""
verifyWrapper := ""
verifyAddin := ""
if len(swagger.Servers) == 0 {
verifyParam = ", verify=True"
verifyWrapper = `if type(ssl_verify) == str: ssl_verify = False if ssl_verify.lower() == "false" or ssl_verify == "0" else True`
verifyAddin = ", verify=ssl_verify"
} else {
if swagger.Servers[0].URL == "" {
verifyParam = ", ssl_verify=True"
verifyWrapper = `if type(ssl_verify) == str: ssl_verify = False if ssl_verify.lower() == "false" or ssl_verify == "0" else True`
verifyAddin = ", verify=ssl_verify"
}
}
if len(parameters) > 0 {
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
}
@@ -335,13 +351,14 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
// 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):
data := fmt.Sprintf(` async def %s(self%s%s%s%s%s%s):
headers={}
url=f"%s%s"
%s
%s
%s
%s
return requests.%s(url, headers=headers%s%s).text
return requests.%s(url, headers=headers%s%s%s).text
`,
functionname,
authenticationParameter,
@@ -349,16 +366,21 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
parameterData,
queryString,
bodyParameter,
verifyParam,
urlInline,
url,
verifyWrapper,
authenticationSetup,
queryData,
bodyFormatter,
method,
authenticationAddin,
bodyAddin,
verifyAddin,
)
log.Printf("CODE: %s", data)
//log.Println(data)
//log.Println(functionname)
return functionname, data
@@ -412,7 +434,6 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
//log.Printf("%s", j)
api.SmallImage = string(j)
api.LargeImage = string(j)
log.Printf("Set images!")
}
}
@@ -444,6 +465,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
Description: "The apikey to use",
Multiline: false,
Required: true,
Example: "The API key to use. Space = skip",
Schema: SchemaDefinition{
Type: "string",
},
@@ -460,6 +482,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
Description: "The apikey to use",
Multiline: false,
Required: true,
Example: "The API key to use. Space = skip",
Schema: SchemaDefinition{
Type: "string",
},
@@ -475,6 +498,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
Description: "The username to use",
Multiline: false,
Required: true,
Example: "The username to use",
Schema: SchemaDefinition{
Type: "string",
},
@@ -484,6 +508,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
Description: "The password to use",
Multiline: false,
Required: true,
Example: "The password to use",
Schema: SchemaDefinition{
Type: "string",
},
@@ -759,6 +784,32 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(swagger.Servers) == 0 {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
} else {
if swagger.Servers[0].URL == "" {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
}
}
if len(path.Connect.Parameters) > 0 {
for _, param := range path.Connect.Parameters {
if param.Value.Schema == nil || param.Value.In == "header" {
@@ -799,7 +850,6 @@ func handleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters [
}
if param.Value.In == "path" {
//log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
@@ -866,8 +916,33 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
// FIXME - remove this when authentication is properly introduced
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(swagger.Servers) == 0 {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify the SSL certificate request",
Multiline: false,
Required: false,
Example: "False - default=True",
Schema: SchemaDefinition{
Type: "string",
},
})
} else {
if swagger.Servers[0].URL == "" {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
}
}
if len(path.Get.Parameters) > 0 {
for _, param := range path.Get.Parameters {
if param.Value.Schema == nil || param.Value.In == "header" {
@@ -909,7 +984,6 @@ func handleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
}
if param.Value.In == "path" {
log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
@@ -976,6 +1050,31 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(swagger.Servers) == 0 {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
} else {
if swagger.Servers[0].URL == "" {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
}
}
if len(path.Head.Parameters) > 0 {
for _, param := range path.Head.Parameters {
if param.Value.Schema == nil || param.Value.In == "header" {
@@ -1016,7 +1115,6 @@ func handleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
}
if param.Value.In == "path" {
//log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
@@ -1082,6 +1180,31 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(swagger.Servers) == 0 {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
} else {
if swagger.Servers[0].URL == "" {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
}
}
if len(path.Delete.Parameters) > 0 {
for _, param := range path.Delete.Parameters {
if param.Value.Schema == nil || param.Value.In == "header" {
@@ -1122,7 +1245,6 @@ func handleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []
}
if param.Value.In == "path" {
//log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
@@ -1178,9 +1300,9 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
Parameters: extraParameters,
}
if path.Post.RequestBody != nil {
log.Printf("RequestBody: %#v", path.Post.RequestBody)
}
//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)
@@ -1191,6 +1313,31 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(swagger.Servers) == 0 {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
} else {
if swagger.Servers[0].URL == "" {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
}
}
if len(path.Post.Parameters) > 0 {
for _, param := range path.Post.Parameters {
@@ -1233,7 +1380,6 @@ func handlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wo
}
if param.Value.In == "path" {
//log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
@@ -1299,6 +1445,31 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(swagger.Servers) == 0 {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
} else {
if swagger.Servers[0].URL == "" {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
}
}
if len(path.Patch.Parameters) > 0 {
for _, param := range path.Patch.Parameters {
if param.Value.Schema == nil || param.Value.In == "header" {
@@ -1339,7 +1510,6 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W
}
if param.Value.In == "path" {
//log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
@@ -1405,6 +1575,31 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(swagger.Servers) == 0 {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
} else {
if swagger.Servers[0].URL == "" {
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
Name: "ssl_verify",
Description: "Check if you want to verify request",
Multiline: false,
Required: false,
Example: "True",
Schema: SchemaDefinition{
Type: "string",
},
})
}
}
if len(path.Put.Parameters) > 0 {
for _, param := range path.Put.Parameters {
@@ -1446,7 +1641,6 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
}
if param.Value.In == "path" {
//log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
+67 -6
View File
@@ -238,7 +238,7 @@ type AppInfo struct {
type ScheduleOld struct {
Id string `json:"id" datastore:"id"`
Seconds int `json:"seconds" datastore:"seconds"`
WorkflowId string `json:"workflow_id datastore:"workflow_id", `
WorkflowId string `json:"workflow_id" datastore:"workflow_id", `
Argument string `json:"argument" datastore:"argument"`
AppInfo AppInfo `json:"appinfo" datastore:"appinfo,noindex"`
Finished bool `json:"finished" finished:"id"`
@@ -1803,6 +1803,49 @@ func getUserCount() (int, error) {
return count, nil
}
func handleGetSchedules(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
return
}
user, err := handleApiAuthentication(resp, request)
if err != nil {
log.Printf("Api authentication failed in set new workflowhandler: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if user.Role != "admin" {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Admin required"}`))
return
}
ctx := context.Background()
schedules, err := getAllSchedules(ctx)
if err != nil {
log.Printf("Failed getting schedules: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Couldn't get schedules"}`))
return
}
newjson, err := json.Marshal(schedules)
if err != nil {
log.Printf("Failed unmarshal: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking environments"}`)))
return
}
//log.Printf("Existing environments: %s", string(newjson))
resp.WriteHeader(200)
resp.Write(newjson)
}
func handleGetEnvironments(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
@@ -2635,6 +2678,21 @@ func handleDeleteSchedule(resp http.ResponseWriter, request *http.Request) {
return
}
user, err := handleApiAuthentication(resp, request)
if err != nil {
log.Printf("Api authentication failed in set new workflowhandler: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// FIXME: IAM - Get workflow and check owner
if user.Role != "admin" {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Admin required"}`))
return
}
location := strings.Split(request.URL.String(), "/")
var workflowId string
@@ -2655,7 +2713,7 @@ func handleDeleteSchedule(resp http.ResponseWriter, request *http.Request) {
}
ctx := context.Background()
err := DeleteKey(ctx, "schedules", workflowId)
err = DeleteKey(ctx, "schedules", workflowId)
if err != nil {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "Can't delete"}`))
@@ -4648,7 +4706,7 @@ func handleDeleteOutlookSub(resp http.ResponseWriter, request *http.Request) {
ctx := context.Background()
workflow, err := getWorkflow(ctx, workflowId)
if err != nil {
log.Printf("Failed getting the workflow locally: %s", err)
log.Printf("Failed getting the workflow locally (delete outlook): %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
@@ -4782,7 +4840,7 @@ func createOutlookSub(resp http.ResponseWriter, request *http.Request) {
ctx := context.Background()
workflow, err := getWorkflow(ctx, workflowId)
if err != nil {
log.Printf("Failed getting the workflow locally: %s", err)
log.Printf("Failed getting the workflow locally (outlook sub): %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
@@ -5765,7 +5823,7 @@ func runInit(ctx context.Context) {
_, _, err := handleExecution(schedule.WorkflowId, Workflow{}, request)
if err != nil {
log.Printf("Failed to execute: %s", err)
log.Printf("Failed to execute %s: %s", schedule.WorkflowId, err)
}
}
@@ -5825,7 +5883,7 @@ func runInit(ctx context.Context) {
}
log.Printf("Downloading OpenAPI data for search - EXTRA APPS")
apis := "https://github.com/frikky/OpenAPI-security-definitions"
apis := "https://github.com/frikky/security-openapis"
// THis gets memory problems hahah
//apis := "https://github.com/APIs-guru/openapi-directory"
@@ -5897,6 +5955,7 @@ func init() {
// App specific
r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/download_remote", loadSpecificApps).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}", deleteWorkflowApp).Methods("DELETE", "OPTIONS")
r.HandleFunc("/api/v1/apps/{appId}/config", getWorkflowAppConfig).Methods("GET", "OPTIONS")
@@ -5914,6 +5973,8 @@ func init() {
/* Everything below here increases the counters*/
r.HandleFunc("/api/v1/workflows", getWorkflows).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/workflows", setNewWorkflow).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/schedules", handleGetSchedules).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/workflows/download_remote", loadSpecificWorkflows).Methods("POST", "OPTIONS")
//r.HandleFunc("/api/v1/workflows/{key}/execute_fs", executeWorkflowFS)
r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS")
r.HandleFunc("/api/v1/workflows/{key}/schedule", scheduleWorkflow).Methods("POST", "OPTIONS")
+284 -88
View File
File diff suppressed because one or more lines are too long
+12 -12
View File
@@ -13,18 +13,6 @@ services:
restart: unless-stopped
depends_on:
- backend
database:
#build: ./backend/database
image: frikky/shuffle:database
container_name: shuffle-database
hostname: shuffle-database
ports:
- "8000:8000"
networks:
- shuffle
restart: unless-stopped
volumes:
- ${DB_LOCATION}:/etc/shuffle
backend:
#build: ./backend
image: frikky/shuffle:backend
@@ -59,6 +47,18 @@ services:
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
- DOCKER_API_VERSION=1.40
restart: unless-stopped
database:
#build: ./backend/database
image: frikky/shuffle:database
container_name: shuffle-database
hostname: shuffle-database
ports:
- "8000:8000"
networks:
- shuffle
restart: unless-stopped
volumes:
- ${DB_LOCATION}:/etc/shuffle
networks:
shuffle:
driver: bridge
+109 -1
View File
@@ -8,7 +8,8 @@ import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import { useAlert } from "react-alert";
@@ -29,9 +30,39 @@ const Admin = (props) => {
const [curTab, setCurTab] = React.useState(0);
const [users, setUsers] = React.useState([]);
const [environments, setEnvironments] = React.useState([]);
const [schedules, setSchedules] = React.useState([])
const alert = useAlert()
const deleteSchedule = (data) => {
// FIXME - add some check here ROFL
console.log("INPUT: ", data)
// Just use this one?
const url = globalUrl+'/api/v1/workflows/'+data["workflow_id datastore:"]+"/schedule/"+data.id
console.log("URL: ", url)
fetch(url, {
method: 'DELETE',
credentials: "include",
headers: {
'Content-Type': 'application/json',
},
})
.then(response =>
response.json().then(responseJson => {
if (responseJson["success"] === false) {
alert.error("Failed stopping schedule")
} else {
getSchedules()
alert.success("Successfully stopped schedule!")
}
}),
)
.catch(error => {
console.log("Error in userdata: ", error)
});
}
const submitUser = (data) => {
// FIXME - add some check here ROFL
console.log("INPUT: ", data)
@@ -132,6 +163,31 @@ const Admin = (props) => {
});
}
const getSchedules = () => {
fetch(globalUrl+"/api/v1/workflows/schedules", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!")
return
}
return response.json()
})
.then((responseJson) => {
setSchedules(responseJson)
})
.catch(error => {
alert.error(error.toString())
});
}
const getEnvironments = () => {
fetch(globalUrl+"/api/v1/getenvironments", {
method: 'GET',
@@ -329,6 +385,54 @@ const Admin = (props) => {
</div>
: null
const schedulesView = curTab === 2 ?
<div>
<h2>
Schedules
</h2>
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: inputColor}}/>
<List>
<ListItem>
<ListItemText
primary="Interval (seconds)"
style={{maxWidth: 200}}
/>
<ListItemText
primary="Argument"
style={{maxWidth: 400, overflow: "hidden"}}
/>
<ListItemText
primary="Actions"
/>
</ListItem>
{schedules === undefined || schedules === null ? null : schedules.map(schedule => {
return (
<ListItem>
<ListItemText
style={{maxWidth: 200}}
primary={schedule.seconds}
/>
<ListItemText
primary={schedule.argument}
style={{maxWidth: 400, overflow: "hidden"}}
/>
<ListItemText>
<Button
style={{}}
variant="contained"
color="primary"
onClick={() => deleteSchedule(schedule)}
>
Delete
</Button>
</ListItemText>
</ListItem>
)
})}
</List>
</div>
: null
const environmentView = curTab === 1 ?
<div>
<h2>
@@ -359,6 +463,8 @@ const Admin = (props) => {
const setConfig = (event, newValue) => {
if (newValue === 1) {
getEnvironments()
} else if (newValue === 2) {
getSchedules()
}
setModalUser({})
@@ -377,10 +483,12 @@ const Admin = (props) => {
>
<Tab label="Users" />
<Tab label="Environments"/>
<Tab label="Schedules"/>
</Tabs>
<div style={{marginBottom: 10}}/>
{usersView}
{environmentView}
{schedulesView}
</Paper>
</div>
+112 -172
View File
@@ -10,6 +10,8 @@ import Drawer from '@material-ui/core/Drawer';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import Grid from '@material-ui/core/Grid';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import ButtonBase from '@material-ui/core/ButtonBase';
import Tooltip from '@material-ui/core/Tooltip';
import Select from '@material-ui/core/Select';
@@ -100,7 +102,7 @@ const AngularWorkflow = (props) => {
const [cy, setCy] = React.useState()
const [appSearch, setAppSearch] = React.useState("")
const [currentView, setCurrentView] = React.useState("apps")
const [currentView, setCurrentView] = React.useState(0)
const [triggerAuthentication, setTriggerAuthentication] = React.useState({})
const [triggerFolders, setTriggerFolders] = React.useState([])
@@ -164,9 +166,6 @@ const AngularWorkflow = (props) => {
const [lastSaved, setLastSaved] = React.useState(true)
const [AppsHoverColor, setAppsHoverColor] = useState(hoverOutColor);
const [VariablesHoverColor, setVariablesHoverColor] = useState(hoverOutColor);
const [HookHoverColor, setHookHoverColor] = useState(hoverOutColor);
const [appAdded, setAppAdded] = useState(false)
const [update, setUpdate] = useState("");
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
@@ -262,7 +261,7 @@ const AngularWorkflow = (props) => {
const abortExecution = () => {
setExecutionRunning(false)
alert.success("Aborting execution")
alert.info("Aborting execution")
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/executions/"+executionRequest.execution_id+"/abort", {
method: 'GET',
headers: {
@@ -274,7 +273,9 @@ const AngularWorkflow = (props) => {
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
}
} else {
alert.success("Execution aborted")
}
return response.json()
})
@@ -547,68 +548,6 @@ const AngularWorkflow = (props) => {
return true
}
const executeWorkflowWebsocket = () => {
if (!lastSaved) {
//alert.error("You might have forgotten to save before executing.")
console.log("FIXME: Might have forgotten to save before executing.")
}
var returncheck = monitorUpdates()
if (!returncheck) {
alert.error("No startnode set.")
return
}
setVisited([])
setExecutionRunning(true)
setExecutionRequest({})
var curelements = cy.elements()
for (var i = 0; i < curelements.length; i++) {
curelements[i].addClass("not-executing-highlight")
}
if (executionText.length > 0) {
alert.success("Starting execution with argument "+executionText)
} else {
alert.success("Starting execution")
}
const data = {"execution_argument": executionText}
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/execute_fs", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
body: JSON.stringify(data),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
}
return response.json()
})
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to start: "+responseJson.reason)
stop()
return
}
setExecutionRequest({
"execution_id": responseJson.execution_id,
"authorization": responseJson.authorization,
})
setExecutingNodes([workflow.start])
})
.catch(error => {
alert.error(error.toString())
});
}
const executeWorkflow = () => {
if (!lastSaved) {
//alert.error("You might have forgotten to save before executing.")
@@ -639,14 +578,14 @@ const AngularWorkflow = (props) => {
const data = {"execution_argument": executionText, "start": workflow.start}
fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/execute", {
method: 'POST',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
body: JSON.stringify(data),
})
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
@@ -657,7 +596,13 @@ const AngularWorkflow = (props) => {
.then((responseJson) => {
if (!responseJson.success) {
alert.error("Failed to start: "+responseJson.reason)
setExecutionRunning(false)
setExecutionRequestStarted(false)
stop()
for (var i = 0; i < curelements.length; i++) {
curelements[i].removeClass("not-executing-highlight")
}
return
} else {
setExecutionRunning(true)
@@ -1429,16 +1374,17 @@ const AngularWorkflow = (props) => {
}
const appViewStyle = {
marginLeft: "5px",
marginRight: "5px",
marginLeft: 5,
marginRight: 5,
display: "flex",
flexDirection: "column",
height: "100%",
}
const scrollStyle = {
marginTop: "10px",
marginTop: 10,
overflow: "scroll",
height: "66vh",
height: "100%",
overflowX: "auto",
overflowY: "auto",
}
@@ -1455,37 +1401,6 @@ const AngularWorkflow = (props) => {
display: "flex",
}
// All this is stupid lmao
const handleHookHover = () => {
setHookHoverColor(hoverColor)
setAppsHoverColor(hoverOutColor)
setVariablesHoverColor(hoverOutColor)
}
const handleHookHoverOut = () => {
setHookHoverColor(hoverOutColor)
}
const handleAppsHover = () => {
setAppsHoverColor(hoverColor)
setHookHoverColor(hoverOutColor)
setVariablesHoverColor(hoverOutColor)
}
const handleAppsHoverOut = () => {
setAppsHoverColor(hoverOutColor)
}
const handleVariablesHover = () => {
setVariablesHoverColor(hoverColor)
setHookHoverColor(hoverOutColor)
setAppsHoverColor(hoverOutColor)
}
const handleVariablesHoverOut = () => {
setVariablesHoverColor(hoverOutColor)
}
const paperVariableStyle = {
minHeight: "50px",
maxHeight: "50px",
@@ -1560,6 +1475,7 @@ const AngularWorkflow = (props) => {
aria-controls="long-menu"
aria-haspopup="true"
onClick={menuClick}
style={{color: "white"}}
>
<MoreVertIcon />
</IconButton>
@@ -1605,57 +1521,77 @@ const AngularWorkflow = (props) => {
</div>
)
}
const curTab = 0
const handleSetTab = (event, newValue) => {
setCurrentView(newValue)
}
const HandleLeftView = () => {
// Defaults to apps.
var thisview = <AppView />
if (currentView === "triggers") {
if (currentView === 1) {
thisview = <TriggersView />
} else if (currentView === "variables") {
} else if (currentView === 2) {
thisview = <VariablesView />
}
const tabStyle = {
maxWidth: leftBarSize/3,
minWidth: leftBarSize/3,
flex: 1,
textTransform: "none",
}
const iconStyle = {
marginTop: 3,
marginRight: 5,
}
return(
<div>
<Divider style={{marginTop: 10, height: 1, width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
<div style={{minHeight: bodyHeight-appBarSize-150, maxHeight: bodyHeight-appBarSize-100}}>
<div style={{minHeight: bodyHeight-appBarSize-50, maxHeight: bodyHeight-appBarSize-50}}>
{thisview}
</div>
<div style={{bottom: 0, left: 0}}>
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
<div style={{display: "flex"}}>
<div style={{flex: "1", marginLeft: "10px", marginTop: "10px", color: AppsHoverColor, cursor: "pointer", textAlign: "center"}} onMouseOver={handleAppsHover} onMouseOut={handleAppsHoverOut} onClick={() => {setCurrentView("apps")}}>
<Grid container direction="row" alignItems="center">
<Grid item>
<AppsIcon style={{marginTop: "3px", marginRight: "5px"}} />
</Grid>
<Divider style={{backgroundColor: "rgb(91, 96, 100)"}}/>
<Tabs
value={currentView}
indicatorColor="primary"
textColor="white"
onChange={handleSetTab}
aria-label="Left sidebar tab"
>
<Tab label={
<Grid container direction="row" alignItems="center">
<Grid item>
Apps
</Grid>
<AppsIcon style={iconStyle} />
</Grid>
</div>
<div style={{flex: "1", marginLeft: "10px", marginTop: "10px", color: HookHoverColor, cursor: "pointer"}} onMouseOver={handleHookHover} onMouseOut={handleHookHoverOut} onClick={() => {setCurrentView("triggers")}}>
<Grid container direction="row" alignItems="center">
<Grid item>
<ScheduleIcon style={{marginTop: "3px", marginRight: "5px"}} />
</Grid>
<Grid item>
Apps
</Grid>
</Grid>
} style={tabStyle} />
<Tab label={
<Grid container direction="row" alignItems="center">
<Grid item>
Triggers
</Grid>
<ScheduleIcon style={iconStyle} />
</Grid>
</div>
<div style={{flex: "1", marginLeft: "10px", marginTop: "10px", color: VariablesHoverColor, cursor: "pointer"}} onMouseOver={handleVariablesHover} onMouseOut={handleVariablesHoverOut} onClick={() => {setCurrentView("variables")}}>
<Grid container direction="row" alignItems="center">
<Grid item>
<FavoriteBorderIcon style={{marginTop: "3px", marginRight: "5px"}} />
</Grid>
<Grid item>
Triggers
</Grid>
</Grid>
} style={tabStyle} />
<Tab label={
<Grid container direction="row" alignItems="center">
<Grid item>
Variables
</Grid>
<FavoriteBorderIcon style={iconStyle} />
</Grid>
</div>
</div>
</div>
<Grid item>
Variables
</Grid>
</Grid>
}style={tabStyle} />
</Tabs>
</div>
)
}
@@ -1965,7 +1901,7 @@ const AngularWorkflow = (props) => {
node.data.type = "ACTION"
node.isStartNode = action["id"] === workflow.start
return node;
return node
})
const tmpelements = [].concat(actions)
@@ -2001,14 +1937,16 @@ const AngularWorkflow = (props) => {
}
}
const handleDragStop = (e) => {
const handleDragStop = (e, app) => {
newNodeId = ""
console.log("STOP!: ", e)
console.log("APP!: ", app)
}
const appScrollStyle = {
overflow: "scroll",
maxHeight: bodyHeight-appBarSize-150,
minHeight: bodyHeight-appBarSize-150,
maxHeight: bodyHeight-appBarSize-55,
minHeight: bodyHeight-appBarSize-55,
overflowY: "auto",
overflowX: "hidden",
}
@@ -2063,7 +2001,8 @@ const AngularWorkflow = (props) => {
return(
<Draggable
onDrag={(e) => {handleAppDrag(e, app)}}
onStop={(e) => {handleDragStop(e)}}
onStop={(e) => {handleDragStop(e, app)}}
key={app.id}
dragging={false}
position={{
x: 0,
@@ -2427,7 +2366,7 @@ const AngularWorkflow = (props) => {
} else if (data.variant === "WORKFLOW_VARIABLE") {
varcolor = "#f85a3e"
if (workflow.workflow_variables === null || workflow.workflow_variables === undefined || workflow.workflow_variables.length === 0) {
setCurrentView("variables")
setCurrentView(2)
datafield =
<div>
<div>
@@ -2703,11 +2642,11 @@ const AngularWorkflow = (props) => {
}
const setTriggerFolderWrapperMulti = event => {
const { options } = event.target;
const value = [];
const { options } = event.target
const value = []
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
value.push(options[i].value);
value.push(options[i].value)
}
}
@@ -2783,7 +2722,7 @@ const AngularWorkflow = (props) => {
if (splitItems.includes(value)) {
for( var i = 0; i < splitItems.length; i++){
if (splitItems[i] === value) {
splitItems.splice(i, 1);
splitItems.splice(i, 1)
}
}
@@ -2793,7 +2732,7 @@ const AngularWorkflow = (props) => {
for( var i = 0; i < splitItems.length; i++){
if (splitItems[i] === "") {
splitItems.splice(i, 1);
splitItems.splice(i, 1)
}
}
@@ -2824,7 +2763,7 @@ const AngularWorkflow = (props) => {
}
const AppConditionHandler = (props) => {
const { tmpdata, type } = props;
const { tmpdata, type } = props
if (tmpdata === undefined) {
return tmpdata
@@ -2933,7 +2872,7 @@ const AngularWorkflow = (props) => {
} else if (data.variant === "WORKFLOW_VARIABLE") {
varcolor = "#f85a3e"
if (workflow.workflow_variables === null || workflow.workflow_variables === undefined || workflow.workflow_variables.length === 0) {
setCurrentView("variables")
setCurrentView(2)
datafield =
<div>
<div>
@@ -3192,8 +3131,8 @@ const AngularWorkflow = (props) => {
const EdgeSidebar = () => {
const ConditionHandler = (condition, index) => {
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
const [open, setOpen] = React.useState(false)
const [anchorEl, setAnchorEl] = React.useState(null)
const deleteCondition = (conditionIndex) => {
console.log(selectedEdge)
@@ -3223,7 +3162,7 @@ const AngularWorkflow = (props) => {
const menuClick = (event) => {
console.log("MENU CLICK")
setOpen(!open)
setAnchorEl(event.currentTarget);
setAnchorEl(event.currentTarget)
}
return (
@@ -3363,7 +3302,7 @@ const AngularWorkflow = (props) => {
})
.catch(error => {
console.log(error.toString())
});
})
}
const getTriggerAuth = () => {
@@ -3384,7 +3323,7 @@ const AngularWorkflow = (props) => {
})
.catch(error => {
console.log(error.toString())
});
})
}
// Getting the triggers and the folders if they exist
@@ -3426,8 +3365,8 @@ const AngularWorkflow = (props) => {
})
.catch(error => {
console.log(error.toString())
});
}, 2500);
})
}, 2500)
console.log(data)
saveWorkflow(workflow)
@@ -3801,7 +3740,7 @@ const AngularWorkflow = (props) => {
})
.catch(error => {
alert.error(error.toString())
});
})
}
const startMailSub = (trigger, triggerindex) => {
@@ -3857,7 +3796,7 @@ const AngularWorkflow = (props) => {
})
.catch(error => {
alert.error(error.toString())
});
})
}
const newWebhook = (trigger) => {
@@ -3903,7 +3842,7 @@ const AngularWorkflow = (props) => {
})
.catch(error => {
console.log(error.toString())
});
})
}
const deleteWebhook = (trigger, triggerindex) => {
@@ -3942,7 +3881,7 @@ const AngularWorkflow = (props) => {
})
.catch(error => {
alert.error(error.toString())
});
})
}
const UserinputSidebar = () => {
@@ -4356,7 +4295,7 @@ const AngularWorkflow = (props) => {
}
if (Object.getOwnPropertyNames(selectedAction).length > 0 && Object.getOwnPropertyNames(selectedApp).length > 0) {
//console.time('ACTIONSTART');
//console.time('ACTIONSTART')
return(
<div style={rightsidebarStyle}>
{appApiView}
@@ -4479,11 +4418,13 @@ const AngularWorkflow = (props) => {
<div style={{ marginTop: "auto", marginBottom: "auto", marginRight: 15, }}>
{timestamp}
</div>
<Tooltip color="primary" title={resultsLength+" actions ran"} placement="top">
<div style={{marginRight: 10, marginTop: "auto", marginBottom: "auto",}}>
{resultsLength}/{data.workflow.actions.length}
</div>
</Tooltip>
{data.workflow.actions !== null ?
<Tooltip color="primary" title={resultsLength+" actions ran"} placement="top">
<div style={{marginRight: 10, marginTop: "auto", marginBottom: "auto",}}>
{resultsLength}/{data.workflow.actions.length}
</div>
</Tooltip>
: null}
</div>
<Tooltip title={"Inspect execution"} placement="top">
<KeyboardArrowRightIcon style={{marginTop: "auto", marginBottom: "auto"}}/>
@@ -4545,7 +4486,7 @@ const AngularWorkflow = (props) => {
executionData.results.map(data => {
var showResult = data.result.trim()
showResult.split(" None").join(" \"None\"")
//showResult = replaceAll(showResult, " None", " \"None\"");
//showResult = replaceAll(showResult, " None", " \"None\"")
var jsonvalid = true
try {
JSON.parse(showResult)
@@ -4569,7 +4510,7 @@ const AngularWorkflow = (props) => {
{jsonvalid ? <ReactJson
src={JSON.parse(showResult)}
theme="solarized"
collapsed={false}
collapsed={true}
displayDataTypes={false}
name={"Results for "+data.action.label}
/>
@@ -4608,7 +4549,6 @@ const AngularWorkflow = (props) => {
<RightSideBar />
<BottomCytoscapeBar />
<TopCytoscapeBar />
<NoActionsBar />
</div>
:
<div style={{color: "white"}}>
@@ -4872,4 +4812,4 @@ const AngularWorkflow = (props) => {
)
}
export default AngularWorkflow;
export default AngularWorkflow
+7 -7
View File
@@ -147,13 +147,13 @@ const App = (message, props) => {
// This is a mess hahahah
return (
<MuiThemeProvider theme={theme}>
<CookiesProvider>
<BrowserRouter>
<Provider template={AlertTemplate} {...options}>
{includedData}
</Provider>
</BrowserRouter>
</CookiesProvider>
<CookiesProvider>
<BrowserRouter>
<Provider template={AlertTemplate} {...options}>
{includedData}
</Provider>
</BrowserRouter>
</CookiesProvider>
</MuiThemeProvider>
);
};
+71 -41
View File
@@ -4,11 +4,13 @@ import {BrowserView, MobileView} from "react-device-detect";
import {Link} from 'react-router-dom';
import Paper from '@material-ui/core/Paper';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Button from '@material-ui/core/Button';
import Divider from '@material-ui/core/Divider';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Switch from '@material-ui/core/Switch';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
@@ -65,7 +67,7 @@ const useStyles = makeStyles({
const rewrite = (args) => {
return args.reduce(function(args, a){
if (0 == a.indexOf('-X')) {
if (0 === a.indexOf('-X')) {
args.push('-X')
args.push(a.slice(2))
} else {
@@ -103,35 +105,35 @@ const parseCurl = (s) => {
out.url = arg
break;
case arg == '-A' || arg == '--user-agent':
case arg === '-A' || arg === '--user-agent':
state = 'user-agent'
break;
case arg == '-H' || arg == '--header':
case arg === '-H' || arg === '--header':
state = 'header'
break;
case arg == '-d' || arg == '--data' || arg == '--data-ascii':
case arg === '-d' || arg === '--data' || arg === '--data-ascii':
state = 'data'
break;
case arg == '-u' || arg == '--user':
case arg === '-u' || arg === '--user':
state = 'user'
break;
case arg == '-I' || arg == '--head':
case arg === '-I' || arg === '--head':
out.method = 'HEAD'
break;
case arg == '-X' || arg == '--request':
case arg === '-X' || arg === '--request':
state = 'method'
break;
case arg == '-b' || arg =='--cookie':
case arg === '-b' || arg === '--cookie':
state = 'cookie'
break;
case arg == '--compressed':
case arg === '--compressed':
out.header['Accept-Encoding'] = out.header['Accept-Encoding'] || 'deflate, gzip'
break;
@@ -147,7 +149,7 @@ const parseCurl = (s) => {
state = ''
break;
case 'data':
if (out.method == 'GET' || out.method == 'HEAD') out.method = 'POST'
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
@@ -196,6 +198,7 @@ const AppCreator = (props) => {
const [updater, setUpdater] = useState("tmp")
const [baseUrl, setBaseUrl] = useState("");
const [actionsModalOpen, setActionsModalOpen] = useState(false);
const [authenticationRequired, setAuthenticationRequired] = useState(false);
const [authenticationOption, setAuthenticationOption] = useState(authenticationOptions[0]);
const [parameterName, setParameterName] = useState("");
const [parameterLocation, setParameterLocation] = useState(apikeySelection.length > 0 ? apikeySelection[0] : "");
@@ -372,30 +375,6 @@ const AppCreator = (props) => {
securitySchemes = data.components.securitySchemes
}
// FIXME: Have multiple authentication options?
if (securitySchemes !== undefined) {
for (const [key, value] of Object.entries(securitySchemes)) {
if (value.scheme === "bearer") {
setAuthenticationOption("Bearer auth")
break
} else if (value.type === "apiKey") {
setAuthenticationOption("API key")
value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1);
setParameterLocation(value.in)
if (!apikeySelection.includes(value.in)) {
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")
break
}
}
}
console.log(data)
@@ -451,9 +430,39 @@ const AppCreator = (props) => {
}
}
// FIXME: Have multiple authentication options?
if (securitySchemes !== undefined) {
for (const [key, value] of Object.entries(securitySchemes)) {
if (value.scheme === "bearer") {
setAuthenticationOption("Bearer auth")
setAuthenticationRequired(true)
break
} else if (value.type === "apiKey") {
setAuthenticationOption("API key")
value.in = value.in.charAt(0).toUpperCase() + value.in.slice(1);
setParameterLocation(value.in)
if (!apikeySelection.includes(value.in)) {
console.log("APIKEY SELECT: ", apikeySelection)
alert.error("Might be error in setting up API key authentication")
}
console.log("PARAM NAME: ", value.name)
setParameterName(value.name)
setAuthenticationRequired(true)
break
} else if (value.scheme === "basic") {
setAuthenticationOption("Basic auth")
setAuthenticationRequired(true)
break
}
}
}
setActions(newActions)
}
// Saving the app that's been configured.
const submitApp = () => {
alert.info("Uploading and building app " + name)
@@ -701,7 +710,6 @@ const AppCreator = (props) => {
</a>
</h4>
Users will be required to submit their API as the header "Authorization: Bearer APIKEY"
<Divider style={{marginBottom: "10px", marginTop: "30px", height: "1px", width: "100%", backgroundColor: "grey"}}/>
</div>
: null
@@ -714,7 +722,6 @@ const AppCreator = (props) => {
</a>
</h4>
Users will be required to submit a valid username and password before using the API
<Divider style={{marginBottom: "10px", marginTop: "30px", height: "1px", width: "100%", backgroundColor: "grey"}}/>
</div>
: null
@@ -810,7 +817,7 @@ const AppCreator = (props) => {
id="standard-required"
margin="normal"
variant="outlined"
defaultValue={parameterName}
value={parameterName}
helperText={<div style={{color:"white", marginBottom: "2px",}}>Can't be empty. Can't contain any of the following characters: !#$%&'^+-._~|]+$</div>}
onChange={e => setParameterName(e.target.value)}
InputProps={{
@@ -847,7 +854,6 @@ const AppCreator = (props) => {
)}
)}
</Select>
<Divider style={{marginBottom: "10px", marginTop: "30px", height: "1px", width: "100%", backgroundColor: "grey"}}/>
</div>
: null
@@ -926,11 +932,13 @@ const AppCreator = (props) => {
{data.method} - {url} - {data.name}
</div>
</Tooltip>
{/*
<Tooltip title="Test action" placement="bottom">
<div style={{color: "#f85a3e", cursor: "pointer", marginRight: "10px", }} onClick={() => {testAction(index)}}>
Test
</div>
</Tooltip>
*/}
<Tooltip title="Delete action" placement="bottom">
<div style={{color: "#f85a3e", cursor: "pointer"}} onClick={() => {deleteAction(index)}}>
Delete
@@ -1261,6 +1269,10 @@ const AppCreator = (props) => {
if (request.header !== undefined && request.header !== null) {
var headers = []
for (let [key, value] of Object.entries(request.header)) {
if (parameterName !== undefined && key.toLowerCase() === parameterName.toLowerCase()) {
continue
}
headers += key+"="+value+"\n"
}
@@ -1550,11 +1562,17 @@ const AppCreator = (props) => {
}}
/>
<FormControl style={{marginTop: "15px",}} variant="outlined">
<h5 style={{marginBottom: "10px", color: "white",}}>Authentication</h5>
<h5 style={{marginBottom: "10px", color: "white",}}>Authentication
</h5>
<Select
fullWidth
onChange={(e) => {
setAuthenticationOption(e.target.value)
if (e.target.value === "No authentication") {
setAuthenticationRequired(false)
} else {
setAuthenticationRequired(true)
}
}}
value={authenticationOption}
style={{backgroundColor: inputColor, color: "white", height: "50px"}}
@@ -1569,16 +1587,28 @@ const AppCreator = (props) => {
{basicAuth}
{bearerAuth}
{apiKey}
{/*authenticationOption === "No authentication" ? null :
<FormControlLabel
style={{color: "white", marginBottom: 0, marginTop: 20}}
label=<div style={{color: "white"}}>Authentication required (default true)</div>
control={<Switch checked={authenticationRequired} onChange={() => {
setAuthenticationRequired(!authenticationRequired)
}} />}
/>*/}
<Divider style={{marginBottom: "10px", marginTop: "30px", height: "1px", width: "100%", backgroundColor: "grey"}}/>
<div style={{marginTop: "25px"}}>
{actionView}
</div>
{/*
<Divider style={{marginBottom: "10px", marginTop: "30px", height: "1px", width: "100%", backgroundColor: "grey"}}/>
{testView}
*/}
<Button color="primary" variant="contained" style={{borderRadius: "0px", marginTop: "30px", height: "50px",}} onClick={() => {
submitApp()
}}>
Save
Save
</Button>
{errorCode.length > 0 ? `Error: ${errorCode}` : null}
</Paper>
+35 -33
View File
@@ -20,6 +20,8 @@ import YAML from 'yaml'
import {Link} from 'react-router-dom';
import Breadcrumbs from '@material-ui/core/Breadcrumbs';
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
import PublishIcon from '@material-ui/icons/Publish';
import CloudDownload from '@material-ui/icons/CloudDownload';
import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete';
@@ -455,14 +457,15 @@ const Apps = (props) => {
<div style={{width: "100%", margin: 25}}>
<h2>App Creator</h2>
<a href="/docs/apps" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">How it works</a>
&nbsp;- <a href="https://github.com/frikky/OpenAPI-security-definitions" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
&nbsp;- <a href="https://github.com/frikky/security-openapis" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">Security API's</a>
&nbsp;- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
<div/>
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. Use the links above to find potential apps you're looking for using OpenAPI or make one from scratch. There's 1000+ available.
<div/>
<div style={{marginTop: 20}}>
<Divider style={{height: 1, backgroundColor: dividerColor, marginTop: 20, marginBottom: 20}} />
<div style={{}}>
<Button
variant="outlined"
variant="text"
component="label"
color="primary"
style={{marginRight: 10, }}
@@ -470,11 +473,12 @@ const Apps = (props) => {
setOpenApiModal(true)
}}
>
Create from OpenAPI
<PublishIcon style={{marginRight: 5}} /> Create from OpenAPI
</Button>
<Link to="/apps/new" style={{textDecoration: "none", color: "#f85a3e"}}>
&nbsp;OR&nbsp;
<Link to="/apps/new" style={{marginLeft: 5, textDecoration: "none", color: "#f85a3e"}}>
<Button
variant="outlined"
variant="text"
component="label"
color="primary"
style={{}}
@@ -508,9 +512,9 @@ const Apps = (props) => {
}
const appView = isLoggedIn ?
<div style={{maxWidth: 1366, margin: "auto",}}>
<div style={{maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto",}}>
<div style={appViewStyle}>
<div>
<div style={{flex: 1}}>
<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)"}}>
@@ -528,10 +532,10 @@ const Apps = (props) => {
</Breadcrumbs>
<UploadView/>
</div>
<Divider style={{marginBottom: "10px", marginTop: "10px", height: "100%", width: "1px", backgroundColor: dividerColor}}/>
<Divider style={{marginBottom: 10, marginTop: 10, height: "100%", width: 1, backgroundColor: dividerColor}}/>
<div style={{flex: 1, marginLeft: 10, marginRight: 10}}>
<div style={{display: "flex"}}>
<div style={{flex: 10}}>
<div style={{flex: 1}}>
<h2>Available integrations</h2>
</div>
{isLoading ? <CircularProgress style={{marginTop: 13, marginRight: 15}} /> : null}
@@ -543,18 +547,20 @@ const Apps = (props) => {
setSearchBackend(!searchBackend)}
} />}
/>
<Button
variant="outlined"
component="label"
color="primary"
style={{margin: 5, maxHeight: 50, marginTop: 10}}
onClick={() => {
setOpenApi(baseRepository)
setLoadAppsModalOpen(true)
}}
>
Download more apps
</Button>
<Tooltip title={"Download from Github"} style={{marginTop: "28px", width: "100%"}} aria-label={"Upload"}>
<Button
variant="outlined"
component="label"
color="primary"
style={{margin: 5, maxHeight: 50, marginTop: 10}}
onClick={() => {
setOpenApi(baseRepository)
setLoadAppsModalOpen(true)
}}
>
<CloudDownloadIcon />
</Button>
</Tooltip>
</div>
<TextField
style={{backgroundColor: inputColor}}
@@ -577,7 +583,7 @@ const Apps = (props) => {
<div style={{marginTop: 15}}>
{apps.length > 0 ?
filteredApps.length > 0 ?
<div style={{maxHeight: "78vh", overflowY: "scroll"}}>
<div style={{height: "75vh", overflowY: "scroll"}}>
{filteredApps.map(app => {
return (
appPaper(app)
@@ -642,10 +648,7 @@ const Apps = (props) => {
})
.then((response) => {
if (response.status === 200) {
response.text().then(function (text) {
console.log("RETURN: ", text)
alert.success("Loaded existing apps!")
})
alert.success("Loaded existing apps!")
}
setIsLoading(false)
stop()
@@ -656,11 +659,10 @@ const Apps = (props) => {
console.log("DATA: ", responseJson)
if (responseJson.reason !== undefined) {
alert.error("Failed loading: "+responseJson.reason)
} else {
alert.error("Failed loading")
}
})
.catch(error => {
console.log("ERROR: ", error.toString())
alert.error(error.toString())
})
}
@@ -1017,13 +1019,13 @@ const Apps = (props) => {
</DialogContent>
<DialogActions>
{circularLoader}
<Button style={{borderRadius: "0px"}} onClick={() => setOpenApiModal(false)} color="primary">
Cancel
</Button>
<Button style={{borderRadius: "0px"}} onClick={() => setOpenApiModal(false)} color="primary">
Cancel
</Button>
<Button style={{borderRadius: "0px"}} disabled={appValidation.length === 0} onClick={() => {
redirectOpenApi()
}} color="primary">
Submit
Submit
</Button>
</DialogActions>
</FormControl>
+24 -2
View File
@@ -32,7 +32,7 @@ const hrefStyle = {
}
const Docs = (props) => {
const { isLoaded, globalUrl } = props;
const { isLoaded, globalUrl, inputColor } = props;
const [data, setData] = useState("");
const [firstrequest, setFirstrequest] = useState(true);
@@ -179,6 +179,23 @@ const Docs = (props) => {
return <img style={{maxWidth: "100%"}} alt={props.alt} src={props.src}/>
}
function CodeHandler(props) {
return <code style={{padding: 5, backgroundColor: inputColor}}>{props.value}</code>
}
function Heading(props) {
const element = React.createElement(`h${props.level}`, {style: {marginTop: 25}}, props.children)
console.log(props)
return (
<span>
<Divider style={{width: "90%", marginTop: 25, backgroundColor: inputColor}} />
{element}
</span>
)
}
//React.createElement("p", {style: {color: "red", backgroundColor: "blue"}}, this.props.paragraph)
//function unicodeToChar(text) {
// return text.replace(/\\u[\dA-F]{4}/gi,
// function (match) {
@@ -214,7 +231,12 @@ const Docs = (props) => {
id="markdown_wrapper"
escapeHtml={false}
source={data}
renderers={{link: OuterLink, image: Img}}
renderers={{
link: OuterLink,
image: Img,
code: CodeHandler,
heading: Heading,
}}
/>
</div>
</div>
+172 -1
View File
@@ -14,6 +14,7 @@ import MenuItem from '@material-ui/core/MenuItem';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch';
import CircularProgress from '@material-ui/core/CircularProgress';
import CachedIcon from '@material-ui/icons/Cached';
import EditIcon from '@material-ui/icons/Edit';
import MoreVertIcon from '@material-ui/icons/MoreVert';
@@ -31,6 +32,9 @@ import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
const inputColor = "#383B40"
const surfaceColor = "#27292D"
const Workflows = (props) => {
@@ -51,6 +55,10 @@ const Workflows = (props) => {
const [, setTrackingId] = React.useState("")
const [collapseJson, setCollapseJson] = React.useState(false)
const [field1, setField1] = React.useState("")
const [field2, setField2] = React.useState("")
const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows")
const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false)
const [modalOpen, setModalOpen] = React.useState(false);
const [newWorkflowName, setNewWorkflowName] = React.useState("");
@@ -173,6 +181,7 @@ const Workflows = (props) => {
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
alert.error("Failed loading executions for current workflow")
}
return response.json()
@@ -758,7 +767,10 @@ const Workflows = (props) => {
for (var key in event.target.files) {
const file = event.target.files[key]
if (file.type !== "application/json") {
//alert.error("File has to contain json.")
if (file.type !== undefined) {
alert.error("File has to contain valid json")
}
continue
}
@@ -798,6 +810,8 @@ const Workflows = (props) => {
reader.readAsText(file)
}
}
setLoadWorkflowsModalOpen(false)
}
const modalView = modalOpen ?
@@ -886,11 +900,18 @@ const Workflows = (props) => {
<Tooltip color="primary" title={"Create new workflow"} placement="top">
<Button color="primary" style={{}} variant="text" onClick={() => setModalOpen(true)}><AddIcon /></Button>
</Tooltip>
{/*
<Tooltip color="primary" title={"Import workflows"} placement="top">
<Button color="primary" style={{}} variant="text" onClick={() => upload.click()}>
<PublishIcon />
</Button>
</Tooltip>
*/}
<Tooltip color="primary" title={"Download workflows"} placement="top">
<Button color="primary" style={{}} variant="text" onClick={() => setLoadWorkflowsModalOpen(true)}>
<CloudDownloadIcon />
</Button>
</Tooltip>
<input hidden type="file" multiple="multiple" ref={(ref) => upload = ref} onChange={importFiles} />
</div>
</div>
@@ -962,10 +983,160 @@ const Workflows = (props) => {
</Paper>
</div>
const importWorkflowsFromUrl = (url) => {
console.log("IMPORT WORKFLOWS FROM ", downloadUrl)
const parsedData = {
"url": url,
}
if (field1.length > 0) {
parsedData["field_1"] = field1
}
if (field2.length > 0) {
parsedData["field_2"] = field2
}
alert.success("Getting specific workflows from your URL.")
var cors = "cors"
fetch(globalUrl+"/api/v1/workflows/download_remote", {
method: "POST",
mode: "cors",
headers: {
'Accept': 'application/json',
},
body: JSON.stringify(parsedData),
credentials: "include",
})
.then((response) => {
if (response.status === 200) {
response.text().then(function (text) {
console.log("RETURN: ", text)
alert.success("Loaded existing apps!")
})
}
return response.json()
})
.then((responseJson) => {
console.log("DATA: ", responseJson)
if (responseJson.reason !== undefined) {
alert.error("Failed loading: "+responseJson.reason)
} else {
alert.error("Failed loading")
}
})
.catch(error => {
alert.error(error.toString())
})
}
const handleGithubValidation = () => {
importWorkflowsFromUrl(downloadUrl)
setLoadWorkflowsModalOpen(false)
}
const workflowDownloadModalOpen = loadWorkflowsModalOpen ?
<Dialog modal
open={loadWorkflowsModalOpen}
onClose={() => {
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: "800px",
minHeight: "320px",
},
}}
>
<DialogTitle>
<div style={{color: "rgba(255,255,255,0.9)"}}>
Load workflows from github repo
<div style={{float: "right"}}>
<Tooltip color="primary" title={"Import manually"} placement="top">
<Button color="primary" style={{}} variant="text" onClick={() => upload.click()}>
<PublishIcon />
</Button>
</Tooltip>
</div>
</div>
</DialogTitle>
<DialogContent style={{color: "rgba(255,255,255,0.65)"}}>
Repository (supported: github, gitlab, bitbucket)
<TextField
style={{backgroundColor: inputColor}}
variant="outlined"
margin="normal"
value={downloadUrl}
InputProps={{
style:{
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={e => setDownloadUrl(e.target.value)}
placeholder="https://github.com/frikky/shuffle-apps"
fullWidth
/>
<span style={{marginTop: 10}}>Authentication (optional - private repos etc):</span>
<div style={{display: "flex"}}>
<TextField
style={{flex: 1, backgroundColor: inputColor}}
variant="outlined"
margin="normal"
InputProps={{
style:{
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={e => setField1(e.target.value)}
type="username"
placeholder="Username / APIkey (optional)"
fullWidth
/>
<TextField
style={{flex: 1, backgroundColor: inputColor}}
variant="outlined"
margin="normal"
InputProps={{
style:{
color: "white",
height: "50px",
fontSize: "1em",
},
}}
onChange={e => setField2(e.target.value)}
type="password"
placeholder="Password (optional)"
fullWidth
/>
</div>
</DialogContent>
<DialogActions>
<Button style={{borderRadius: "0px"}} onClick={() => setLoadWorkflowsModalOpen(false)} color="primary">
Cancel
</Button>
<Button style={{borderRadius: "0px"}} disabled={downloadUrl.length === 0 || !downloadUrl.includes("http")} onClick={() => {
handleGithubValidation()
}} color="primary">
Submit
</Button>
</DialogActions>
</Dialog>
: null
const loadedCheck = isLoaded && isLoggedIn && workflowDone ?
<div>
{workflowView}
{modalView}
{workflowDownloadModalOpen}
</div>
:
<div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+26
View File
@@ -0,0 +1,26 @@
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<!---->
<defs>
<linearGradient y2="0%" x2="100%" y1="0%" x1="0%" id="30c29011-a081-4741-b6bb-e06d8873e7b7" gradientTransform="rotate(25)">
<stop stop-color=" rgb(169, 37, 128)" offset="0%"/>
<stop stop-color=" rgb(247, 188, 0)" offset="100%"/>
</linearGradient>
</defs>
<!---->
<!---->
<!---->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="202" width="202" y="-1" x="-1"/>
</g>
<g>
<title>Layer 1</title>
<g fill="url(#30c29011-a081-4741-b6bb-e06d8873e7b7)" transform="matrix(1.1414221157695137,0,0,1.1414221157695137,-10.343879888974278,-11.465698853151771) " id="1085c47b-b6bc-4e6d-b1f9-4301cfb34be7">
<switch transform="translate(2.6282968521118164,0) translate(0.0000033420565159758553,0) translate(-0.2776981592178345,0) translate(44.68110275268555,47.30940246582031) ">
<g id="svg_2">
<path id="svg_3" d="m89.141,35.617c-2.891,-12.765 -11.297,-21.212 -20.442,-25.253c11.371,10.018 21.846,29.405 8.814,47.069c-5.406,7.331 -16.217,10.228 -20.746,8.184c0,0 7.002,-1.487 11.357,-5.295c8.469,-8.017 11.299,-20.932 4.52,-32.673a25.839,25.839 0 0 0 -3.357,-4.575c-0.018,-0.021 -0.033,-0.042 -0.051,-0.062c-4.764,-5.683 -11.307,-9.075 -18.337,-10.116c-9.127,-1.715 -20.896,0.515 -29.71,8.666c-9.609,8.888 -12.721,20.391 -11.647,30.333c2.989,-14.858 14.542,-33.622 36.355,-31.171c9.053,1.019 16.965,8.932 17.461,13.877c0,0 -5.277,-8.281 -18.365,-8.281c-0.24,0.004 -0.747,0.024 -0.761,0.024l-0.167,0.01c-8.51,0.333 -16.783,4.784 -21.83,13.526a25.819,25.819 0 0 0 -2.284,5.195l-0.028,0.074c-2.539,6.968 -2.205,14.33 0.407,20.938c3.079,8.763 10.896,17.841 22.361,21.397c12.502,3.878 24.02,0.82 32.092,-5.079c-14.363,4.837 -36.389,4.216 -45.173,-15.899c-3.645,-8.35 -0.749,-19.159 3.287,-22.061c0,0 -4.534,8.71 2.012,20.044c4.482,7.484 12.617,12.658 22.949,12.658c0.498,0 4.488,-0.311 5.926,-0.633l0.08,-0.011c7.303,-1.286 13.512,-5.256 17.928,-10.822c6.05,-7.046 10.003,-18.356 7.349,-30.064z"/>
</g>
</switch>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 440 KiB

-33
View File
@@ -1,33 +0,0 @@
# Build script to make it work as an open source platform
# 1. Grab builtin functions - Done in backend as a button click
# 2. Upload to database & build docker images
# 3. Run docker-compose: frontend, backend, database & orborus
# 4. Set up docker swarm for apps?
# Basic overview of how it works:
#
# Backend: o
# |
# Orborus: o
# / \
# Workers: o o
# / / \
# Apps: o o o
# 1. Grab builtin functions
# Where should I have these? Maybe OpenAPI github repo and just preload?
echo "Building frontend"
cd frontend
npm run build
rm -rf ../backend/go-app/build
cp -r build/ ../backend/go-app/build
echo "Setting up backend"
cd ../backend/go-app
go build
go test
#gcloud app deploy $GOPATH/src/github.com/frikky/shuffle/app.yaml
echo "This script is not finished. Please follow the docker installation guide here: https://github.com/frikky/shuffle/blob/master/install-guide.md"