Fixed merge issues
This commit is contained in:
@@ -21,6 +21,7 @@ SHUFFLE_DEFAULT_APIKEY=
|
|||||||
|
|
||||||
# Local location of your app directory. Can't use ~/
|
# Local location of your app directory. Can't use ~/
|
||||||
# Files will get better at some point. Right now: local saving.
|
# Files will get better at some point. Right now: local saving.
|
||||||
|
SHUFFLE_APP_HOTLOAD_FOLDER=./shuffle-apps
|
||||||
SHUFFLE_APP_HOTLOAD_LOCATION=./shuffle-apps
|
SHUFFLE_APP_HOTLOAD_LOCATION=./shuffle-apps
|
||||||
SHUFFLE_FILE_LOCATION=./shuffle-files
|
SHUFFLE_FILE_LOCATION=./shuffle-files
|
||||||
|
|
||||||
@@ -29,18 +30,33 @@ BACKEND_HOSTNAME=shuffle-backend
|
|||||||
BACKEND_PORT=5001
|
BACKEND_PORT=5001
|
||||||
FRONTEND_PORT=3001
|
FRONTEND_PORT=3001
|
||||||
FRONTEND_PORT_HTTPS=3443
|
FRONTEND_PORT_HTTPS=3443
|
||||||
|
# CHANGE THIS IF YOU WANT GOOD LOCAL EXECUTIONS:
|
||||||
OUTER_HOSTNAME=shuffle-backend
|
OUTER_HOSTNAME=shuffle-backend
|
||||||
DB_LOCATION=./shuffle-database
|
DB_LOCATION=./shuffle-database
|
||||||
|
|
||||||
# Proxy configurations. SHUFFLE_PASS_WORKER_PROXY must be FALSE to not pass the proxy information to sub-apps.
|
# Proxy configurations. SHUFFLE_PASS_WORKER_PROXY must be FALSE to not pass the proxy information to sub-apps.
|
||||||
# PS: It will skip proxy for
|
# PS: It will skip proxy for
|
||||||
SHUFFLE_HTTP_PROXY=
|
HTTP_PROXY=
|
||||||
SHUFFLE_HTTPS_PROXY=
|
HTTPS_PROXY=
|
||||||
SHUFFLE_PASS_WORKER_PROXY=TRUE
|
SHUFFLE_PASS_WORKER_PROXY=TRUE
|
||||||
|
SHUFFLE_PASS_APP_PROXY=FALSE
|
||||||
|
|
||||||
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
|
SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io
|
||||||
SHUFFLE_BASE_IMAGE_NAME=frikky
|
SHUFFLE_BASE_IMAGE_NAME=frikky
|
||||||
SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-0.8.60"
|
SHUFFLE_BASE_IMAGE_TAG_SUFFIX="-0.8.80"
|
||||||
|
|
||||||
# Used for auto-cleanup of containers. REALLY important at scale.
|
# Used for auto-cleanup of containers. REALLY important at scale.
|
||||||
SHUFFLE_CONTAINER_AUTO_CLEANUP=false
|
SHUFFLE_CONTAINER_AUTO_CLEANUP=false
|
||||||
|
SHUFFLE_ELASTIC=true
|
||||||
|
|
||||||
|
# DATABASE CONFIGURATIONS
|
||||||
|
DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
||||||
|
#SHUFFLE_OPENSEARCH_URL=https://shuffle-opensearch:9200
|
||||||
|
SHUFFLE_OPENSEARCH_URL=http://shuffle-opensearch:9200
|
||||||
|
SHUFFLE_OPENSEARCH_USERNAME=
|
||||||
|
SHUFFLE_OPENSEARCH_PASSWORD=
|
||||||
|
SHUFFLE_OPENSEARCH_CERTIFICATE_FILE=
|
||||||
|
SHUFFLE_OPENSEARCH_APIKEY=
|
||||||
|
SHUFFLE_OPENSEARCH_CLOUDID=
|
||||||
|
SHUFFLE_OPENSEARCH_PROXY=
|
||||||
|
SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true
|
||||||
|
|||||||
+43
-49
@@ -1,23 +1,40 @@
|
|||||||
# Installation guide
|
# Installation guide
|
||||||
Installation of Shuffle is currently only available in docker. Looking for how to update Shuffle? Check the [updating guide](https://shuffler.io/docs/configuration#updating_shuffle)
|
Installation of Shuffle is currently only available in docker. Looking for how to update Shuffle? Check the [updating guide](https://shuffler.io/docs/configuration#updating_shuffle)
|
||||||
|
|
||||||
## Docker
|
This document outlines a an introduction environment which is not scalable. [Read here](https://shuffler.io/docs/configuration#production_readiness) for information on production readiness.
|
||||||
The Docker setup is done with docker-compose and is a single command to get set up.
|
|
||||||
|
# Docker - *nix
|
||||||
|
The Docker setup is done with docker-compose
|
||||||
|
|
||||||
**PS: if you're setting up Shuffle on Windows, go to the next step (Windows Docker setup)**
|
**PS: if you're setting up Shuffle on Windows, go to the next step (Windows Docker setup)**
|
||||||
|
|
||||||
1. Make sure you have Docker and [docker-compose](https://docs.docker.com/compose/install/) installed.
|
1. Make sure you have [Docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) installed.
|
||||||
2. Adapt the .env file to your convenience.
|
2. Download Shuffle
|
||||||
3. Run docker-compose.
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/frikky/Shuffle
|
git clone https://github.com/frikky/Shuffle
|
||||||
cd Shuffle
|
cd Shuffle
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Fix prerequisites for the Opensearch database (Elasticsearch):
|
||||||
|
```
|
||||||
|
sudo chown 1000:1000 -R shuffle-database # Required for Opensearch
|
||||||
|
sudo sysctl -w vm.max_map_count=262144 # https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html
|
||||||
|
|
||||||
|
# To make the changes permanent, do:
|
||||||
|
# 1. Open the file /etc/sysctl.conf
|
||||||
|
# 2. Go to the bottom of the file
|
||||||
|
# 3. Add this line:
|
||||||
|
vm.max_map_count=262144
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run docker-compose.
|
||||||
|
```
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
When you're done, skip to the "After installation" step below.
|
When you're done, skip to the "After installation" step below.
|
||||||
|
|
||||||
## Windows Docker setup
|
## Windows with WSL
|
||||||
This step is for setting up with Docker on windows from scratch.
|
This step is for setting up with Docker on windows from scratch.
|
||||||
|
|
||||||
1. Make sure you have [Docker](https://docs.docker.com/docker-for-windows/install/) and [docker-compose](https://docs.docker.com/compose/install/) installed. WSL2 may be required.
|
1. Make sure you have [Docker](https://docs.docker.com/docker-for-windows/install/) and [docker-compose](https://docs.docker.com/compose/install/) installed. WSL2 may be required.
|
||||||
@@ -27,7 +44,18 @@ This step is for setting up with Docker on windows from scratch.
|
|||||||
```
|
```
|
||||||
OUTER_HOSTNAME=YOUR.IP.HERE
|
OUTER_HOSTNAME=YOUR.IP.HERE
|
||||||
```
|
```
|
||||||
5. Run docker-compose
|
|
||||||
|
5. Configure max memory (WSL) by opening a new CMD/Powershell window. Required for Elasticsearch
|
||||||
|
```
|
||||||
|
wsl -d docker-desktop
|
||||||
|
sysctl -w vm.max_map_count=262144
|
||||||
|
echo "vm.max_map_count = 262144" > /etc/sysctl.d/99-docker-desktop.conf
|
||||||
|
echo -e "\nvm.max_map_count = 262144\n" >> /etc/sysctl.d/00-alpine.conf
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/42111566/elasticsearch-in-windows-docker-image-vm-max-map-count
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Run docker-compose
|
||||||
```
|
```
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
@@ -35,53 +63,20 @@ docker-compose up -d
|
|||||||
### Configurations (proxies, default users etc.)
|
### Configurations (proxies, default users etc.)
|
||||||
https://shuffler.io/docs/configuration
|
https://shuffler.io/docs/configuration
|
||||||
|
|
||||||
### After installation
|
### After installation
|
||||||
1. After installation, go to http://localhost:3001/adminsetup (or your servername)
|
1. After installation, go to http://localhost:3001/adminsetup (or your servername - https is on port 3443)
|
||||||
|
2. Now set up your admin account (username & password). Shuffle doesn't have a default username and password.
|
||||||
2. Now set up your admin account (username & password). Shuffle doesn't have a default username and password.
|
3. Sign in with the same Username & Password! Go to /apps and see if you have any apps yet. If not - you may need to [configure proxies](https://shuffler.io/docs/configuration#production_readiness)
|
||||||
3. Check out https://shuffler.io/docs/configuration as it has a lot of useful information to get started
|
4. Check out https://shuffler.io/docs/configuration as it has a lot of useful information to get started
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Useful info
|
### Useful info
|
||||||
* Check out [getting started](https://shuffler.io/docs/getting_started)
|
* Check out [getting started](https://shuffler.io/docs/getting_started)
|
||||||
|
* The default state of Shuffle is NOT scalable. See [production setup](https://shuffler.io/docs/configuration#production_readiness) for more info
|
||||||
* The server is available on http://localhost:3001 (or your servername)
|
* The server is available on http://localhost:3001 (or your servername)
|
||||||
* Further configurations can be done in docker-compose.yml and .env.
|
* Further configurations can be done in docker-compose.yml and .env.
|
||||||
* Default database location is /etc/shuffle
|
* Default database location is in the same folder: ./shuffle-database
|
||||||
|
|
||||||
### Execution problems
|
|
||||||
If you have problems with your first execution (hello world), you might need to set the correct Docker API version. Here's how:
|
|
||||||
|
|
||||||
1. Find your API version by running "docker version"
|
|
||||||
```
|
|
||||||
$ docker version
|
|
||||||
|
|
||||||
Client:
|
|
||||||
Version: 17.09.1-ce
|
|
||||||
API version: 1.32 # <-- this one
|
|
||||||
Go version: go1.8.3
|
|
||||||
Git commit: 19e2cf6
|
|
||||||
Built: Thu Dec 7 22:24:16 2017
|
|
||||||
OS/Arch: linux/amd64
|
|
||||||
|
|
||||||
Server:
|
|
||||||
Version: 17.09.1-ce
|
|
||||||
API version: 1.32 (minimum version 1.12)
|
|
||||||
Go version: go1.8.3
|
|
||||||
Git commit: 19e2cf6
|
|
||||||
Built: Thu Dec 7 22:22:56 2017
|
|
||||||
OS/Arch: linux/amd64
|
|
||||||
Experimental: false
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Open docker-compose.yml and change the line with "DOCKER_API_VERSION" to your version.
|
|
||||||
3. Restart docker-compose
|
|
||||||
```
|
|
||||||
docker-compose down
|
|
||||||
docker-compose up
|
|
||||||
```
|
|
||||||
|
|
||||||
Related issue: #47
|
|
||||||
|
|
||||||
# Local development installation
|
# Local development installation
|
||||||
Local development is pretty straight forward with **ReactJS** and **Golang**. This part is intended to help you run the code for development purposes.
|
Local development is pretty straight forward with **ReactJS** and **Golang**. This part is intended to help you run the code for development purposes.
|
||||||
@@ -100,7 +95,7 @@ npm start
|
|||||||
## Backend - Golang
|
## Backend - Golang
|
||||||
http://localhost:5001 - REST API - requires [>=go1.13](https://golang.org/dl/)
|
http://localhost:5001 - REST API - requires [>=go1.13](https://golang.org/dl/)
|
||||||
```bash
|
```bash
|
||||||
export DATASTORE_EMULATOR_HOST=0.0.0.0:8000
|
export SHUFFLE_OPENSEARCH_URL="http://localhost:9200"
|
||||||
cd backend/go-app
|
cd backend/go-app
|
||||||
go run *.go
|
go run *.go
|
||||||
```
|
```
|
||||||
@@ -121,7 +116,6 @@ cd functions/onprem/orborus
|
|||||||
go run orborus.go
|
go run orborus.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Environments (modify for Windows):
|
Environments (modify for Windows):
|
||||||
```
|
```
|
||||||
export ORG_ID=Shuffle
|
export ORG_ID=Shuffle
|
||||||
|
|||||||
+6
-1
@@ -19,6 +19,11 @@ functions/generated_apps
|
|||||||
backend/onprem/app_sdk/apps
|
backend/onprem/app_sdk/apps
|
||||||
*test.py
|
*test.py
|
||||||
|
|
||||||
shuffle-database
|
|
||||||
*.exe
|
*.exe
|
||||||
*debug*
|
*debug*
|
||||||
|
|
||||||
|
shuffle-database/batch_metrics_enabled.conf
|
||||||
|
shuffle-database/logging_enabled.conf
|
||||||
|
shuffle-database/nodes
|
||||||
|
shuffle-database/performance_analyzer_enabled.conf
|
||||||
|
shuffle-database/rca_enabled.conf
|
||||||
|
|||||||
+357
-101
@@ -22,6 +22,7 @@ class AppBase:
|
|||||||
|
|
||||||
# apikey is for the user / org
|
# apikey is for the user / org
|
||||||
# authorization is for the specific workflow
|
# authorization is for the specific workflow
|
||||||
|
|
||||||
self.url = os.getenv("CALLBACK_URL", "https://shuffler.io")
|
self.url = os.getenv("CALLBACK_URL", "https://shuffler.io")
|
||||||
self.base_url = os.getenv("BASE_URL", "https://shuffler.io")
|
self.base_url = os.getenv("BASE_URL", "https://shuffler.io")
|
||||||
self.action = os.getenv("ACTION", "")
|
self.action = os.getenv("ACTION", "")
|
||||||
@@ -54,7 +55,7 @@ class AppBase:
|
|||||||
# for action["parameters"]
|
# for action["parameters"]
|
||||||
# print("AUTH: ", key, value)
|
# print("AUTH: ", key, value)
|
||||||
# params[item["key"]] = item["value"]
|
# params[item["key"]] = item["value"]
|
||||||
#except KeyError:
|
#except KeyError:
|
||||||
# print("No authentication specified!")
|
# print("No authentication specified!")
|
||||||
# pass
|
# pass
|
||||||
|
|
||||||
@@ -513,10 +514,31 @@ class AppBase:
|
|||||||
# return
|
# return
|
||||||
|
|
||||||
print("[INFO] Multiplier length: %d" % len(param_multiplier))
|
print("[INFO] Multiplier length: %d" % len(param_multiplier))
|
||||||
|
#tmp = ""
|
||||||
for subparams in param_multiplier:
|
for subparams in param_multiplier:
|
||||||
print(f"SUBPARAMS IN MULTI: {subparams}")
|
print(f"SUBPARAMS IN MULTI: {subparams}")
|
||||||
try:
|
try:
|
||||||
tmp = await func(**subparams)
|
#tmp = await func(**subparams)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
tmp = await func(**subparams)
|
||||||
|
break
|
||||||
|
except TypeError as e:
|
||||||
|
errorstring = "%s" % e
|
||||||
|
if "got an unexpected keyword argument" in errorstring:
|
||||||
|
fieldsplit = errorstring.split("'")
|
||||||
|
if len(fieldsplit) > 1:
|
||||||
|
field = fieldsplit[1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
del subparams[field]
|
||||||
|
print("Removed field invalid field %s" % field)
|
||||||
|
except KeyError:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
except:
|
except:
|
||||||
e = ""
|
e = ""
|
||||||
try:
|
try:
|
||||||
@@ -661,6 +683,59 @@ class AppBase:
|
|||||||
else:
|
else:
|
||||||
return returns
|
return returns
|
||||||
|
|
||||||
|
def set_cache(self, key, value):
|
||||||
|
org_id = self.full_execution["workflow"]["execution_org"]["id"]
|
||||||
|
url = "%s/api/v1/orgs/%s/set_cache" % (self.url, org_id)
|
||||||
|
data = {
|
||||||
|
"workflow_id": self.full_execution["workflow"]["id"],
|
||||||
|
"execution_id": self.current_execution_id,
|
||||||
|
"authorization": self.authorization,
|
||||||
|
"org_id": org_id,
|
||||||
|
"key": key,
|
||||||
|
"value": str(value),
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url, json=data)
|
||||||
|
try:
|
||||||
|
allvalues = response.json()
|
||||||
|
allvalues["key"] = key
|
||||||
|
allvalues["value"] = str(value)
|
||||||
|
return allvalues
|
||||||
|
except:
|
||||||
|
print("Value couldn't be parsed")
|
||||||
|
#return response.json()
|
||||||
|
return {"success": False}
|
||||||
|
|
||||||
|
def get_cache(self, key):
|
||||||
|
org_id = self.full_execution["workflow"]["execution_org"]["id"]
|
||||||
|
url = "%s/api/v1/orgs/%s/get_cache" % (self.url, org_id)
|
||||||
|
data = {
|
||||||
|
"workflow_id": self.full_execution["workflow"]["id"],
|
||||||
|
"execution_id": self.current_execution_id,
|
||||||
|
"authorization": self.authorization,
|
||||||
|
"org_id": org_id,
|
||||||
|
"key": key,
|
||||||
|
}
|
||||||
|
|
||||||
|
value = requests.post(url, json=data)
|
||||||
|
try:
|
||||||
|
allvalues = value.json()
|
||||||
|
print("VAL1: ", allvalues)
|
||||||
|
allvalues["key"] = key
|
||||||
|
print("VAL2: ", allvalues)
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsedvalue = json.loads(allvalues["value"])
|
||||||
|
allvalues["value"] = parsedvalue
|
||||||
|
except:
|
||||||
|
print("Parsing of value as JSON failed. Continue anyway!")
|
||||||
|
|
||||||
|
return allvalues
|
||||||
|
except:
|
||||||
|
print("Value couldn't be parsed, or json dump of value failed")
|
||||||
|
#return value.json()
|
||||||
|
return {"success": False}
|
||||||
|
|
||||||
# Sets files in the backend
|
# Sets files in the backend
|
||||||
def set_files(self, infiles):
|
def set_files(self, infiles):
|
||||||
full_execution = self.full_execution
|
full_execution = self.full_execution
|
||||||
@@ -859,7 +934,6 @@ class AppBase:
|
|||||||
return parse_nested_param(string + ')', level)
|
return parse_nested_param(string + ')', level)
|
||||||
elif len(re.findall("\(", string)) < len(re.findall("\)", string)):
|
elif len(re.findall("\(", string)) < len(re.findall("\)", string)):
|
||||||
return parse_nested_param('(' + string, level)
|
return parse_nested_param('(' + string, level)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return 'Failed to parse params'
|
return 'Failed to parse params'
|
||||||
|
|
||||||
@@ -950,23 +1024,25 @@ class AppBase:
|
|||||||
print(f"JSON ERROR in join(): {e}")
|
print(f"JSON ERROR in join(): {e}")
|
||||||
|
|
||||||
if "len" in thistype or "length" in thistype or "lenght" in thistype:
|
if "len" in thistype or "length" in thistype or "lenght" in thistype:
|
||||||
tmp = ""
|
print(f"Trying to length-parse: {data}")
|
||||||
try:
|
try:
|
||||||
tmp = json.loads(tmpdata)
|
tmp_len = json.loads(data, parse_float=str, parse_int=str, parse_constant=str)
|
||||||
except:
|
except (NameError, KeyError, TypeError, json.decoder.JSONDecodeError) as e:
|
||||||
try:
|
try:
|
||||||
tmpdata = data.replace("\'", "\"")
|
print(f"[WARNING] INITIAL Parsing bug for length in app sdk: {e}")
|
||||||
tmp = json.loads(tmpdata)
|
# data = data.replace("\'", "\"")
|
||||||
except:
|
data = data.replace("True", "true")
|
||||||
print("[ERROR] Parsing bug for length in app sdk")
|
data = data.replace("False", "false")
|
||||||
pass
|
data = data.replace("None", "null")
|
||||||
|
data = data.replace("\"", "\\\"")
|
||||||
|
data = data.replace("'", "\"")
|
||||||
|
|
||||||
if isinstance(tmp, list):
|
tmp_len = json.loads(data, parse_float=str, parse_int=str, parse_constant=str)
|
||||||
return str(len(tmp))
|
except (NameError, KeyError, TypeError, json.decoder.JSONDecodeError) as e:
|
||||||
elif isinstance(tmp, object):
|
tmp_len = str(data)
|
||||||
return str(len(tmp))
|
|
||||||
|
return str(len(tmp_len))
|
||||||
|
|
||||||
return str(len(data))
|
|
||||||
if "parse" in thistype:
|
if "parse" in thistype:
|
||||||
splitvalues = []
|
splitvalues = []
|
||||||
default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)"""
|
default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)"""
|
||||||
@@ -1003,49 +1079,65 @@ class AppBase:
|
|||||||
def parse_wrapper(data):
|
def parse_wrapper(data):
|
||||||
try:
|
try:
|
||||||
if "(" not in data or ")" not in data:
|
if "(" not in data or ")" not in data:
|
||||||
return (data, False)
|
return data, False
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return (data, False)
|
return data, False
|
||||||
|
|
||||||
#print("Running %s" % data)
|
wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length", "lenght",
|
||||||
|
"join"]
|
||||||
# Look for the INNER wrapper first, then move out
|
|
||||||
wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length", "lenght", "join"]
|
if not any(wrapper in data for wrapper in wrappers):
|
||||||
found = False
|
return data, False
|
||||||
for wrapper in wrappers:
|
|
||||||
if wrapper not in data.lower():
|
|
||||||
continue
|
|
||||||
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
return (data, False)
|
|
||||||
|
|
||||||
# Do stuff here.
|
# Do stuff here.
|
||||||
innervalue = parse_nested_param(data, maxDepth(data)-0)
|
inner_value = parse_nested_param(data, maxDepth(data) - 0)
|
||||||
outervalue = parse_nested_param(data, maxDepth(data)-1)
|
outer_value = parse_nested_param(data, maxDepth(data) - 1)
|
||||||
print("INNER: ", innervalue)
|
|
||||||
print("OUTER: ", outervalue)
|
print("INNER: ", inner_value)
|
||||||
|
print("OUTER: ", outer_value)
|
||||||
if outervalue != innervalue:
|
|
||||||
#print("Outer: ", outervalue, " inner: ", innervalue)
|
wrapper_group = "|".join(wrappers)
|
||||||
for key in range(len(innervalue)):
|
parse_string = data
|
||||||
# Replace OUTERVALUE[key] with INNERVALUE[key] in data.
|
max_depth = maxDepth(parse_string)
|
||||||
print("Replace %s with %s in %s" % (outervalue[key], innervalue[key], data))
|
|
||||||
data = data.replace(outervalue[key], innervalue[key])
|
if outer_value != inner_value:
|
||||||
else:
|
for casting_items in reversed(range(max_depth + 1)):
|
||||||
for thistype in wrappers:
|
c_parentheses = parse_nested_param(parse_string, casting_items)[0]
|
||||||
if thistype.lower() not in data.lower():
|
match_string = re.escape(c_parentheses)
|
||||||
|
custom_casting = re.findall(fr"({wrapper_group})\({match_string}", parse_string)
|
||||||
|
|
||||||
|
# no matching ; go next group
|
||||||
|
if len(custom_casting) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
parsed_value = parse_type(innervalue[0], thistype.lower())
|
inner_result = parse_type(c_parentheses, custom_casting[0])
|
||||||
print("Parsed value from %s: %s" % (thistype, parsed_value))
|
|
||||||
return (parsed_value, True)
|
# if result is a string then parse else return
|
||||||
|
if isinstance(inner_result, str):
|
||||||
#print("DATA: %s\n" % data)
|
parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})", inner_result)
|
||||||
return (parse_wrapper(data)[0], True)
|
elif isinstance(inner_result, list):
|
||||||
|
parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})",
|
||||||
|
json.dumps(inner_result))
|
||||||
|
else:
|
||||||
|
parse_string = inner_result
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
c_parentheses = parse_nested_param(parse_string, 0)[0]
|
||||||
|
match_string = re.escape(c_parentheses)
|
||||||
|
custom_casting = re.findall(fr"({wrapper_group})\({match_string}", parse_string)
|
||||||
|
|
||||||
|
# check if a wrapper was found
|
||||||
|
if len(custom_casting) != 0:
|
||||||
|
inner_result = parse_type(c_parentheses, custom_casting[0])
|
||||||
|
if isinstance(inner_result, str):
|
||||||
|
parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})", inner_result)
|
||||||
|
elif isinstance(inner_result, list):
|
||||||
|
parse_string = parse_string.replace(f"{custom_casting[0]}({c_parentheses})",
|
||||||
|
json.dumps(inner_result))
|
||||||
|
else:
|
||||||
|
parse_string = inner_result
|
||||||
|
|
||||||
|
return parse_string, True
|
||||||
|
|
||||||
# Looks for parantheses to grab special cases within a string, e.g:
|
# Looks for parantheses to grab special cases within a string, e.g:
|
||||||
# int(1) lower(HELLO) or length(what's the length)
|
# int(1) lower(HELLO) or length(what's the length)
|
||||||
@@ -1121,15 +1213,19 @@ class AppBase:
|
|||||||
|
|
||||||
# Parses JSON loops and such down to the item you're looking for
|
# Parses JSON loops and such down to the item you're looking for
|
||||||
def recurse_json(basejson, parsersplit):
|
def recurse_json(basejson, parsersplit):
|
||||||
match = "#(\d+):?-?([0-9a-z]+)?#?"
|
match = "#([0-9a-z]+):?-?([0-9a-z]+)?#?"
|
||||||
#print("Split: %s\n%s" % (parsersplit, basejson))
|
#print("Split: %s\n%s" % (parsersplit, basejson))
|
||||||
try:
|
try:
|
||||||
outercnt = 0
|
outercnt = 0
|
||||||
|
|
||||||
# Loops over split values
|
# Loops over split values
|
||||||
for value in parsersplit:
|
for value in parsersplit:
|
||||||
|
#if " " in value:
|
||||||
|
# value = value.replace(" ", "_", -1)
|
||||||
|
|
||||||
#print("VALUE: %s\n" % value)
|
#print("VALUE: %s\n" % value)
|
||||||
actualitem = re.findall(match, value, re.MULTILINE)
|
actualitem = re.findall(match, value, re.MULTILINE)
|
||||||
|
#print("ACTUAL RECURSE: (%s) %s" % (value, actualitem))
|
||||||
if value == "#":
|
if value == "#":
|
||||||
newvalue = []
|
newvalue = []
|
||||||
for innervalue in basejson:
|
for innervalue in basejson:
|
||||||
@@ -1158,7 +1254,12 @@ class AppBase:
|
|||||||
|
|
||||||
# Means it's a single item -> continue
|
# Means it's a single item -> continue
|
||||||
if seconditem == "":
|
if seconditem == "":
|
||||||
#print("[INFO] In first - handling %s" % firstitem)
|
print("[INFO] In first - handling %s. Len: %d" % (firstitem, len(basejson)))
|
||||||
|
if firstitem.lower() == "max" or firstitem.lower() == "last":
|
||||||
|
firstitem = len(basejson)-1
|
||||||
|
if firstitem.lower() == "min" or firstitem.lower() == "first":
|
||||||
|
firstitem = 0
|
||||||
|
|
||||||
tmpitem = basejson[int(firstitem)]
|
tmpitem = basejson[int(firstitem)]
|
||||||
try:
|
try:
|
||||||
newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:])
|
newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:])
|
||||||
@@ -1166,16 +1267,23 @@ class AppBase:
|
|||||||
newvalue, is_loop = (tmpitem, parsersplit[outercnt+1:])
|
newvalue, is_loop = (tmpitem, parsersplit[outercnt+1:])
|
||||||
else:
|
else:
|
||||||
print("[INFO] In ELSE - handling %s and %s" % (firstitem, seconditem))
|
print("[INFO] In ELSE - handling %s and %s" % (firstitem, seconditem))
|
||||||
if seconditem == "max":
|
if firstitem.lower() == "max" or firstitem.lower() == "last":
|
||||||
seconditem = len(basejson)
|
firstitem = len(basejson)-1
|
||||||
if seconditem == "min":
|
if firstitem.lower() == "min" or firstitem.lower() == "first":
|
||||||
|
firstitem = 0
|
||||||
|
if seconditem.lower() == "max" or seconditem.lower() == "last":
|
||||||
|
seconditem = len(basejson)-1
|
||||||
|
if seconditem.lower() == "min" or seconditem.lower() == "first":
|
||||||
seconditem = 0
|
seconditem = 0
|
||||||
|
|
||||||
newvalue = []
|
newvalue = []
|
||||||
for i in range(int(firstitem), int(seconditem)):
|
if int(seconditem) > len(basejson):
|
||||||
|
seconditem = len(basejson)
|
||||||
|
|
||||||
|
for i in range(int(firstitem), int(seconditem)+1):
|
||||||
# 1. Check the next item (message)
|
# 1. Check the next item (message)
|
||||||
# 2. Call this function again
|
# 2. Call this function again
|
||||||
print("Base: %s" % basejson[i])
|
#print("Base: %s" % basejson[i])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret, is_loop = recurse_json(basejson[i], parsersplit[outercnt+1:])
|
ret, is_loop = recurse_json(basejson[i], parsersplit[outercnt+1:])
|
||||||
@@ -1184,10 +1292,11 @@ class AppBase:
|
|||||||
#ret = innervalue
|
#ret = innervalue
|
||||||
ret, is_loop = recurse_json(innervalue, parsersplit[outercnt:])
|
ret, is_loop = recurse_json(innervalue, parsersplit[outercnt:])
|
||||||
|
|
||||||
print(ret)
|
#print("IN LIST: %s" % ret)
|
||||||
#exit()
|
#exit()
|
||||||
newvalue.append(ret)
|
newvalue.append(ret)
|
||||||
|
|
||||||
|
#print("Returning %s" % newvalue)
|
||||||
return newvalue, is_loop
|
return newvalue, is_loop
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1195,16 +1304,43 @@ class AppBase:
|
|||||||
if len(value) == 0:
|
if len(value) == 0:
|
||||||
return basejson, False
|
return basejson, False
|
||||||
|
|
||||||
if isinstance(basejson[value], str):
|
try:
|
||||||
print(f"LOADING STRING '%s' AS JSON" % basejson[value])
|
if isinstance(basejson, list):
|
||||||
try:
|
print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (list): %s" % value)
|
||||||
basejson = json.loads(basejson[value])
|
return basejson, False
|
||||||
except json.decoder.JSONDecodeError as e:
|
elif isinstance(basejson[value], str):
|
||||||
print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value])
|
print(f"[INFO] LOADING STRING '%s' AS JSON" % basejson[value])
|
||||||
return basejson[value], False
|
try:
|
||||||
else:
|
basejson = json.loads(basejson[value])
|
||||||
basejson = basejson[value]
|
print("BASEJSON: %s" % basejson)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value])
|
||||||
|
return basejson[value], False
|
||||||
|
else:
|
||||||
|
basejson = basejson[value]
|
||||||
|
except KeyError as e:
|
||||||
|
print("[WARNING] Running secondary value check with replacement of underscore in %s: %s" % (value, e))
|
||||||
|
if "_" in value:
|
||||||
|
value = value.replace("_", " ", -1)
|
||||||
|
elif " " in value:
|
||||||
|
value = value.replace(" ", "_", -1)
|
||||||
|
|
||||||
|
if isinstance(basejson, list):
|
||||||
|
print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (list): %s" % value)
|
||||||
|
return basejson, False
|
||||||
|
elif isinstance(basejson[value], str):
|
||||||
|
print(f"[INFO] LOADING STRING '%s' AS JSON" % basejson[value])
|
||||||
|
try:
|
||||||
|
basejson = json.loads(basejson[value])
|
||||||
|
print("BASEJSON: %s" % basejson)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value])
|
||||||
|
return basejson[value], False
|
||||||
|
else:
|
||||||
|
basejson = basejson[value]
|
||||||
|
|
||||||
|
|
||||||
|
#print("Parsed BASEJSON: %s" % basejson)
|
||||||
outercnt += 1
|
outercnt += 1
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
@@ -1240,12 +1376,28 @@ class AppBase:
|
|||||||
appendresult += char
|
appendresult += char
|
||||||
|
|
||||||
actionname_lower = "exec"
|
actionname_lower = "exec"
|
||||||
|
elif actionname_lower.startswith("shuffle_cache "):
|
||||||
|
actionname_lower = "shuffle_cache"
|
||||||
|
|
||||||
actionname_lower = actionname_lower.replace(" ", "_", -1)
|
actionname_lower = actionname_lower.replace(" ", "_", -1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if actionname_lower == "exec" or actionname_lower == "webhook" or actionname_lower == "schedule" or actionname_lower == "userinput" or actionname_lower == "email_trigger" or actionname_lower == "trigger":
|
if actionname_lower == "exec" or actionname_lower == "webhook" or actionname_lower == "schedule" or actionname_lower == "userinput" or actionname_lower == "email_trigger" or actionname_lower == "trigger":
|
||||||
baseresult = execution_data["execution_argument"]
|
baseresult = execution_data["execution_argument"]
|
||||||
|
elif actionname_lower == "shuffle_cache":
|
||||||
|
print("SHOULD GET CACHE KEY: %s" % parsersplit)
|
||||||
|
if len(parsersplit) > 1:
|
||||||
|
actual_key = parsersplit[1]
|
||||||
|
print("KEY: %s" % actual_key)
|
||||||
|
cachedata = self.get_cache(actual_key)
|
||||||
|
print("CACHE: %s" % cachedata)
|
||||||
|
parsersplit.pop(1)
|
||||||
|
try:
|
||||||
|
baseresult = json.dumps(cachedata)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("Failed json dumping: %s" % e)
|
||||||
|
|
||||||
|
#returndata = str(baseresult)+str(appendresult)
|
||||||
else:
|
else:
|
||||||
#print("Within execution data check. Execution data: %s", execution_data["results"])
|
#print("Within execution data check. Execution data: %s", execution_data["results"])
|
||||||
if execution_data["results"] != None:
|
if execution_data["results"] != None:
|
||||||
@@ -1302,7 +1454,9 @@ class AppBase:
|
|||||||
|
|
||||||
print("[INFO] After second return")
|
print("[INFO] After second return")
|
||||||
if len(parsersplit) == 1:
|
if len(parsersplit) == 1:
|
||||||
return str(baseresult)+str(appendresult), False
|
returndata = str(baseresult)+str(appendresult)
|
||||||
|
print("RETURNING!")#: %s" % returndata)
|
||||||
|
return returndata, False
|
||||||
|
|
||||||
baseresult = baseresult.replace(" True,", " true,")
|
baseresult = baseresult.replace(" True,", " true,")
|
||||||
baseresult = baseresult.replace(" False", " false,")
|
baseresult = baseresult.replace(" False", " false,")
|
||||||
@@ -1320,9 +1474,17 @@ class AppBase:
|
|||||||
return str(baseresult)+str(appendresult), False
|
return str(baseresult)+str(appendresult), False
|
||||||
|
|
||||||
print("[INFO] After fourth parser return as JSON")
|
print("[INFO] After fourth parser return as JSON")
|
||||||
|
|
||||||
data, is_loop = recurse_json(basejson, parsersplit[1:])
|
data, is_loop = recurse_json(basejson, parsersplit[1:])
|
||||||
parseditem = data
|
parseditem = data
|
||||||
|
|
||||||
|
if isinstance(parseditem, dict) or isinstance(parseditem, list):
|
||||||
|
try:
|
||||||
|
parseditem = json.dumps(parseditem)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("Parseditem issue: %s" % e)
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("DATA: (%s) %s" % (type(data), data))
|
||||||
if is_loop:
|
if is_loop:
|
||||||
print("DATA IS A LOOP - SHOULD WRAP")
|
print("DATA IS A LOOP - SHOULD WRAP")
|
||||||
if parsersplit[-1] == "#":
|
if parsersplit[-1] == "#":
|
||||||
@@ -1333,8 +1495,18 @@ class AppBase:
|
|||||||
print("SET DATA WRAPPER TO %s!" % parsersplit[-1])
|
print("SET DATA WRAPPER TO %s!" % parsersplit[-1])
|
||||||
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
|
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
print("Before last return with %s" % appendresult)
|
print("Before last return with %s" % appendresult)
|
||||||
return str(parseditem)+str(appendresult), is_loop
|
returndata = str(parseditem)+str(appendresult)
|
||||||
|
|
||||||
|
# New in 0.8.97: Don't return items without lists
|
||||||
|
print("RETURNDATA: %s" % returndata)
|
||||||
|
#return returndata, is_loop
|
||||||
|
try:
|
||||||
|
return json.dumps(json.loads(returndata)), is_loop
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("Error in decoder: %s" % e)
|
||||||
|
return returndata, is_loop
|
||||||
|
|
||||||
# Parses parameters sent to it and returns whether it did it successfully with the values found
|
# Parses parameters sent to it and returns whether it did it successfully with the values found
|
||||||
def parse_params(action, fullexecution, parameter):
|
def parse_params(action, fullexecution, parameter):
|
||||||
@@ -1362,12 +1534,24 @@ class AppBase:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Handles for loops etc.
|
# Handles for loops etc.
|
||||||
value, is_loop = get_json_value(fullexecution, to_be_replaced)
|
# FIXME: Should it dump to string here? Doesn't that defeat the purpose?
|
||||||
|
# Trying without string dumping.
|
||||||
|
|
||||||
|
value, is_loop = get_json_value(fullexecution, to_be_replaced)
|
||||||
|
#print("\n\nType of value: %s. Value: %s" % (type(value), value))
|
||||||
|
print("\n\nType of value: %s" % type(value))
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict) or isinstance(value, list):
|
||||||
|
# Changed from JSON dump to str() 28.05.2021
|
||||||
|
# This makes it so the parameters gets lists and dicts straight up
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value))
|
parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value))
|
||||||
|
|
||||||
|
#try:
|
||||||
|
# parameter["value"] = parameter["value"].replace(to_be_replaced, str(value))
|
||||||
|
#except:
|
||||||
|
# parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value))
|
||||||
|
# print("Failed parsing value as string?")
|
||||||
else:
|
else:
|
||||||
print("Unknown type %s" % type(value))
|
print("Unknown type %s" % type(value))
|
||||||
try:
|
try:
|
||||||
@@ -1375,6 +1559,7 @@ class AppBase:
|
|||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
parameter["value"] = parameter["value"].replace(to_be_replaced, value)
|
||||||
|
|
||||||
|
#print("VALUE: %s" % parameter["value"])
|
||||||
|
|
||||||
if parameter["variant"] == "WORKFLOW_VARIABLE":
|
if parameter["variant"] == "WORKFLOW_VARIABLE":
|
||||||
print("Handling workflow variable")
|
print("Handling workflow variable")
|
||||||
@@ -1575,16 +1760,15 @@ class AppBase:
|
|||||||
"matches regex",
|
"matches regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
# FIXME - what should I do here?
|
|
||||||
if not condition["condition"]["value"] in available_checks:
|
if not condition["condition"]["value"] in available_checks:
|
||||||
self.logger.warning("Skipping %s %s %s because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"]))
|
self.logger.warning("Skipping %s %s %s because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"]))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
#print(destinationvalue)
|
#print(destinationvalue)
|
||||||
# NEGATE
|
# NEGATE
|
||||||
validation = run_validation(sourcevalue, condition["condition"]["value"], destinationvalue)
|
|
||||||
|
|
||||||
# Configuration = negated because of WorkflowAppActionParam..
|
# Configuration = negated because of WorkflowAppActionParam..
|
||||||
|
validation = run_validation(sourcevalue, condition["condition"]["value"], destinationvalue)
|
||||||
try:
|
try:
|
||||||
if condition["condition"]["configuration"]:
|
if condition["condition"]["configuration"]:
|
||||||
validation = not validation
|
validation = not validation
|
||||||
@@ -1602,9 +1786,18 @@ class AppBase:
|
|||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# THE START IS ACTUALLY RIGHT HERE :O
|
# THE START IS ACTUALLY RIGHT HERE :O
|
||||||
# Checks whether conditions are met, otherwise set
|
# Checks whether conditions are met, otherwise set
|
||||||
branchcheck, tmpresult = check_branch_conditions(action, fullexecution)
|
branchcheck, tmpresult = check_branch_conditions(action, fullexecution)
|
||||||
|
if isinstance(tmpresult, object) or isinstance(tmpresult, list):
|
||||||
|
print("Fixing branch return as object -> string")
|
||||||
|
try:
|
||||||
|
#tmpresult = tmpresult.replace("'", "\"")
|
||||||
|
tmpresult = json.dumps(tmpresult)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print(f"[WARNING] Failed condition parsing {tmpresult} to string")
|
||||||
|
|
||||||
if not branchcheck:
|
if not branchcheck:
|
||||||
self.logger.info("Failed one or more branch conditions.")
|
self.logger.info("Failed one or more branch conditions.")
|
||||||
action_result["result"] = tmpresult
|
action_result["result"] = tmpresult
|
||||||
@@ -1691,23 +1884,21 @@ class AppBase:
|
|||||||
if values != None:
|
if values != None:
|
||||||
added = 0
|
added = 0
|
||||||
for val in values:
|
for val in values:
|
||||||
#print(f"VAL: {val}")
|
replace_value = val["value"]
|
||||||
#parameter["value"].replace(val["key"], val["value"], -1)
|
replace_key = val["key"]
|
||||||
#print(f'PARAM1: {action["parameters"][counter]["value"]}')
|
if (val["value"].startswith("{") and val["value"].endswith("}")) or (val["value"].startswith("[") and val["value"].endswith("]")):
|
||||||
action["parameters"][counter]["value"] = action["parameters"][counter]["value"].replace(val["key"], val["value"], 1)
|
print(f"""Trying to parse as JSON: {val["value"]}""")
|
||||||
#action["parameters"][counter]["value"].replace(r"${url}", r"$Find_URLs.valid.#.data", 1)
|
try:
|
||||||
#print(f'PARAM2: {action["parameters"][counter]["value"]}')
|
value_replace = json.loads(val["value"])
|
||||||
#newparams.append({
|
# If it gets here, remove the "" infront and behind the key as well since this is preventing the JSON from being loaded
|
||||||
# "name": val["key"],
|
replace_key = f"\"{replace_key}\""
|
||||||
# "value": val["value"],
|
except json.decoder.JSONDecodeError as e:
|
||||||
# "variant": "STATIC_VALUE",
|
print("Failed JSON replacement for OpenAPI %s", val["key"])
|
||||||
# "id": "body_replacement",
|
elif val["value"].lower() == "true" or val["value"].lower() == "false":
|
||||||
# "schema": {
|
replace_key = f"\"{replace_key}\""
|
||||||
# "type": "string",
|
|
||||||
# },
|
action["parameters"][counter]["value"] = action["parameters"][counter]["value"].replace(replace_key, replace_value, 1)
|
||||||
#})
|
|
||||||
|
|
||||||
#print(f'[INFO] Added param {val["key"]} for body with value {val["value"]} (using OpenAPI)')
|
|
||||||
print(f'[INFO] Added param {val["key"]} for body (using OpenAPI)')
|
print(f'[INFO] Added param {val["key"]} for body (using OpenAPI)')
|
||||||
added += 1
|
added += 1
|
||||||
|
|
||||||
@@ -1718,6 +1909,34 @@ class AppBase:
|
|||||||
print("KeyError body OpenAPI: %s" % e)
|
print("KeyError body OpenAPI: %s" % e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
newvalue = json.loads(action["parameters"][counter]["value"])
|
||||||
|
deletekeys = []
|
||||||
|
for key, value in newvalue.items():
|
||||||
|
if isinstance(value, str) and len(value) == 0:
|
||||||
|
deletekeys.append(key)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for deletekey in deletekeys:
|
||||||
|
del newvalue[deletekey]
|
||||||
|
|
||||||
|
action["parameters"][counter]["value"] = json.dumps(newvalue)
|
||||||
|
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("Failed JSON replacement for OpenAPI keys (2) {e}")
|
||||||
|
|
||||||
|
#if "\n" in action["parameters"][counter]["value"]:
|
||||||
|
# print("MODIFYING BODY!!")
|
||||||
|
# newbody = ""
|
||||||
|
# for line in action["parameters"][counter]["value"].split("\n"):
|
||||||
|
# if ": \"\"" in line:
|
||||||
|
# print("Skipping line %s" % line)
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# newbody += line
|
||||||
|
|
||||||
|
# print("New body: %s" % newbody)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
#print(action["parameters"])
|
#print(action["parameters"])
|
||||||
@@ -1802,7 +2021,6 @@ class AppBase:
|
|||||||
|
|
||||||
print("PRE new_replacement")
|
print("PRE new_replacement")
|
||||||
|
|
||||||
# FIXME: Only do this IF they want to loop
|
|
||||||
new_replacement = []
|
new_replacement = []
|
||||||
for i in range(len(json_replacement)):
|
for i in range(len(json_replacement)):
|
||||||
if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list):
|
if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list):
|
||||||
@@ -1858,6 +2076,13 @@ class AppBase:
|
|||||||
params[parameter["name"]] = resultarray
|
params[parameter["name"]] = resultarray
|
||||||
multi_parameters[parameter["name"]] = resultarray
|
multi_parameters[parameter["name"]] = resultarray
|
||||||
|
|
||||||
|
#if len(resultarray) == 0:
|
||||||
|
# print("[WARNING] Returning empty array because the array length to be looped is 0 (1)")
|
||||||
|
# action_result["status"] = "SUCCESS"
|
||||||
|
# action_result["result"] = "[]"
|
||||||
|
# self.send_result(action_result, headers, stream_path)
|
||||||
|
# return
|
||||||
|
|
||||||
multi_execution_lists.append(new_replacement)
|
multi_execution_lists.append(new_replacement)
|
||||||
#print("MULTI finished: %s" % json_replacement)
|
#print("MULTI finished: %s" % json_replacement)
|
||||||
else:
|
else:
|
||||||
@@ -1942,6 +2167,13 @@ class AppBase:
|
|||||||
|
|
||||||
# With this parameter ready, add it to... a greater list of parameters. Rofl
|
# With this parameter ready, add it to... a greater list of parameters. Rofl
|
||||||
print("LENGTH OF ARR: %d" % len(resultarray))
|
print("LENGTH OF ARR: %d" % len(resultarray))
|
||||||
|
if len(resultarray) == 0:
|
||||||
|
print("[WARNING] Returning empty array because the array length to be looped is 0 (0)")
|
||||||
|
action_result["status"] = "SUCCESS"
|
||||||
|
action_result["result"] = "[]"
|
||||||
|
self.send_result(action_result, headers, stream_path)
|
||||||
|
return
|
||||||
|
|
||||||
#print("RESULTARRAY: %s" % resultarray)
|
#print("RESULTARRAY: %s" % resultarray)
|
||||||
if resultarray not in multi_execution_lists:
|
if resultarray not in multi_execution_lists:
|
||||||
multi_execution_lists.append(resultarray)
|
multi_execution_lists.append(resultarray)
|
||||||
@@ -2086,8 +2318,33 @@ class AppBase:
|
|||||||
return
|
return
|
||||||
|
|
||||||
print("[INFO] Running normal execution\n")
|
print("[INFO] Running normal execution\n")
|
||||||
newres = await func(**params)
|
|
||||||
print("\n[INFO] Returned from execution!")#, newres)
|
#newres = await func(**params)
|
||||||
|
#print("PARAMS: %s" % params)
|
||||||
|
#newres = ""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
newres = await func(**params)
|
||||||
|
break
|
||||||
|
except TypeError as e:
|
||||||
|
newres = ""
|
||||||
|
errorstring = "%s" % e
|
||||||
|
if "got an unexpected keyword argument" in errorstring:
|
||||||
|
fieldsplit = errorstring.split("'")
|
||||||
|
if len(fieldsplit) > 1:
|
||||||
|
field = fieldsplit[1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
del params[field]
|
||||||
|
print("Removed field invalid field %s" % field)
|
||||||
|
except KeyError:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
#break
|
||||||
|
|
||||||
|
print("\n[INFO] Returned from execution with types %s" % type(newres))
|
||||||
|
#print("\n[INFO] Returned from execution with %s of types %s" % (newres, type(newres)))#, newres)
|
||||||
if isinstance(newres, tuple):
|
if isinstance(newres, tuple):
|
||||||
print("[INFO] Handling return as tuple")
|
print("[INFO] Handling return as tuple")
|
||||||
# Handles files.
|
# Handles files.
|
||||||
@@ -2328,7 +2585,6 @@ class AppBase:
|
|||||||
# self.action = cls
|
# self.action = cls
|
||||||
|
|
||||||
app = cls(redis=None, logger=logger, console_logger=logger)
|
app = cls(redis=None, logger=logger, console_logger=logger)
|
||||||
|
|
||||||
if isinstance(action, str):
|
if isinstance(action, str):
|
||||||
print("Normal execution. Action is a string.")
|
print("Normal execution. Action is a string.")
|
||||||
elif isinstance(action, object):
|
elif isinstance(action, object):
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
|
### DEFAULT
|
||||||
NAME=shuffle-app_sdk
|
NAME=shuffle-app_sdk
|
||||||
VERSION=0.8.64
|
VERSION=0.8.104
|
||||||
|
|
||||||
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
||||||
docker build . -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|
||||||
#docker push frikky/$NAME:$VERSION
|
#docker push frikky/$NAME:$VERSION
|
||||||
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||||
@@ -12,3 +15,21 @@ docker build . -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.
|
|||||||
|
|
||||||
docker push frikky/shuffle:app_sdk
|
docker push frikky/shuffle:app_sdk
|
||||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
docker push ghcr.io/frikky/$NAME:nightly
|
||||||
|
|
||||||
|
#### BLACKARCH ###
|
||||||
|
NAME=shuffle-app_sdk_kali
|
||||||
|
docker build . -f Dockerfile_kali -t frikky/shuffle:app_sdk_kali -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|
||||||
|
docker push frikky/shuffle:app_sdk_kali
|
||||||
|
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
docker push ghcr.io/frikky/$NAME:nightly
|
||||||
|
|
||||||
|
### BLACKARCH ###
|
||||||
|
NAME=shuffle-app_sdk_blackarch
|
||||||
|
docker build . -f Dockerfile_blackarch -t frikky/shuffle:app_sdk_blackarch -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
|
||||||
|
docker push frikky/shuffle:app_sdk_blackarch
|
||||||
|
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
docker push ghcr.io/frikky/$NAME:nightly
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
urllib3=1.25.9
|
urllib3==1.25.9
|
||||||
requests=2.25.1
|
requests==2.25.1
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
FROM peterclemenko/blackarch as base
|
|
||||||
|
|
||||||
FROM base as builder
|
|
||||||
|
|
||||||
RUN /bin/pacman -Syu --noconfirm
|
|
||||||
|
|
||||||
RUN /bin/pacman -Sy --noconfirm base-devel libffi musl openssl python python-pip -y
|
|
||||||
|
|
||||||
RUN mkdir /install
|
|
||||||
WORKDIR /install
|
|
||||||
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
RUN pip install --prefix="/install" -r /requirements.txt
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
|
||||||
COPY __init__.py /app/walkoff_app_sdk/__init__.py
|
|
||||||
COPY app_base.py /app/walkoff_app_sdk/app_base.py
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Frikkylikeme
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# app_sdk.py
|
|
||||||
This is the SDK used for apps to behave like they should.
|
|
||||||
To change it in the backend, upload it to Buckets/shuffler.appspot.com/generated_apps/baseline.
|
|
||||||
|
|
||||||
# static_baseline.py
|
|
||||||
It's used for python code generation and should be under MIT. Has to be located here because it's used by the backend.
|
|
||||||
|
|
||||||
## If you want to update apps.. PS: downloads from docker hub do overrides.. :)
|
|
||||||
1. Write your code & check if runtime works
|
|
||||||
2. Build app_base image
|
|
||||||
3. docker rm $(docker ps -aq) # Remove all stopped containers
|
|
||||||
4. Delete the specific app's Docker image (docker rmi frikky/shuffle:...)
|
|
||||||
5. Rebuild the Docker image (click load in GUI?)
|
|
||||||
|
|
||||||
# LICENSE
|
|
||||||
Everything in here is MIT, not AGPLv3 as indicated by the license.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
NAME=app_sdk_blackarch
|
|
||||||
VERSION=0.7.3
|
|
||||||
|
|
||||||
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
|
||||||
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
|
|
||||||
#docker push frikky/$NAME:$VERSION
|
|
||||||
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
|
||||||
#docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
|
|
||||||
docker push frikky/shuffle:$NAME
|
|
||||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
requests
|
|
||||||
urllib3
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Goal here:
|
|
||||||
# * Make an app from WALKOFF able to run without app_base.py from WALKOFF
|
|
||||||
# # How:
|
|
||||||
# * Make it rely 100% on INPUT throug HTTP invocations instead of redis READS
|
|
||||||
# # But really, how?
|
|
||||||
# * Make a WORKER that reads the queue, and reuses a function
|
|
||||||
|
|
||||||
# Here to get it global
|
|
||||||
apikey = ""
|
|
||||||
try:
|
|
||||||
apikey = os.environ["FUNCTION_APIKEY"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Authorize the execution
|
|
||||||
def authorization(request):
|
|
||||||
# This is basically my issue, but it enforces the use of an internal API key for execution
|
|
||||||
try:
|
|
||||||
apikey = os.environ["FUNCTION_APIKEY"]
|
|
||||||
except KeyError:
|
|
||||||
return f"Internal server error", 500
|
|
||||||
|
|
||||||
|
|
||||||
# Check API key from ENV authentication
|
|
||||||
authentication = request.headers.get("Authorization")
|
|
||||||
if authentication == None: return f"Unauthorized", 401
|
|
||||||
|
|
||||||
apikey_split = authentication.split(" ")
|
|
||||||
if apikey_split[0] != "Bearer" or len(apikey_split) != 2:
|
|
||||||
return f"Apikey error", 401
|
|
||||||
|
|
||||||
if apikey != apikey_split[1]:
|
|
||||||
return f"Unauthorized", 401
|
|
||||||
|
|
||||||
return run(request)
|
|
||||||
|
|
||||||
class AppBase:
|
|
||||||
""" The base class for Python-based Walkoff applications, handles Redis and logging configurations. """
|
|
||||||
__version__ = None
|
|
||||||
app_name = None
|
|
||||||
|
|
||||||
def __init__(self, redis=None, logger=None, console_logger=None):#, docker_client=None):
|
|
||||||
self.logger = logger if logger is not None else logging.getLogger("AppBaseLogger")
|
|
||||||
self.redis=redis
|
|
||||||
self.console_logger=console_logger
|
|
||||||
self.current_execution_id = None
|
|
||||||
self.url = "https://shuffler.io"
|
|
||||||
self.apikey = apikey
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def run(cls, action):
|
|
||||||
""" Connect to Redis and HTTP session, await actions """
|
|
||||||
logging.basicConfig(format="{asctime} - {name} - {levelname}:{message}", style='{')
|
|
||||||
logger = logging.getLogger(f"{cls.__name__}")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
app = cls(redis=None, logger=logger, console_logger=logger)
|
|
||||||
|
|
||||||
# Authorization for the app/function to control the workflow
|
|
||||||
# Function will crash if its wrong, which it probably should.
|
|
||||||
|
|
||||||
await app.execute_action(action)
|
|
||||||
|
|
||||||
async def execute_action(self, action):
|
|
||||||
# FIXME - add request for the function STARTING here. Use "results stream" or something
|
|
||||||
# PAUSED, AWAITING_DATA, PENDING, COMPLETED, ABORTED, EXECUTING, SUCCESS, FAILURE
|
|
||||||
|
|
||||||
self.authorization = action["authorization"]
|
|
||||||
self.execution_id = action["execution_id"]
|
|
||||||
self.current_execution_id = action["execution_id"]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
FROM kalilinux/kali-rolling as base
|
|
||||||
|
|
||||||
FROM base as builder
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get dist-upgrade -y
|
|
||||||
RUN apt install build-essential libffi-dev musl-dev openssl python3 python3-pip -y
|
|
||||||
|
|
||||||
RUN mkdir /install
|
|
||||||
WORKDIR /install
|
|
||||||
|
|
||||||
COPY requirements.txt /requirements.txt
|
|
||||||
RUN pip install --prefix="/install" -r /requirements.txt
|
|
||||||
|
|
||||||
FROM base
|
|
||||||
|
|
||||||
COPY --from=builder /install /usr/local
|
|
||||||
COPY __init__.py /app/walkoff_app_sdk/__init__.py
|
|
||||||
COPY app_base.py /app/walkoff_app_sdk/app_base.py
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Frikkylikeme
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# app_sdk.py
|
|
||||||
This is the SDK used for apps to behave like they should.
|
|
||||||
To change it in the backend, upload it to Buckets/shuffler.appspot.com/generated_apps/baseline.
|
|
||||||
|
|
||||||
# static_baseline.py
|
|
||||||
It's used for python code generation and should be under MIT. Has to be located here because it's used by the backend.
|
|
||||||
|
|
||||||
## If you want to update apps.. PS: downloads from docker hub do overrides.. :)
|
|
||||||
1. Write your code & check if runtime works
|
|
||||||
2. Build app_base image
|
|
||||||
3. docker rm $(docker ps -aq) # Remove all stopped containers
|
|
||||||
4. Delete the specific app's Docker image (docker rmi frikky/shuffle:...)
|
|
||||||
5. Rebuild the Docker image (click load in GUI?)
|
|
||||||
|
|
||||||
# LICENSE
|
|
||||||
Everything in here is MIT, not AGPLv3 as indicated by the license.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
NAME=app_sdk_kali
|
|
||||||
VERSION=0.7.3
|
|
||||||
|
|
||||||
docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force
|
|
||||||
docker build . -t frikky/shuffle:$NAME -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
|
|
||||||
#docker push frikky/$NAME:$VERSION
|
|
||||||
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
|
||||||
#docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
|
|
||||||
docker push frikky/shuffle:$NAME
|
|
||||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
requests
|
|
||||||
urllib3
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Goal here:
|
|
||||||
# * Make an app from WALKOFF able to run without app_base.py from WALKOFF
|
|
||||||
# # How:
|
|
||||||
# * Make it rely 100% on INPUT throug HTTP invocations instead of redis READS
|
|
||||||
# # But really, how?
|
|
||||||
# * Make a WORKER that reads the queue, and reuses a function
|
|
||||||
|
|
||||||
# Here to get it global
|
|
||||||
apikey = ""
|
|
||||||
try:
|
|
||||||
apikey = os.environ["FUNCTION_APIKEY"]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Authorize the execution
|
|
||||||
def authorization(request):
|
|
||||||
# This is basically my issue, but it enforces the use of an internal API key for execution
|
|
||||||
try:
|
|
||||||
apikey = os.environ["FUNCTION_APIKEY"]
|
|
||||||
except KeyError:
|
|
||||||
return f"Internal server error", 500
|
|
||||||
|
|
||||||
|
|
||||||
# Check API key from ENV authentication
|
|
||||||
authentication = request.headers.get("Authorization")
|
|
||||||
if authentication == None: return f"Unauthorized", 401
|
|
||||||
|
|
||||||
apikey_split = authentication.split(" ")
|
|
||||||
if apikey_split[0] != "Bearer" or len(apikey_split) != 2:
|
|
||||||
return f"Apikey error", 401
|
|
||||||
|
|
||||||
if apikey != apikey_split[1]:
|
|
||||||
return f"Unauthorized", 401
|
|
||||||
|
|
||||||
return run(request)
|
|
||||||
|
|
||||||
class AppBase:
|
|
||||||
""" The base class for Python-based Walkoff applications, handles Redis and logging configurations. """
|
|
||||||
__version__ = None
|
|
||||||
app_name = None
|
|
||||||
|
|
||||||
def __init__(self, redis=None, logger=None, console_logger=None):#, docker_client=None):
|
|
||||||
self.logger = logger if logger is not None else logging.getLogger("AppBaseLogger")
|
|
||||||
self.redis=redis
|
|
||||||
self.console_logger=console_logger
|
|
||||||
self.current_execution_id = None
|
|
||||||
self.url = "https://shuffler.io"
|
|
||||||
self.apikey = apikey
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
async def run(cls, action):
|
|
||||||
""" Connect to Redis and HTTP session, await actions """
|
|
||||||
logging.basicConfig(format="{asctime} - {name} - {levelname}:{message}", style='{')
|
|
||||||
logger = logging.getLogger(f"{cls.__name__}")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
app = cls(redis=None, logger=logger, console_logger=logger)
|
|
||||||
|
|
||||||
# Authorization for the app/function to control the workflow
|
|
||||||
# Function will crash if its wrong, which it probably should.
|
|
||||||
|
|
||||||
await app.execute_action(action)
|
|
||||||
|
|
||||||
async def execute_action(self, action):
|
|
||||||
# FIXME - add request for the function STARTING here. Use "results stream" or something
|
|
||||||
# PAUSED, AWAITING_DATA, PENDING, COMPLETED, ABORTED, EXECUTING, SUCCESS, FAILURE
|
|
||||||
|
|
||||||
self.authorization = action["authorization"]
|
|
||||||
self.execution_id = action["execution_id"]
|
|
||||||
self.current_execution_id = action["execution_id"]
|
|
||||||
+153
-251
@@ -5,20 +5,24 @@ import (
|
|||||||
"github.com/frikky/shuffle-shared"
|
"github.com/frikky/shuffle-shared"
|
||||||
|
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
|
//"bufio"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
//"strconv"
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
//"github.com/docker/docker"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
//"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
newdockerclient "github.com/fsouza/go-dockerclient"
|
||||||
"github.com/go-git/go-billy/v5"
|
"github.com/go-git/go-billy/v5"
|
||||||
|
|
||||||
network "github.com/docker/docker/api/types/network"
|
//network "github.com/docker/docker/api/types/network"
|
||||||
natting "github.com/docker/go-connections/nat"
|
//natting "github.com/docker/go-connections/nat"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@@ -214,11 +218,28 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin
|
|||||||
|
|
||||||
// Dockerfile is inside the TAR itself. Not local context
|
// Dockerfile is inside the TAR itself. Not local context
|
||||||
// docker build --build-arg http_proxy=http://my.proxy.url
|
// docker build --build-arg http_proxy=http://my.proxy.url
|
||||||
|
// Attempt at setting name according to #359: https://github.com/frikky/Shuffle/issues/359
|
||||||
|
labels := map[string]string{}
|
||||||
|
target := ""
|
||||||
|
if len(tags) > 0 {
|
||||||
|
if strings.Contains(tags[0], ":") {
|
||||||
|
version := strings.Split(tags[0], ":")
|
||||||
|
if len(version) == 2 {
|
||||||
|
target = fmt.Sprintf("shuffle-build-%s", version[1])
|
||||||
|
tags = append(tags, target)
|
||||||
|
labels["name"] = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = labels
|
||||||
buildOptions := types.ImageBuildOptions{
|
buildOptions := types.ImageBuildOptions{
|
||||||
Remove: true,
|
Remove: true,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
BuildArgs: map[string]*string{},
|
BuildArgs: map[string]*string{},
|
||||||
|
Labels: labels,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkMode: "host",
|
// NetworkMode: "host",
|
||||||
|
|
||||||
httpProxy := os.Getenv("HTTP_PROXY")
|
httpProxy := os.Getenv("HTTP_PROXY")
|
||||||
@@ -231,13 +252,14 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build the actual image
|
// Build the actual image
|
||||||
log.Printf("[INFO] Building %s. This may take up to a few minutes.", dockerfileFolder)
|
log.Printf(`[INFO] Building %s with proxy "%s". Tags: "%s". This may take up to a few minutes.`, dockerfileFolder, httpsProxy, strings.Join(tags, ","))
|
||||||
imageBuildResponse, err := client.ImageBuild(
|
imageBuildResponse, err := client.ImageBuild(
|
||||||
ctx,
|
ctx,
|
||||||
dockerFileTarReader,
|
dockerFileTarReader,
|
||||||
buildOptions,
|
buildOptions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//log.Printf("RESPONSE: %#v", imageBuildResponse)
|
||||||
//log.Printf("Response: %#v", imageBuildResponse.Body)
|
//log.Printf("Response: %#v", imageBuildResponse.Body)
|
||||||
//log.Printf("IMAGERESPONSE: %#v", imageBuildResponse.Body)
|
//log.Printf("IMAGERESPONSE: %#v", imageBuildResponse.Body)
|
||||||
|
|
||||||
@@ -391,89 +413,6 @@ func stopWebhook(image string, identifier string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - remember to set DOCKER_API_VERSION
|
|
||||||
// FIXME - remove github.com/docker/docker/vendor
|
|
||||||
// FIXME - Library dependencies for NAT is fucked..
|
|
||||||
// https://docs.docker.com/develop/sdk/examples/
|
|
||||||
func deployWebhook(image string, identifier string, path string, port string, callbackurl string, apikey string) error {
|
|
||||||
cli, err := client.NewEnvClient()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Unable to create docker client")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newport, err := natting.NewPort("tcp", port)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Unable to create docker port")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - logging?
|
|
||||||
|
|
||||||
hostConfig := &container.HostConfig{
|
|
||||||
PortBindings: natting.PortMap{
|
|
||||||
newport: []natting.PortBinding{
|
|
||||||
{
|
|
||||||
HostIP: "0.0.0.0",
|
|
||||||
HostPort: port,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RestartPolicy: container.RestartPolicy{
|
|
||||||
Name: "always",
|
|
||||||
},
|
|
||||||
LogConfig: container.LogConfig{
|
|
||||||
Type: "json-file",
|
|
||||||
Config: map[string]string{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
//networkConfig := &network.NetworkSettings{}
|
|
||||||
networkConfig := &network.NetworkingConfig{
|
|
||||||
EndpointsConfig: map[string]*network.EndpointSettings{},
|
|
||||||
}
|
|
||||||
|
|
||||||
test := &network.EndpointSettings{
|
|
||||||
Gateway: "helo",
|
|
||||||
}
|
|
||||||
|
|
||||||
networkConfig.EndpointsConfig["bridge"] = test
|
|
||||||
|
|
||||||
exposedPorts := map[natting.Port]struct{}{
|
|
||||||
newport: struct{}{},
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &container.Config{
|
|
||||||
Image: image,
|
|
||||||
Env: []string{
|
|
||||||
fmt.Sprintf("URIPATH=%s", path),
|
|
||||||
fmt.Sprintf("HOOKPORT=%s", port),
|
|
||||||
fmt.Sprintf("CALLBACKURL=%s", callbackurl),
|
|
||||||
fmt.Sprintf("APIKEY=%s", apikey),
|
|
||||||
fmt.Sprintf("HOOKID=%s", identifier),
|
|
||||||
},
|
|
||||||
ExposedPorts: exposedPorts,
|
|
||||||
Hostname: fmt.Sprintf("%s-%s", image, identifier),
|
|
||||||
}
|
|
||||||
|
|
||||||
cont, err := cli.ContainerCreate(
|
|
||||||
context.Background(),
|
|
||||||
config,
|
|
||||||
hostConfig,
|
|
||||||
networkConfig,
|
|
||||||
fmt.Sprintf("%s-%s", image, identifier),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
|
|
||||||
log.Printf("Container %s is created", cont.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts a new webhook
|
// Starts a new webhook
|
||||||
func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
|
func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
@@ -501,7 +440,7 @@ func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
hook, err := getHook(ctx, fileId)
|
hook, err := shuffle.GetHook(ctx, fileId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed getting hook %s (stop docker): %s", fileId, err)
|
log.Printf("Failed getting hook %s (stop docker): %s", fileId, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -521,8 +460,8 @@ func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
hook.Status = "stopped"
|
hook.Status = "stopped"
|
||||||
hook.Running = false
|
hook.Running = false
|
||||||
hook.Actions = []HookAction{}
|
hook.Actions = []shuffle.HookAction{}
|
||||||
err = setHook(ctx, *hook)
|
err = shuffle.SetHook(ctx, *hook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed setting hook: %s", err)
|
log.Printf("Failed setting hook: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -585,7 +524,7 @@ func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := DeleteKey(ctx, "hooks", fileId)
|
err := shuffle.DeleteKey(ctx, "hooks", fileId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false, "message": "Can't delete"}`))
|
resp.Write([]byte(`{"success": false, "message": "Can't delete"}`))
|
||||||
@@ -606,121 +545,6 @@ func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`))
|
resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts a new webhook
|
|
||||||
func handleStartHookDocker(resp http.ResponseWriter, request *http.Request) {
|
|
||||||
cors := handleCors(resp, request)
|
|
||||||
if cors {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
location := strings.Split(request.URL.String(), "/")
|
|
||||||
|
|
||||||
var fileId string
|
|
||||||
if location[1] == "api" {
|
|
||||||
if len(location) <= 4 {
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileId = location[4]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fileId) != 32 {
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
hook, err := getHook(ctx, fileId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed getting hook %s (start docker): %s", fileId, err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hook.Info.Url) == 0 {
|
|
||||||
log.Printf("Hook url can't be empty.")
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Status: %s", hook.Status)
|
|
||||||
log.Printf("Running: %t", hook.Running)
|
|
||||||
if hook.Running || hook.Status == "Running" {
|
|
||||||
message := fmt.Sprintf("Error: %s is already running", hook.Id)
|
|
||||||
log.Println(message)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - verify?
|
|
||||||
// FIXME - static port? Generate from available range.
|
|
||||||
image := "webhook"
|
|
||||||
filepath := "/webhook"
|
|
||||||
baseUrl := "http://localhost"
|
|
||||||
callbackUrl := "http://localhost:8001"
|
|
||||||
|
|
||||||
// This is here to force stop and remove the old webhook
|
|
||||||
err = stopWebhook(image, fileId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamic ish ports
|
|
||||||
var startPort int64 = 5001
|
|
||||||
var endPort int64 = 5010
|
|
||||||
port := findAvailablePorts(startPort, endPort)
|
|
||||||
if len(port) == 0 {
|
|
||||||
message := fmt.Sprintf("Not ports available in the range %d-%d", startPort, endPort)
|
|
||||||
log.Println(message)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message)))
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
hook.Status = "running"
|
|
||||||
hook.Running = true
|
|
||||||
|
|
||||||
// Set this for more than just hooks?
|
|
||||||
if hook.Type == "webhook" {
|
|
||||||
hook.Info.Url = fmt.Sprintf("%s:%s%s", baseUrl, port, filepath)
|
|
||||||
}
|
|
||||||
err = setHook(ctx, *hook)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed setting hook: %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cloud run? Let's make a generic webhook that can be deployed easily
|
|
||||||
log.Printf("Should run a webhook with the following: \nUrl: %s\nId: %s\n", hook.Info.Url, hook.Id)
|
|
||||||
|
|
||||||
// FIXME - set port based on what the user specified / what was generated
|
|
||||||
// FIXME - add nonstatic APIKEY
|
|
||||||
apiKey := "ASD"
|
|
||||||
|
|
||||||
err = deployWebhook(image, fileId, filepath, port, callbackUrl, apiKey)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed starting container %s-%s: %s", image, fileId, err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - get some real data?
|
|
||||||
log.Printf("[INFO] Successfully started %s-%s on port %s with filepath %s", image, fileId, port, filepath)
|
|
||||||
resp.WriteHeader(200)
|
|
||||||
resp.Write([]byte(`{"success": true, "message": "Started webhook"}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if an image exists
|
// Checks if an image exists
|
||||||
func imageCheckBuilder(images []string) error {
|
func imageCheckBuilder(images []string) error {
|
||||||
//log.Printf("[FIXME] ImageNames to check: %#v", images)
|
//log.Printf("[FIXME] ImageNames to check: %#v", images)
|
||||||
@@ -767,7 +591,7 @@ func imageCheckBuilder(images []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hookTest() {
|
func hookTest() {
|
||||||
var hook Hook
|
var hook shuffle.Hook
|
||||||
err := json.Unmarshal([]byte(webhook), &hook)
|
err := json.Unmarshal([]byte(webhook), &hook)
|
||||||
log.Println(webhook)
|
log.Println(webhook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -776,12 +600,12 @@ func hookTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err = setHook(ctx, hook)
|
err = shuffle.SetHook(ctx, hook)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed setting hook: %s", err)
|
log.Printf("Failed setting hook: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
returnHook, err := getHook(ctx, hook.Id)
|
returnHook, err := shuffle.GetHook(ctx, hook.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed getting hook %s (test): %s", hook.Id, err)
|
log.Printf("Failed getting hook %s (test): %s", hook.Id, err)
|
||||||
}
|
}
|
||||||
@@ -799,13 +623,13 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Just here to verify that the user is logged in
|
// Just here to verify that the user is logged in
|
||||||
_, err := shuffle.HandleApiAuthentication(resp, request)
|
//_, err := shuffle.HandleApiAuthentication(resp, request)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
log.Printf("Api authentication failed in validate swagger: %s", err)
|
// log.Printf("[WARNING] Api authentication failed in DOWNLOAD IMAGE: %s", err)
|
||||||
resp.WriteHeader(401)
|
// resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false}`))
|
// resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
// return
|
||||||
}
|
//}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(request.Body)
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -818,16 +642,6 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
|||||||
Name string `datastore:"name" json:"name" yaml:"name"`
|
Name string `datastore:"name" json:"name" yaml:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//body = []byte(`swagger: "2.0"`)
|
|
||||||
//body = []byte(`swagger: '1.0'`)
|
|
||||||
//newbody := string(body)
|
|
||||||
//newbody = strings.TrimSpace(newbody)
|
|
||||||
//body = []byte(newbody)
|
|
||||||
//log.Println(string(body))
|
|
||||||
//tmpbody, err := yaml.YAMLToJSON(body)
|
|
||||||
//log.Println(err)
|
|
||||||
//log.Println(string(tmpbody))
|
|
||||||
|
|
||||||
// This has to be done in a weird way because Datastore doesn't
|
// This has to be done in a weird way because Datastore doesn't
|
||||||
// support map[string]interface and similar (openapi3.Swagger)
|
// support map[string]interface and similar (openapi3.Swagger)
|
||||||
var version requestCheck
|
var version requestCheck
|
||||||
@@ -839,16 +653,10 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Image to load: %s", version.Name)
|
log.Printf("[DEBUG] Image to load: %s", version.Name)
|
||||||
//cli, err := client.NewEnvClient()
|
|
||||||
//if err != nil {
|
|
||||||
// log.Println("Unable to create docker client")
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
|
|
||||||
dockercli, err := client.NewEnvClient()
|
dockercli, err := client.NewEnvClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unable to create docker client: %s", err)
|
log.Printf("[WARNING] Unable to create docker client: %s", err)
|
||||||
resp.WriteHeader(422)
|
resp.WriteHeader(422)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed JSON marshalling: %s"}`, err)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed JSON marshalling: %s"}`, err)))
|
||||||
return
|
return
|
||||||
@@ -861,37 +669,131 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
img := types.ImageSummary{}
|
img := types.ImageSummary{}
|
||||||
tagFound := ""
|
tagFound := ""
|
||||||
|
|
||||||
|
img2 := types.ImageSummary{}
|
||||||
|
tagFound2 := ""
|
||||||
|
|
||||||
|
alternativeNameSplit := strings.Split(version.Name, "/")
|
||||||
|
alternativeName := version.Name
|
||||||
|
if len(alternativeNameSplit) == 3 {
|
||||||
|
alternativeName = strings.Join(alternativeNameSplit[1:3], "/")
|
||||||
|
}
|
||||||
|
|
||||||
for _, image := range images {
|
for _, image := range images {
|
||||||
for _, tag := range image.RepoTags {
|
for _, tag := range image.RepoTags {
|
||||||
log.Printf("[INFO] Docker Image: %s", tag)
|
|
||||||
|
|
||||||
if strings.ToLower(tag) == strings.ToLower(version.Name) {
|
if strings.ToLower(tag) == strings.ToLower(version.Name) {
|
||||||
img = image
|
img = image
|
||||||
tagFound = tag
|
tagFound = tag
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(tag) == strings.ToLower(alternativeName) {
|
||||||
|
img2 = image
|
||||||
|
tagFound2 = tag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// REBUILDS THE APP
|
||||||
if len(img.ID) == 0 {
|
if len(img.ID) == 0 {
|
||||||
|
if len(img2.ID) == 0 {
|
||||||
|
workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 0)
|
||||||
|
log.Printf("[INFO] Getting workflowapps for a rebuild. Got %d with err %#v", len(workflowapps), err)
|
||||||
|
if err == nil {
|
||||||
|
imageName := ""
|
||||||
|
imageVersion := ""
|
||||||
|
newNameSplit := strings.Split(version.Name, ":")
|
||||||
|
if len(newNameSplit) == 2 {
|
||||||
|
log.Printf("[DEBUG] Found name %#v", newNameSplit)
|
||||||
|
|
||||||
|
findVersionSplit := strings.Split(newNameSplit[1], "_")
|
||||||
|
log.Printf("[DEBUG] Found another split %#v", findVersionSplit)
|
||||||
|
if len(findVersionSplit) == 2 {
|
||||||
|
imageVersion = findVersionSplit[len(findVersionSplit)-1]
|
||||||
|
imageName = findVersionSplit[0]
|
||||||
|
} else if len(findVersionSplit) >= 2 {
|
||||||
|
imageVersion = findVersionSplit[len(findVersionSplit)-1]
|
||||||
|
imageName = strings.Join(findVersionSplit[0:len(findVersionSplit)-1], "_")
|
||||||
|
} else {
|
||||||
|
log.Printf("[DEBUG] Couldn't parse appname & version for %#v", findVersionSplit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(imageName) > 0 && len(imageVersion) > 0 {
|
||||||
|
foundApp := shuffle.WorkflowApp{}
|
||||||
|
imageName = strings.ToLower(imageName)
|
||||||
|
imageVersion = strings.ToLower(imageVersion)
|
||||||
|
log.Printf("[DEBUG] Looking for appname %s with version %s", imageName, imageVersion)
|
||||||
|
|
||||||
|
for _, app := range workflowapps {
|
||||||
|
if strings.ToLower(strings.Replace(app.Name, " ", "_", -1)) == imageName && app.AppVersion == imageVersion {
|
||||||
|
if app.Generated {
|
||||||
|
log.Printf("[DEBUG] Found matching app %s:%s - %s", imageName, imageVersion, app.ID)
|
||||||
|
foundApp = app
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
log.Printf("[WARNING] Trying to rebuild app that isn't generated - not allowed. Looking further.")
|
||||||
|
}
|
||||||
|
|
||||||
|
//break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(foundApp.ID) > 0 {
|
||||||
|
openApiApp, err := shuffle.GetOpenApiDatastore(ctx, foundApp.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ERROR] Failed getting OpenAPI app %s to database: %s", foundApp.ID, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("[DEBUG] Found OpenAPI app for %s as generated - now building!", version.Name)
|
||||||
|
user := shuffle.User{}
|
||||||
|
|
||||||
|
//img = version.Name
|
||||||
|
if len(alternativeName) > 0 {
|
||||||
|
tagFound = alternativeName
|
||||||
|
} else {
|
||||||
|
tagFound = version.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSwaggerApp(resp, []byte(openApiApp.Body), user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[WARNING] Couldn't find an image with registry name %s and %s", version.Name, alternativeName)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't find image %s"}`, version.Name)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tagFound) == 0 && len(tagFound2) > 0 {
|
||||||
|
img = img2
|
||||||
|
tagFound = tagFound2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Printf("[INFO] Img found (%s): %#v", tagFound, img)
|
||||||
|
log.Printf("[INFO] Img found to be downloaded by client: %s", tagFound)
|
||||||
|
|
||||||
|
newClient, err := newdockerclient.NewClientFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[WARNING] Failed setting up docker env: %s", newClient)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't find image %s"}`, version.Name)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't make docker client"}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = tagFound
|
|
||||||
|
|
||||||
/*
|
////https://github.com/fsouza/go-dockerclient/issues/600
|
||||||
log.Printf("IMg: %#v", img)
|
//defer fileReader.Close()
|
||||||
pullOptions := types.ImagePullOptions{}
|
opts := newdockerclient.ExportImageOptions{
|
||||||
log.Printf("[INFO] Pulling image %s", image)
|
Name: tagFound,
|
||||||
reader, err := dockercli.ImagePull(ctx, tag, pullOptions)
|
OutputStream: resp,
|
||||||
if err != nil {
|
}
|
||||||
log.Printf("[ERROR] Failed getting image %s: %s", image, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
io.Copy(os.Stdout, r)
|
if err := newClient.ExportImage(opts); err != nil {
|
||||||
*/
|
log.Printf("[WARNING] FAILED to save image to file: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
resp.WriteHeader(200)
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't export image"}`)))
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": true, "message": "Downloading image %s"}`, version.Name)))
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-6
@@ -2,7 +2,8 @@ module shuffle
|
|||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
//replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
|
replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
|
||||||
|
|
||||||
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -10,15 +11,19 @@ require (
|
|||||||
cloud.google.com/go/datastore v1.4.0
|
cloud.google.com/go/datastore v1.4.0
|
||||||
cloud.google.com/go/pubsub v1.3.1
|
cloud.google.com/go/pubsub v1.3.1
|
||||||
cloud.google.com/go/storage v1.12.0
|
cloud.google.com/go/storage v1.12.0
|
||||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
|
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect
|
||||||
github.com/basgys/goxml2json v1.1.0
|
github.com/basgys/goxml2json v1.1.0
|
||||||
github.com/frikky/kin-openapi v0.38.0
|
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
|
||||||
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
|
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v1.13.1
|
github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/frikky/shuffle-shared v0.0.23
|
github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect
|
||||||
|
github.com/frikky/kin-openapi v0.39.0
|
||||||
|
github.com/frikky/shuffle-shared v0.0.69
|
||||||
|
github.com/fsouza/go-dockerclient v1.7.2
|
||||||
github.com/ghodss/yaml v1.0.0
|
github.com/ghodss/yaml v1.0.0
|
||||||
github.com/go-git/go-billy/v5 v5.0.0
|
github.com/go-git/go-billy/v5 v5.0.0
|
||||||
github.com/go-git/go-git/v5 v5.0.0
|
github.com/go-git/go-git/v5 v5.0.0
|
||||||
@@ -26,9 +31,9 @@ require (
|
|||||||
github.com/gorilla/handlers v1.4.2 // indirect
|
github.com/gorilla/handlers v1.4.2 // indirect
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/h2non/filetype v1.0.12
|
github.com/h2non/filetype v1.0.12
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
|
go4.org v0.0.0-20201209231011-d4a079459e60 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423
|
golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423
|
||||||
google.golang.org/api v0.36.0
|
google.golang.org/api v0.36.0
|
||||||
|
|||||||
@@ -1,604 +0,0 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
|
||||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
|
||||||
cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms=
|
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
|
||||||
cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
|
|
||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
|
||||||
cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY=
|
|
||||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
|
||||||
cloud.google.com/go/bigquery v1.6.0/go.mod h1:hyFDG0qSGdHNz8Q6nDN8rYIkld0q/+5uBZaelxiDLfE=
|
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/datastore v1.4.0 h1:CFDJm15RpYXeEblQ0TMDUrYtqmBmbAWTy536nA8JIc8=
|
|
||||||
cloud.google.com/go/datastore v1.4.0/go.mod h1:d18825/a9bICdAIJy2EkHs9joU4RlIZ1t6l8WDdbdY0=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
cloud.google.com/go/storage v1.7.0 h1:DzdLPI8Em+DEk7IzA2a10ivq3mxIEASC9GeNJ6FFt5Q=
|
|
||||||
cloud.google.com/go/storage v1.7.0/go.mod h1:jGMIBwF+L/tL6WN/W5InNgYYu4HP0DvGB6rQ1mufWfs=
|
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
|
||||||
cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4=
|
|
||||||
cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
|
||||||
github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw=
|
|
||||||
github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw=
|
|
||||||
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 h1:9bAydALqAjBfPHd/eAiJBHnMZUYov8m2PkXVr+YGQeI=
|
|
||||||
github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82/go.mod h1:tyA14J0sA3Hph4dt+AfCjPrYR13+vVodshQSM7km9qw=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
|
||||||
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
|
||||||
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
|
||||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
|
||||||
github.com/frikky/kin-openapi v0.38.0 h1:V7ttwIJS8Vks4KL+mZVj1ZSqhIcQtgaG8akeqXEQgsE=
|
|
||||||
github.com/frikky/kin-openapi v0.38.0/go.mod h1:Fr28TtCHL4K0kIqtqui8HWxN1LG5uAh3z/tDfFyiA1s=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.12 h1:+0EIfThmK47Po+LogPYZR4XjbS4Ds19WNMFu2YUSjhw=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.12/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.15 h1:508ceeEHfPBMCC8/K4Zve3kwRQqiXNJSw6+BDoq9X4E=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.15/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.20 h1:y6JlPnQDq//elICWvVfUfJyU9gH3fSpQmPy+agqZ5sA=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.20/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.21 h1:xj/XPsXTa2rx41mm4nUc7+2K9RGkq2/mpjSPtIfpjE4=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.21/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.22 h1:TFMcJCNmOOSneMMWbg5dNzp2z6m0aZLROuL+bzVToRE=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.22/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.23 h1:Pnlc2M6fHnFRLFd5K1iLTVv4/t4P04Ri1GJ5CMxzq0U=
|
|
||||||
github.com/frikky/shuffle-shared v0.0.23/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ=
|
|
||||||
github.com/getkin/kin-openapi v0.8.0 h1:a6TQjTqwkyscC4/hShJX7WhCVE+4bi9lzw61XHQW5hE=
|
|
||||||
github.com/getkin/kin-openapi v0.8.0/go.mod h1:zZQMFkVgRHCdhgb6ihCTIo9dyDZFvX0k/xAKqw1FhPw=
|
|
||||||
github.com/getkin/kin-openapi v0.52.0 h1:6WqsF5d6PfJ8AscdD+9Rtb2RP2iBWyC7V6GcjssWg7M=
|
|
||||||
github.com/getkin/kin-openapi v0.52.0/go.mod h1:fRpo2Nw4Czgy0QnrIesRrEXs5+15N1F9mGZLP/aIomE=
|
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
|
||||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
|
||||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
|
||||||
github.com/go-git/go-billy v4.2.0+incompatible h1:Z6QtVXd5tjxUtcODLugkJg4WaZnGg13CD8qB9pr+7q0=
|
|
||||||
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
|
||||||
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
|
||||||
github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA=
|
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
|
||||||
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
|
|
||||||
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
|
||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
|
||||||
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
|
|
||||||
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
|
|
||||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
|
|
||||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
|
||||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
|
||||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
|
||||||
github.com/h2non/filetype v1.0.12 h1:yHCsIe0y2cvbDARtJhGBTD2ecvqMSTvlIcph9En/Zao=
|
|
||||||
github.com/h2non/filetype v1.0.12/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
|
||||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
|
||||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
|
||||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
|
||||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
|
|
||||||
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA=
|
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
|
||||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 h1:/hEknzWkMPCjTo7StMHRrBRa8YBbXuBWfck8680k3RE=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
|
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e h1:hq86ru83GdWTlfQFZGO4nZJTU4Bs2wfHl8oFHRaXsfc=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64=
|
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
|
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d h1:lzLdP95xJmMpwQ6LUHwrc5V7js93hTiY7gkznu0BgmY=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
|
||||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.23.0 h1:YlvGEOq2NA2my8cZ/9V8BcEO9okD48FlJcdqN0xJL3s=
|
|
||||||
google.golang.org/api v0.23.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
|
||||||
google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo=
|
|
||||||
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE=
|
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
|
|
||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31 h1:Bz1qTn2YRWV+9OKJtxHJiQKCiXIdf+kwuKXdt9cBxyU=
|
|
||||||
google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595 h1:x7nk+/4+SvuTDI4wnzQUlhvi+DTpyfncXBo3QWTFs7U=
|
|
||||||
google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
|
||||||
google.golang.org/grpc v1.34.1 h1:ugq+9++ZQPFzM2pKUMCIK8gj9M0pFyuUWO9Q8kwEDQw=
|
|
||||||
google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
|
||||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
|
||||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
|
||||||
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
|
|
||||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
|
||||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
+1073
-1450
File diff suppressed because it is too large
Load Diff
+242
-538
@@ -15,160 +15,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"cloud.google.com/go/datastore"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is what the structure should be when it's sent into a workflow
|
func getOutlookAttachment(client *http.Client, emailId, attachmentId string) ([]shuffle.FullEmail, error) {
|
||||||
type ParsedShuffleMail struct {
|
|
||||||
Body struct {
|
|
||||||
URI []string `json:"uri"`
|
|
||||||
Email []string `json:"email"`
|
|
||||||
Domain []string `json:"domain"`
|
|
||||||
ContentHeader struct {
|
|
||||||
} `json:"content_header"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
ContentType string `json:"content_type"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
RawBody string `json:"raw_body"`
|
|
||||||
} `json:"body"`
|
|
||||||
Header struct {
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
From string `json:"from"`
|
|
||||||
To []string `json:"to"`
|
|
||||||
Date string `json:"date"`
|
|
||||||
Received []struct {
|
|
||||||
Src string `json:"src"`
|
|
||||||
From []string `json:"from"`
|
|
||||||
By []string `json:"by"`
|
|
||||||
With string `json:"with"`
|
|
||||||
Date string `json:"date"`
|
|
||||||
} `json:"received"`
|
|
||||||
ReceivedDomain []string `json:"received_domain"`
|
|
||||||
ReceivedIP []string `json:"received_ip"`
|
|
||||||
Header struct {
|
|
||||||
} `json:"header"`
|
|
||||||
} `json:"header"`
|
|
||||||
MessageID string `json:"message_id"`
|
|
||||||
EmailFileid string `json:"email_fileid"`
|
|
||||||
AttachmentUids []string `json:"attachment_uids"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FullEmail struct {
|
|
||||||
OdataContext string `json:"@odata.context"`
|
|
||||||
OdataEtag string `json:"@odata.etag"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Createddatetime time.Time `json:"createdDateTime"`
|
|
||||||
Lastmodifieddatetime time.Time `json:"lastModifiedDateTime"`
|
|
||||||
Changekey string `json:"changeKey"`
|
|
||||||
Categories []interface{} `json:"categories"`
|
|
||||||
Receiveddatetime time.Time `json:"receivedDateTime"`
|
|
||||||
Sentdatetime time.Time `json:"sentDateTime"`
|
|
||||||
Hasattachments bool `json:"hasAttachments"`
|
|
||||||
Internetmessageid string `json:"internetMessageId"`
|
|
||||||
Subject string `json:"subject"`
|
|
||||||
Bodypreview string `json:"bodyPreview"`
|
|
||||||
Importance string `json:"importance"`
|
|
||||||
Parentfolderid string `json:"parentFolderId"`
|
|
||||||
Conversationid string `json:"conversationId"`
|
|
||||||
Conversationindex string `json:"conversationIndex"`
|
|
||||||
Isdeliveryreceiptrequested interface{} `json:"isDeliveryReceiptRequested"`
|
|
||||||
Isreadreceiptrequested bool `json:"isReadReceiptRequested"`
|
|
||||||
Isread bool `json:"isRead"`
|
|
||||||
Isdraft bool `json:"isDraft"`
|
|
||||||
Weblink string `json:"webLink"`
|
|
||||||
Inferenceclassification string `json:"inferenceClassification"`
|
|
||||||
Body struct {
|
|
||||||
Contenttype string `json:"contentType"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
} `json:"body"`
|
|
||||||
Sender struct {
|
|
||||||
Emailaddress struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
} `json:"emailAddress"`
|
|
||||||
} `json:"sender"`
|
|
||||||
From struct {
|
|
||||||
Emailaddress struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
} `json:"emailAddress"`
|
|
||||||
} `json:"from"`
|
|
||||||
Torecipients []struct {
|
|
||||||
Emailaddress struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
} `json:"emailAddress"`
|
|
||||||
} `json:"toRecipients"`
|
|
||||||
Ccrecipients []interface{} `json:"ccRecipients"`
|
|
||||||
Bccrecipients []interface{} `json:"bccRecipients"`
|
|
||||||
Replyto []interface{} `json:"replyTo"`
|
|
||||||
Flag struct {
|
|
||||||
Flagstatus string `json:"flagStatus"`
|
|
||||||
} `json:"flag"`
|
|
||||||
Attachments []struct {
|
|
||||||
OdataType string `json:"@odata.type"`
|
|
||||||
OdataMediacontenttype string `json:"@odata.mediaContentType"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Lastmodifieddatetime time.Time `json:"lastModifiedDateTime"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Contenttype string `json:"contentType"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
Isinline bool `json:"isInline"`
|
|
||||||
Contentid interface{} `json:"contentId"`
|
|
||||||
Contentlocation interface{} `json:"contentLocation"`
|
|
||||||
Contentbytes string `json:"contentBytes"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MailData struct {
|
|
||||||
Value []struct {
|
|
||||||
Subscriptionid string `json:"subscriptionId"`
|
|
||||||
Subscriptionexpirationdatetime string `json:"subscriptionExpirationDateTime"`
|
|
||||||
Changetype string `json:"changeType"`
|
|
||||||
Resource string `json:"resource"`
|
|
||||||
Resourcedata struct {
|
|
||||||
OdataType string `json:"@odata.type"`
|
|
||||||
OdataID string `json:"@odata.id"`
|
|
||||||
OdataEtag string `json:"@odata.etag"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
} `json:"resourceData"`
|
|
||||||
Clientstate string `json:"clientState"`
|
|
||||||
Tenantid string `json:"tenantId"`
|
|
||||||
} `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutlookProfile struct {
|
|
||||||
OdataContext string `json:"@odata.context"`
|
|
||||||
BusinessPhones []string `json:"businessPhones"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
GivenName string `json:"givenName"`
|
|
||||||
JobTitle interface{} `json:"jobTitle"`
|
|
||||||
Mail string `json:"mail"`
|
|
||||||
MobilePhone interface{} `json:"mobilePhone"`
|
|
||||||
OfficeLocation interface{} `json:"officeLocation"`
|
|
||||||
PreferredLanguage interface{} `json:"preferredLanguage"`
|
|
||||||
Surname string `json:"surname"`
|
|
||||||
UserPrincipalName string `json:"userPrincipalName"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutlookFolder struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
DisplayName string `json:"displayName"`
|
|
||||||
ParentFolderID string `json:"parentFolderId"`
|
|
||||||
ChildFolderCount int `json:"childFolderCount"`
|
|
||||||
UnreadItemCount int `json:"unreadItemCount"`
|
|
||||||
TotalItemCount int `json:"totalItemCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutlookFolders struct {
|
|
||||||
OdataContext string `json:"@odata.context"`
|
|
||||||
OdataNextLink string `json:"@odata.nextLink"`
|
|
||||||
Value []OutlookFolder `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOutlookAttachment(client *http.Client, emailId, attachmentId string) ([]FullEmail, error) {
|
|
||||||
//requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/ec03b4f2-fccf-4c35-b0eb-be85a0f5dd43/mailFolders")
|
//requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/ec03b4f2-fccf-4c35-b0eb-be85a0f5dd43/mailFolders")
|
||||||
|
|
||||||
requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/%s/attachments/%s", emailId, attachmentId)
|
requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/%s/attachments/%s", emailId, attachmentId)
|
||||||
@@ -177,20 +27,20 @@ func getOutlookAttachment(client *http.Client, emailId, attachmentId string) ([]
|
|||||||
ret, err := client.Get(requestUrl)
|
ret, err := client.Get(requestUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] OutlookErr: %s", err)
|
log.Printf("[INFO] OutlookErr: %s", err)
|
||||||
return []FullEmail{}, err
|
return []shuffle.FullEmail{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(ret.Body)
|
body, err := ioutil.ReadAll(ret.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARNING] Failed body decoding from outlook email")
|
log.Printf("[WARNING] Failed body decoding from outlook email")
|
||||||
return []FullEmail{}, err
|
return []shuffle.FullEmail{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//type FullEmail struct {
|
//type FullEmail struct {
|
||||||
log.Printf("[INFO] Attachment Body: %s", string(body))
|
log.Printf("[INFO] Attachment Body: %s", string(body))
|
||||||
log.Printf("[INFO] Status email: %d", ret.StatusCode)
|
log.Printf("[INFO] Status email: %d", ret.StatusCode)
|
||||||
if ret.StatusCode != 200 {
|
if ret.StatusCode != 200 {
|
||||||
return []FullEmail{}, err
|
return []shuffle.FullEmail{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("Body: %s", string(body))
|
//log.Printf("Body: %s", string(body))
|
||||||
@@ -206,13 +56,13 @@ func getOutlookAttachment(client *http.Client, emailId, attachmentId string) ([]
|
|||||||
emails = append(emails, parsedmail)
|
emails = append(emails, parsedmail)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return []FullEmail{}, nil
|
return []shuffle.FullEmail{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOutlookEmail(client *http.Client, maildata MailData) ([]FullEmail, error) {
|
func getOutlookEmail(client *http.Client, maildata shuffle.MailData) ([]shuffle.FullEmail, error) {
|
||||||
//requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/ec03b4f2-fccf-4c35-b0eb-be85a0f5dd43/mailFolders")
|
//requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/ec03b4f2-fccf-4c35-b0eb-be85a0f5dd43/mailFolders")
|
||||||
|
|
||||||
emails := []FullEmail{}
|
emails := []shuffle.FullEmail{}
|
||||||
for _, email := range maildata.Value {
|
for _, email := range maildata.Value {
|
||||||
//messageId := email.Resourcedata.ID
|
//messageId := email.Resourcedata.ID
|
||||||
//requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me/%s", messageId)
|
//requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me/%s", messageId)
|
||||||
@@ -222,29 +72,29 @@ func getOutlookEmail(client *http.Client, maildata MailData) ([]FullEmail, error
|
|||||||
ret, err := client.Get(requestUrl)
|
ret, err := client.Get(requestUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] OutlookErr: %s", err)
|
log.Printf("[INFO] OutlookErr: %s", err)
|
||||||
return []FullEmail{}, err
|
return []shuffle.FullEmail{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(ret.Body)
|
body, err := ioutil.ReadAll(ret.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARNING] Failed body decoding from outlook email")
|
log.Printf("[WARNING] Failed body decoding from outlook email")
|
||||||
return []FullEmail{}, err
|
return []shuffle.FullEmail{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//type FullEmail struct {
|
//type shuffle.FullEmail struct {
|
||||||
//log.Printf("[INFO] EMAIL Body: %s", string(body))
|
//log.Printf("[INFO] EMAIL Body: %s", string(body))
|
||||||
//log.Printf("[INFO] Status email: %d", ret.StatusCode)
|
//log.Printf("[INFO] Status email: %d", ret.StatusCode)
|
||||||
if ret.StatusCode != 200 {
|
if ret.StatusCode != 200 {
|
||||||
return []FullEmail{}, err
|
return []shuffle.FullEmail{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Printf("Body: %s", string(body))
|
//log.Printf("Body: %s", string(body))
|
||||||
|
|
||||||
parsedmail := FullEmail{}
|
parsedmail := shuffle.FullEmail{}
|
||||||
err = json.Unmarshal(body, &parsedmail)
|
err = json.Unmarshal(body, &parsedmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] Email unmarshal error: %s", err)
|
log.Printf("[INFO] Email unmarshal error: %s", err)
|
||||||
return []FullEmail{}, err
|
return []shuffle.FullEmail{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
emails = append(emails, parsedmail)
|
emails = append(emails, parsedmail)
|
||||||
@@ -253,77 +103,48 @@ func getOutlookEmail(client *http.Client, maildata MailData) ([]FullEmail, error
|
|||||||
return emails, nil
|
return emails, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOutlookFolders(client *http.Client) (OutlookFolders, error) {
|
func getOutlookProfile(client *http.Client) (shuffle.OutlookProfile, error) {
|
||||||
//requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/ec03b4f2-fccf-4c35-b0eb-be85a0f5dd43/mailFolders")
|
|
||||||
requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me/mailFolders")
|
|
||||||
|
|
||||||
ret, err := client.Get(requestUrl)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[INFO] FolderErr: %s", err)
|
|
||||||
return OutlookFolders{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(ret.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[WARNING] Failed body decoding from mailfolders")
|
|
||||||
return OutlookFolders{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//log.Printf("[INFO] Folder Body: %s", string(body))
|
|
||||||
log.Printf("[INFO] Status folders: %d", ret.StatusCode)
|
|
||||||
if ret.StatusCode != 200 {
|
|
||||||
return OutlookFolders{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//log.Printf("Body: %s", string(body))
|
|
||||||
|
|
||||||
mailfolders := OutlookFolders{}
|
|
||||||
err = json.Unmarshal(body, &mailfolders)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Unmarshal: %s", err)
|
|
||||||
return OutlookFolders{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//fmt.Printf("%#v", mailfolders)
|
|
||||||
// FIXME - recursion for subfolders
|
|
||||||
// Recursive struct
|
|
||||||
// folderEndpoint := fmt.Sprintf("%s/%s/childfolders?$top=40", requestUrl, parentId)
|
|
||||||
//for _, folder := range mailfolders.Value {
|
|
||||||
// log.Println(folder.DisplayName)
|
|
||||||
//}
|
|
||||||
|
|
||||||
return mailfolders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOutlookProfile(client *http.Client) (OutlookProfile, error) {
|
|
||||||
requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me?$select=mail")
|
requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me?$select=mail")
|
||||||
|
|
||||||
ret, err := client.Get(requestUrl)
|
ret, err := client.Get(requestUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] Folder error: %s", err)
|
log.Printf("[INFO] Folder error: %s", err)
|
||||||
return OutlookProfile{}, err
|
return shuffle.OutlookProfile{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Status profile: %d", ret.StatusCode)
|
log.Printf("[INFO] Status profile: %d", ret.StatusCode)
|
||||||
body, err := ioutil.ReadAll(ret.Body)
|
body, err := ioutil.ReadAll(ret.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] Body: %s", err)
|
log.Printf("[INFO] Body: %s", err)
|
||||||
return OutlookProfile{}, err
|
return shuffle.OutlookProfile{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] BODY: %s", string(body))
|
log.Printf("[INFO] BODY: %s", string(body))
|
||||||
|
|
||||||
profile := OutlookProfile{}
|
profile := shuffle.OutlookProfile{}
|
||||||
err = json.Unmarshal(body, &profile)
|
err = json.Unmarshal(body, &profile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Unmarshal: %s", err)
|
log.Printf("Unmarshal: %s", err)
|
||||||
return OutlookProfile{}, err
|
return shuffle.OutlookProfile{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return profile, nil
|
return profile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
cors := shuffle.HandleCors(resp, request)
|
||||||
|
if cors {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := shuffle.HandleApiAuthentication(resp, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[INFO] Api authentication failed in getting specific trigger: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
code := request.URL.Query().Get("code")
|
code := request.URL.Query().Get("code")
|
||||||
if len(code) == 0 {
|
if len(code) == 0 {
|
||||||
log.Println("No code")
|
log.Println("No code")
|
||||||
@@ -334,7 +155,7 @@ func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
url := fmt.Sprintf("http://%s%s", request.Host, request.URL.EscapedPath())
|
url := fmt.Sprintf("http://%s%s", request.Host, request.URL.EscapedPath())
|
||||||
log.Println(url)
|
log.Println(url)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, accessToken, err := getOutlookClient(ctx, code, OauthToken{}, url)
|
_, accessToken, err := getOutlookClient(ctx, code, shuffle.OauthToken{}, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Oauth client failure - outlook register: %s", err)
|
log.Printf("Oauth client failure - outlook register: %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -367,7 +188,7 @@ func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
// FIXME - trigger auth
|
// FIXME - trigger auth
|
||||||
senderUser := ""
|
senderUser := ""
|
||||||
trigger := TriggerAuth{}
|
trigger := shuffle.TriggerAuth{}
|
||||||
for _, item := range stateitems {
|
for _, item := range stateitems {
|
||||||
itemsplit := strings.Split(item, "%3D")
|
itemsplit := strings.Split(item, "%3D")
|
||||||
if len(itemsplit) == 1 {
|
if len(itemsplit) == 1 {
|
||||||
@@ -404,7 +225,7 @@ func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
trigger.Code = code
|
trigger.Code = code
|
||||||
trigger.OauthToken = OauthToken{
|
trigger.OauthToken = shuffle.OauthToken{
|
||||||
AccessToken: accessToken.AccessToken,
|
AccessToken: accessToken.AccessToken,
|
||||||
TokenType: accessToken.TokenType,
|
TokenType: accessToken.TokenType,
|
||||||
RefreshToken: accessToken.RefreshToken,
|
RefreshToken: accessToken.RefreshToken,
|
||||||
@@ -425,7 +246,7 @@ func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Should also update the user
|
// Should also update the user
|
||||||
Userdata, err := shuffle.GetUser(ctx, senderUser)
|
Userdata, err := shuffle.GetUser(ctx, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] Username %s doesn't exist (oauth2): %s", trigger.Username, err)
|
log.Printf("[INFO] Username %s doesn't exist (oauth2): %s", trigger.Username, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -458,14 +279,14 @@ func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Set apikey for the user if they don't have one
|
// Set apikey for the user if they don't have one
|
||||||
err = shuffle.SetUser(ctx, Userdata)
|
err = shuffle.SetUser(ctx, Userdata, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed setting user data for %s: %s", Userdata.Username, err)
|
log.Printf("Failed setting user data for %s: %s", Userdata.Username, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setTriggerAuth(ctx, trigger)
|
err = shuffle.SetTriggerAuth(ctx, trigger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to set trigger auth for %s - %s", trigger.Username, err)
|
log.Printf("Failed to set trigger auth for %s - %s", trigger.Username, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -476,50 +297,8 @@ func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write([]byte(`{"success": true}`))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
type OauthToken struct {
|
|
||||||
AccessToken string `json:"AccessToken" datastore:"AccessToken,noindex"`
|
|
||||||
TokenType string `json:"TokenType" datastore:"TokenType,noindex"`
|
|
||||||
RefreshToken string `json:"RefreshToken" datastore:"RefreshToken,noindex"`
|
|
||||||
Expiry time.Time `json:"Expiry" datastore:"Expiry,noindex"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TriggerAuth struct {
|
|
||||||
Id string `json:"id" datastore:"id"`
|
|
||||||
SubscriptionId string `json:"subscriptionId" datastore:"subscriptionId"`
|
|
||||||
|
|
||||||
Username string `json:"username" datastore:"username,noindex"`
|
|
||||||
WorkflowId string `json:"workflow_id" datastore:"workflow_id,noindex"`
|
|
||||||
Owner string `json:"owner" datastore:"owner"`
|
|
||||||
Type string `json:"type" datastore:"type"`
|
|
||||||
Code string `json:"code,omitempty" datastore:"code,noindex"`
|
|
||||||
Start string `json:"start" datastore:"start"`
|
|
||||||
OauthToken OauthToken `json:"oauth_token,omitempty" datastore:"oauth_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTriggerAuth(ctx context.Context, id string) (*TriggerAuth, error) {
|
|
||||||
key := datastore.NameKey("trigger_auth", strings.ToLower(id), nil)
|
|
||||||
triggerauth := &TriggerAuth{}
|
|
||||||
if err := dbclient.Get(ctx, key, triggerauth); err != nil {
|
|
||||||
return &TriggerAuth{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return triggerauth, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTriggerAuth(ctx context.Context, trigger TriggerAuth) error {
|
|
||||||
key1 := datastore.NameKey("trigger_auth", strings.ToLower(trigger.Id), nil)
|
|
||||||
|
|
||||||
// New struct, to not add body, author etc
|
|
||||||
if _, err := dbclient.Put(ctx, key1, &trigger); err != nil {
|
|
||||||
log.Printf("Error adding trigger auth: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// THis all of a sudden became really horrible.. fml
|
// THis all of a sudden became really horrible.. fml
|
||||||
func getOutlookClient(ctx context.Context, code string, accessToken OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) {
|
func getOutlookClient(ctx context.Context, code string, accessToken shuffle.OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) {
|
||||||
|
|
||||||
conf := &oauth2.Config{
|
conf := &oauth2.Config{
|
||||||
ClientID: "fd55c175-aa30-4fa6-b303-09a29fb3f750",
|
ClientID: "fd55c175-aa30-4fa6-b303-09a29fb3f750",
|
||||||
@@ -557,81 +336,6 @@ func getOutlookClient(ctx context.Context, code string, accessToken OauthToken,
|
|||||||
return client, access_token, nil
|
return client, access_token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetOutlookFolders(resp http.ResponseWriter, request *http.Request) {
|
|
||||||
cors := handleCors(resp, request)
|
|
||||||
if cors {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exchange every time hmm
|
|
||||||
// FIXME
|
|
||||||
// Should really just get the code from the trigger that's being used OR the user
|
|
||||||
triggerId := request.URL.Query().Get("trigger_id")
|
|
||||||
if len(triggerId) == 0 {
|
|
||||||
log.Println("No trigger_id supplied")
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
trigger, err := getTriggerAuth(ctx, triggerId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[INFO] Trigger %s doesn't exist - outlook folders.", triggerId)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Trigger doesn't exist."}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//client, accessToken, err := getOutlookClient(ctx, code, OauthToken{}, url)
|
|
||||||
//if err != nil {
|
|
||||||
// log.Printf("Oauth client failure - outlook register: %s", err)
|
|
||||||
// resp.WriteHeader(401)
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
|
|
||||||
// FIXME - should be shuffler in literally every case except testing lol
|
|
||||||
//log.Printf("TRIGGER: %#v", trigger)
|
|
||||||
redirectDomain := "localhost:5001"
|
|
||||||
url := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain)
|
|
||||||
outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[WARNING] Oauth client failure - outlook folders: %s", err)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Failed creating outlook client"}`))
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should be possible, and will also give the actual username
|
|
||||||
/*
|
|
||||||
profile, err := getOutlookProfile(outlookClient)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Outlook profile failure: %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("PROFILE: %#v", profile)
|
|
||||||
*/
|
|
||||||
|
|
||||||
folders, err := getOutlookFolders(outlookClient)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[WARNING] Failed setting outlook folders: %s", err)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Failed getting outlook folders"}`))
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.Marshal(folders.Value)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("[INFO] Failed to marshal folderdata")
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": "Failed decoding JSON"}`))
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.WriteHeader(200)
|
|
||||||
resp.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGetSpecificTrigger(resp http.ResponseWriter, request *http.Request) {
|
func handleGetSpecificTrigger(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -664,7 +368,7 @@ func handleGetSpecificTrigger(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
trigger, err := getTriggerAuth(ctx, workflowId)
|
trigger, err := shuffle.GetTriggerAuth(ctx, workflowId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] Trigger %s doesn't exist - specific trigger.", workflowId)
|
log.Printf("[INFO] Trigger %s doesn't exist - specific trigger.", workflowId)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -678,7 +382,7 @@ func handleGetSpecificTrigger(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger.OauthToken = OauthToken{}
|
trigger.OauthToken = shuffle.OauthToken{}
|
||||||
trigger.Code = ""
|
trigger.Code = ""
|
||||||
|
|
||||||
b, err := json.Marshal(trigger)
|
b, err := json.Marshal(trigger)
|
||||||
@@ -692,201 +396,6 @@ func handleGetSpecificTrigger(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.Write(b)
|
resp.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This sets up the sub with outlook itself
|
|
||||||
// Parses data from the workflow to see whether access is right to subscribe it
|
|
||||||
// Creates the cloud function for outlook return
|
|
||||||
// Wait for it to be available, then schedule a workflow to it
|
|
||||||
func createOutlookSub(resp http.ResponseWriter, request *http.Request) {
|
|
||||||
cors := handleCors(resp, request)
|
|
||||||
if cors {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
location := strings.Split(request.URL.String(), "/")
|
|
||||||
|
|
||||||
var workflowId string
|
|
||||||
if location[1] == "api" {
|
|
||||||
if len(location) <= 4 {
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
workflowId = location[4]
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
workflow, err := shuffle.GetWorkflow(ctx, workflowId)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed getting the workflow locally (outlook sub): %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := shuffle.HandleApiAuthentication(resp, request)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Api authentication failed in outlook deploy: %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME - have a check for org etc too..
|
|
||||||
if user.Id != workflow.Owner && user.Role != "admin" {
|
|
||||||
log.Printf("Wrong user (%s) for workflow %s when deploying outlook", user.Username, workflow.ID)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[INFO] Handle outlook subscription for trigger")
|
|
||||||
|
|
||||||
// Should already be authorized at this point, as the workflow is shared
|
|
||||||
body, err := ioutil.ReadAll(request.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed body read for workflow %s", workflow.ID)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Based on the input data from frontend
|
|
||||||
type CurTrigger struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Folders []string `json:"folders"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//log.Println(string(body))
|
|
||||||
var curTrigger CurTrigger
|
|
||||||
err = json.Unmarshal(body, &curTrigger)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed body read unmarshal for trigger %s", workflow.ID)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(curTrigger.Folders) == 0 {
|
|
||||||
log.Printf("Error for %s. Choosing folders is required, currently 0", workflow.ID)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that it's deployed - wait a few seconds before generating:
|
|
||||||
// 1. Oauth2 token thingies for outlook.office.com
|
|
||||||
// 2. Set the url to have the right mailboxes (probably ID?) ("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages")
|
|
||||||
// 3. Set the callback URL to be the new trigger
|
|
||||||
// 4. Run subscription test
|
|
||||||
// 5. Set the subscriptionId to the trigger object
|
|
||||||
|
|
||||||
// First - lets regenerate an oauth token for outlook.office.com from the original items
|
|
||||||
trigger, err := getTriggerAuth(ctx, curTrigger.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[INFO] Trigger %s doesn't exist - outlook sub.", curTrigger.ID)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// url doesn't really matter here
|
|
||||||
//url := fmt.Sprintf("https://shuffler.io")
|
|
||||||
redirectDomain := "localhost:5001"
|
|
||||||
url := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain)
|
|
||||||
outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Oauth client failure - triggerauth: %s", err)
|
|
||||||
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Location +
|
|
||||||
|
|
||||||
// This is here simply to let the function start
|
|
||||||
// Usually takes 10 attempts minimum :O
|
|
||||||
// 10 * 5 = 50 seconds. That's waaay too much :(
|
|
||||||
|
|
||||||
if runningEnvironment != "cloud" {
|
|
||||||
org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed finding org %s: %s", org.Id, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("[INFO] Starting cloud configuration TO START trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId)
|
|
||||||
|
|
||||||
action := shuffle.CloudSyncJob{
|
|
||||||
Type: "outlook",
|
|
||||||
Action: "start",
|
|
||||||
OrgId: org.Id,
|
|
||||||
PrimaryItemId: trigger.Id,
|
|
||||||
SecondaryItem: trigger.Start,
|
|
||||||
ThirdItem: workflowId,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = executeCloudAction(action, org.SyncConfig.Apikey)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[INFO] Failed cloud action START outlook execution: %s", err)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
log.Printf("[INFO] Successfully set up cloud action trigger")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Should configure a running environment for CLOUD")
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationURL := fmt.Sprintf("%s/api/v1/hooks/webhook_%s", syncSubUrl, trigger.Id)
|
|
||||||
curSubscriptions, err := getOutlookSubscriptions(outlookClient)
|
|
||||||
if err == nil {
|
|
||||||
for _, sub := range curSubscriptions.Value {
|
|
||||||
if sub.NotificationURL == notificationURL {
|
|
||||||
log.Printf("[INFO] Removing existing subscription %s", sub.Id)
|
|
||||||
removeOutlookSubscription(outlookClient, sub.Id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("[INFO] Failed to get subscriptions - need to overwrite")
|
|
||||||
}
|
|
||||||
|
|
||||||
maxFails := 5
|
|
||||||
failCnt := 0
|
|
||||||
log.Println(curTrigger.Folders)
|
|
||||||
for {
|
|
||||||
subId, err := makeOutlookSubscription(outlookClient, curTrigger.Folders, notificationURL)
|
|
||||||
if err != nil {
|
|
||||||
failCnt += 1
|
|
||||||
log.Printf("Failed making oauth subscription, retrying in 5 seconds: %s", err)
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
if failCnt == maxFails {
|
|
||||||
log.Printf("Failed to set up subscription %d times.", maxFails)
|
|
||||||
resp.WriteHeader(401)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the ID somewhere here
|
|
||||||
trigger.SubscriptionId = subId
|
|
||||||
err = setTriggerAuth(ctx, *trigger)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed setting triggerauth: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[INFO] Successfully handled outlook subscription for trigger %s in workflow %s", curTrigger.ID, workflow.ID)
|
|
||||||
|
|
||||||
//log.Printf("%#v", user)
|
|
||||||
resp.WriteHeader(200)
|
|
||||||
resp.Write([]byte(`{"success": true}`))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lists the users current subscriptions
|
// Lists the users current subscriptions
|
||||||
func getOutlookSubscriptions(outlookClient *http.Client) (SubscriptionsWrapper, error) {
|
func getOutlookSubscriptions(outlookClient *http.Client) (SubscriptionsWrapper, error) {
|
||||||
fullUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/subscriptions")
|
fullUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/subscriptions")
|
||||||
@@ -1024,7 +533,7 @@ func handleOutlookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//func getTriggerAuth(ctx context.Context, id string) (*TriggerAuth, error) {
|
//func getTriggerAuth(ctx context.Context, id string) (*TriggerAuth, error) {
|
||||||
hook, err := getTriggerAuth(ctx, hookId)
|
hook, err := shuffle.GetTriggerAuth(ctx, hookId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[INFO] Failed getting trigger %s (callback): %s", hookId, err)
|
log.Printf("[INFO] Failed getting trigger %s (callback): %s", hookId, err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -1054,7 +563,7 @@ func handleOutlookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
// 1. Take the body and parse data -> Get the email itself
|
// 1. Take the body and parse data -> Get the email itself
|
||||||
|
|
||||||
maildata := MailData{}
|
maildata := shuffle.MailData{}
|
||||||
err = json.Unmarshal(body, &maildata)
|
err = json.Unmarshal(body, &maildata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Maildata unmarshal error: %s", err)
|
log.Printf("Maildata unmarshal error: %s", err)
|
||||||
@@ -1076,14 +585,14 @@ func handleOutlookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
log.Printf("[INFO] EMAILS: %d. If this is more than 1, please contact frikky@shuffler.io", len(emails))
|
log.Printf("[INFO] EMAILS: %d. If this is more than 1, please contact frikky@shuffler.io", len(emails))
|
||||||
//log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err)
|
//log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err)
|
||||||
|
|
||||||
//type FullEmail struct {
|
//type shuffle.FullEmail struct {
|
||||||
email := FullEmail{}
|
email := shuffle.FullEmail{}
|
||||||
if len(emails) == 1 {
|
if len(emails) == 1 {
|
||||||
email = emails[0]
|
email = emails[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse indicators (domains, emails, ips, domains etc)!
|
// Parse indicators (domains, emails, ips, domains etc)!
|
||||||
newEmail := ParsedShuffleMail{}
|
newEmail := shuffle.ParsedShuffleMail{}
|
||||||
newEmail.Body.ContentType = email.Body.Contenttype
|
newEmail.Body.ContentType = email.Body.Contenttype
|
||||||
newEmail.Body.Content = email.Body.Content
|
newEmail.Body.Content = email.Body.Content
|
||||||
newEmail.Body.RawBody = email.Body.Content
|
newEmail.Body.RawBody = email.Body.Content
|
||||||
@@ -1203,7 +712,7 @@ func handleOutlookSubRemoval(ctx context.Context, user shuffle.User, workflowId,
|
|||||||
// 2. Stop the subscription
|
// 2. Stop the subscription
|
||||||
// 3. Remove the function
|
// 3. Remove the function
|
||||||
// 4. Remove the database entry for auth
|
// 4. Remove the database entry for auth
|
||||||
trigger, err := getTriggerAuth(ctx, triggerId)
|
trigger, err := shuffle.GetTriggerAuth(ctx, triggerId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Trigger auth %s doesn't exist - outlook sub removal.", triggerId)
|
log.Printf("Trigger auth %s doesn't exist - outlook sub removal.", triggerId)
|
||||||
return err
|
return err
|
||||||
@@ -1293,7 +802,7 @@ func handleDeleteOutlookSub(resp http.ResponseWriter, request *http.Request) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
workflow, err := shuffle.GetWorkflow(ctx, workflowId)
|
workflow, err := shuffle.GetWorkflow(ctx, workflowId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed getting the workflow locally (delete outlook): %s", err)
|
log.Printf("[WARNING] Failed getting the workflow locally (delete outlook): %s", err)
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
resp.Write([]byte(`{"success": false}`))
|
resp.Write([]byte(`{"success": false}`))
|
||||||
return
|
return
|
||||||
@@ -1327,3 +836,198 @@ func handleDeleteOutlookSub(resp http.ResponseWriter, request *http.Request) {
|
|||||||
resp.WriteHeader(200)
|
resp.WriteHeader(200)
|
||||||
resp.Write([]byte(`{"success": true}`))
|
resp.Write([]byte(`{"success": true}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This sets up the sub with outlook itself
|
||||||
|
// Parses data from the workflow to see whether access is right to subscribe it
|
||||||
|
// Creates the cloud function for outlook return
|
||||||
|
// Wait for it to be available, then schedule a workflow to it
|
||||||
|
func createOutlookSub(resp http.ResponseWriter, request *http.Request) {
|
||||||
|
cors := handleCors(resp, request)
|
||||||
|
if cors {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
location := strings.Split(request.URL.String(), "/")
|
||||||
|
|
||||||
|
var workflowId string
|
||||||
|
if location[1] == "api" {
|
||||||
|
if len(location) <= 4 {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowId = location[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
workflow, err := shuffle.GetWorkflow(ctx, workflowId)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[WARNING] Failed getting the workflow locally (outlook sub): %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := shuffle.HandleApiAuthentication(resp, request)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Api authentication failed in outlook deploy: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME - have a check for org etc too..
|
||||||
|
if user.Id != workflow.Owner && user.Role != "admin" {
|
||||||
|
log.Printf("Wrong user (%s) for workflow %s when deploying outlook", user.Username, workflow.ID)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[INFO] Handle outlook subscription for trigger")
|
||||||
|
|
||||||
|
// Should already be authorized at this point, as the workflow is shared
|
||||||
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed body read for workflow %s", workflow.ID)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on the input data from frontend
|
||||||
|
type CurTrigger struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Folders []string `json:"folders"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Println(string(body))
|
||||||
|
var curTrigger CurTrigger
|
||||||
|
err = json.Unmarshal(body, &curTrigger)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed body read unmarshal for trigger %s", workflow.ID)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(curTrigger.Folders) == 0 {
|
||||||
|
log.Printf("Error for %s. Choosing folders is required, currently 0", workflow.ID)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that it's deployed - wait a few seconds before generating:
|
||||||
|
// 1. Oauth2 token thingies for outlook.office.com
|
||||||
|
// 2. Set the url to have the right mailboxes (probably ID?) ("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages")
|
||||||
|
// 3. Set the callback URL to be the new trigger
|
||||||
|
// 4. Run subscription test
|
||||||
|
// 5. Set the subscriptionId to the trigger object
|
||||||
|
|
||||||
|
// First - lets regenerate an oauth token for outlook.office.com from the original items
|
||||||
|
trigger, err := shuffle.GetTriggerAuth(ctx, curTrigger.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[INFO] Trigger %s doesn't exist - outlook sub.", curTrigger.ID)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// url doesn't really matter here
|
||||||
|
//url := fmt.Sprintf("https://shuffler.io")
|
||||||
|
redirectDomain := "localhost:5001"
|
||||||
|
url := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain)
|
||||||
|
outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Oauth client failure - triggerauth: %s", err)
|
||||||
|
resp.Write([]byte(`{"success": false, "reason": ""}`))
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location +
|
||||||
|
|
||||||
|
// This is here simply to let the function start
|
||||||
|
// Usually takes 10 attempts minimum :O
|
||||||
|
// 10 * 5 = 50 seconds. That's waaay too much :(
|
||||||
|
|
||||||
|
if runningEnvironment != "cloud" {
|
||||||
|
org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed finding org %s: %s", org.Id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[INFO] Starting cloud configuration TO START trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId)
|
||||||
|
|
||||||
|
action := shuffle.CloudSyncJob{
|
||||||
|
Type: "outlook",
|
||||||
|
Action: "start",
|
||||||
|
OrgId: org.Id,
|
||||||
|
PrimaryItemId: trigger.Id,
|
||||||
|
SecondaryItem: trigger.Start,
|
||||||
|
ThirdItem: workflowId,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = executeCloudAction(action, org.SyncConfig.Apikey)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[INFO] Failed cloud action START outlook execution: %s", err)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Printf("[INFO] Successfully set up cloud action trigger")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Should configure a running environment for CLOUD")
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationURL := fmt.Sprintf("%s/api/v1/hooks/webhook_%s", syncSubUrl, trigger.Id)
|
||||||
|
curSubscriptions, err := getOutlookSubscriptions(outlookClient)
|
||||||
|
if err == nil {
|
||||||
|
for _, sub := range curSubscriptions.Value {
|
||||||
|
if sub.NotificationURL == notificationURL {
|
||||||
|
log.Printf("[INFO] Removing existing subscription %s", sub.Id)
|
||||||
|
removeOutlookSubscription(outlookClient, sub.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[INFO] Failed to get subscriptions - need to overwrite")
|
||||||
|
}
|
||||||
|
|
||||||
|
maxFails := 5
|
||||||
|
failCnt := 0
|
||||||
|
log.Println(curTrigger.Folders)
|
||||||
|
for {
|
||||||
|
subId, err := makeOutlookSubscription(outlookClient, curTrigger.Folders, notificationURL)
|
||||||
|
if err != nil {
|
||||||
|
failCnt += 1
|
||||||
|
log.Printf("Failed making oauth subscription, retrying in 5 seconds: %s", err)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
if failCnt == maxFails {
|
||||||
|
log.Printf("Failed to set up subscription %d times.", maxFails)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the ID somewhere here
|
||||||
|
trigger.SubscriptionId = subId
|
||||||
|
err = shuffle.SetTriggerAuth(ctx, *trigger)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed setting triggerauth: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Successfully handled outlook subscription for trigger %s in workflow %s", curTrigger.ID, workflow.ID)
|
||||||
|
|
||||||
|
//log.Printf("%#v", user)
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(`{"success": true}`))
|
||||||
|
}
|
||||||
|
|||||||
+410
-1922
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
|||||||
|
curl -XPOST http://192.168.3.8:5001/api/v1/orgs/6a6a99f5-6630-4f91-88ff-571c9f030ea0/set_cache -H "Authorization: Bearer e663cf93-7f10-4560-bef0-303f14aad982" -d '{
|
||||||
|
"workflow_id": "61825389-a125-43a5-9119-97c401e9934b",
|
||||||
|
"execution_id": "2c8ca1b7-6658-4742-86a1-105c9467702d",
|
||||||
|
"org_id": "6a6a99f5-6630-4f91-88ff-571c9f030ea0",
|
||||||
|
"key": "test",
|
||||||
|
"value": "THIS IS SOME DATA HELLO"
|
||||||
|
}'
|
||||||
|
|
||||||
|
curl -XPOST http://192.168.3.8:5001/api/v1/orgs/6a6a99f5-6630-4f91-88ff-571c9f030ea0/get_cache -H "Authorization: Bearer e663cf93-7f10-4560-bef0-303f14aad982" -d '{
|
||||||
|
"workflow_id": "61825389-a125-43a5-9119-97c401e9934b",
|
||||||
|
"execution_id": "2c8ca1b7-6658-4742-86a1-105c9467702d",
|
||||||
|
"org_id": "6a6a99f5-6630-4f91-88ff-571c9f030ea0",
|
||||||
|
"key": "test"
|
||||||
|
}'
|
||||||
|
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:Testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
|
#curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz
|
||||||
|
#curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -O -J
|
||||||
|
#curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz
|
||||||
|
curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:shuffle-tools_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
curl -XPOST -v localhost:5001/api/v1/migrate_database -H 'Authorization: Bearer 0184d7be-33c1-4391-bf9c-dfb8508a4ea2'
|
||||||
|
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
def parse_nested_param(string, level):
|
||||||
|
"""
|
||||||
|
Generate strings contained in nested (), indexing i = level
|
||||||
|
"""
|
||||||
|
if len(re.findall("\(", string)) == len(re.findall("\)", string)):
|
||||||
|
LeftRightIndex = [x for x in zip(
|
||||||
|
[Left.start()+1 for Left in re.finditer('\(', string)],
|
||||||
|
reversed([Right.start() for Right in re.finditer('\)', string)]))]
|
||||||
|
|
||||||
|
elif len(re.findall("\(", string)) > len(re.findall("\)", string)):
|
||||||
|
return parse_nested_param(string + ')', level)
|
||||||
|
elif len(re.findall("\(", string)) < len(re.findall("\)", string)):
|
||||||
|
return parse_nested_param('(' + string, level)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return 'Failed to parse params'
|
||||||
|
|
||||||
|
try:
|
||||||
|
return [string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]]
|
||||||
|
except IndexError:
|
||||||
|
return [string[LeftRightIndex[level+1][0]:LeftRightIndex[level+1][1]]]
|
||||||
|
|
||||||
|
# Parses the deepest part
|
||||||
|
def maxDepth(S):
|
||||||
|
current_max = 0
|
||||||
|
max = 0
|
||||||
|
n = len(S)
|
||||||
|
|
||||||
|
# Traverse the input string
|
||||||
|
for i in range(n):
|
||||||
|
if S[i] == '(':
|
||||||
|
current_max += 1
|
||||||
|
|
||||||
|
if current_max > max:
|
||||||
|
max = current_max
|
||||||
|
elif S[i] == ')':
|
||||||
|
if current_max > 0:
|
||||||
|
current_max -= 1
|
||||||
|
else:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
# finally check for unbalanced string
|
||||||
|
if current_max != 0:
|
||||||
|
return -1
|
||||||
|
|
||||||
|
return max-1
|
||||||
|
|
||||||
|
def parse_type(data, thistype):
|
||||||
|
if data == None:
|
||||||
|
return "Empty"
|
||||||
|
|
||||||
|
if "int" in thistype:
|
||||||
|
try:
|
||||||
|
return int(data)
|
||||||
|
except ValueError:
|
||||||
|
print("ValueError while casting %s" % data)
|
||||||
|
return data
|
||||||
|
if "lower" in thistype:
|
||||||
|
return data.lower()
|
||||||
|
if "upper" in thistype:
|
||||||
|
return data.upper()
|
||||||
|
if "trim" in thistype:
|
||||||
|
return data.strip()
|
||||||
|
if "strip" in thistype:
|
||||||
|
return data.strip()
|
||||||
|
if "split" in thistype:
|
||||||
|
# Should be able to split anything
|
||||||
|
return data.split()
|
||||||
|
if "len" in thistype or "length" in thistype:
|
||||||
|
return len(data)
|
||||||
|
if "parse" in thistype:
|
||||||
|
splitvalues = []
|
||||||
|
default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)"""
|
||||||
|
if "," in data:
|
||||||
|
splitvalues = data.split(",")
|
||||||
|
|
||||||
|
for item in range(len(splitvalues)):
|
||||||
|
splitvalues[item] = splitvalues[item].strip()
|
||||||
|
else:
|
||||||
|
return default_error
|
||||||
|
|
||||||
|
lastsplit = []
|
||||||
|
if ":" in splitvalues[-1]:
|
||||||
|
lastsplit = splitvalues[-1].split(":")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
lastsplit = [int(splitvalues[-1])]
|
||||||
|
except ValueError:
|
||||||
|
return default_error
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsedlist = ",".join(splitvalues[0:-1])
|
||||||
|
print(parsedlist)
|
||||||
|
print(lastsplit)
|
||||||
|
|
||||||
|
if len(lastsplit) > 1:
|
||||||
|
tmp = json.loads(parsedlist)[int(lastsplit[0]):int(lastsplit[1])]
|
||||||
|
else:
|
||||||
|
tmp = json.loads(parsedlist)[lastsplit[0]]
|
||||||
|
|
||||||
|
print(tmp)
|
||||||
|
return tmp
|
||||||
|
except IndexError as e:
|
||||||
|
return default_error
|
||||||
|
|
||||||
|
# Parses the INNER value and recurses until everything is done
|
||||||
|
def parse_wrapper(data):
|
||||||
|
try:
|
||||||
|
if "(" not in data or ")" not in data:
|
||||||
|
return data
|
||||||
|
except TypeError:
|
||||||
|
return data
|
||||||
|
|
||||||
|
print("Running %s" % data)
|
||||||
|
|
||||||
|
# Look for the INNER wrapper first, then move out
|
||||||
|
wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length"]
|
||||||
|
found = False
|
||||||
|
for wrapper in wrappers:
|
||||||
|
if wrapper not in data.lower():
|
||||||
|
continue
|
||||||
|
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Do stuff here.
|
||||||
|
innervalue = parse_nested_param(data, maxDepth(data)-0)
|
||||||
|
outervalue = parse_nested_param(data, maxDepth(data)-1)
|
||||||
|
print("INNER: ", outervalue)
|
||||||
|
print("OUTER: ", outervalue)
|
||||||
|
|
||||||
|
if outervalue != innervalue:
|
||||||
|
#print("Outer: ", outervalue, " inner: ", innervalue)
|
||||||
|
for key in range(len(innervalue)):
|
||||||
|
# Replace OUTERVALUE[key] with INNERVALUE[key] in data.
|
||||||
|
print("Replace %s with %s in %s" % (outervalue[key], innervalue[key], data))
|
||||||
|
data = data.replace(outervalue[key], innervalue[key])
|
||||||
|
else:
|
||||||
|
for thistype in wrappers:
|
||||||
|
if thistype not in data.lower():
|
||||||
|
continue
|
||||||
|
|
||||||
|
parsed_value = parse_type(innervalue[0], thistype)
|
||||||
|
return parsed_value
|
||||||
|
|
||||||
|
print("DATA: %s\n" % data)
|
||||||
|
return parse_wrapper(data)
|
||||||
|
|
||||||
|
def parse_wrapper_start(data):
|
||||||
|
newdata = []
|
||||||
|
newstring = ""
|
||||||
|
record = True
|
||||||
|
paranCnt = 0
|
||||||
|
for char in data:
|
||||||
|
if char == "(":
|
||||||
|
paranCnt += 1
|
||||||
|
|
||||||
|
if not record:
|
||||||
|
record = True
|
||||||
|
|
||||||
|
if record:
|
||||||
|
newstring += char
|
||||||
|
|
||||||
|
if paranCnt == 0 and char == " ":
|
||||||
|
newdata.append(newstring)
|
||||||
|
newstring = ""
|
||||||
|
record = True
|
||||||
|
|
||||||
|
if char == ")":
|
||||||
|
paranCnt -= 1
|
||||||
|
|
||||||
|
if paranCnt == 0:
|
||||||
|
record = False
|
||||||
|
|
||||||
|
if len(newstring) > 0:
|
||||||
|
newdata.append(newstring)
|
||||||
|
|
||||||
|
parsedlist = []
|
||||||
|
non_string = False
|
||||||
|
for item in newdata:
|
||||||
|
ret = parse_wrapper(item)
|
||||||
|
if not isinstance(ret, str):
|
||||||
|
non_string = True
|
||||||
|
|
||||||
|
parsedlist.append(ret)
|
||||||
|
|
||||||
|
if len(parsedlist) > 0 and not non_string:
|
||||||
|
return " ".join(parsedlist)
|
||||||
|
elif len(parsedlist) == 1 and non_string:
|
||||||
|
return parsedlist[0]
|
||||||
|
else:
|
||||||
|
print("Casting back to string because multi: ", parsedlist)
|
||||||
|
newlist = []
|
||||||
|
for item in parsedlist:
|
||||||
|
try:
|
||||||
|
newlist.append(str(item))
|
||||||
|
except ValueError:
|
||||||
|
newlist.append("parsing_error")
|
||||||
|
return " ".join(newlist)
|
||||||
|
|
||||||
|
data = "split(hello there)"
|
||||||
|
data = """parse(["testing", "what", "is this"], 0:2)"""
|
||||||
|
#data = "int(int(2))"
|
||||||
|
print("RET: ", parse_wrapper_start(data))
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ExecutionOrg MUST be executing.
|
||||||
|
curl -XPOST http://localhost:5001/api/v1/orgs/b199646b-16d2-456d-9fd6-b9972e929466/validate_app_values -d '{
|
||||||
|
"append": true,
|
||||||
|
"workflow_check": true,
|
||||||
|
"authorization": "1aae630c-ccaf-4cb5-87f9-8a9e0a9afd11",
|
||||||
|
"execution_ref": "c59ff288-4f02-4d02-b839-133d55c7fdf0",
|
||||||
|
"org_id": "b199646b-16d2-456d-9fd6-b9972e929466",
|
||||||
|
"values": [{
|
||||||
|
"app": "testing",
|
||||||
|
"action": "repeat_back_to_me",
|
||||||
|
"parameternames": ["call"],
|
||||||
|
"parametervalues": ["hey", "ho", "lets", "go"]
|
||||||
|
}]
|
||||||
|
}'
|
||||||
+55
-30
@@ -2,7 +2,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
#build: ./frontend
|
#build: ./frontend
|
||||||
image: ghcr.io/frikky/shuffle-frontend:0.8.71
|
image: ghcr.io/frikky/shuffle-frontend:nightly
|
||||||
container_name: shuffle-frontend
|
container_name: shuffle-frontend
|
||||||
hostname: shuffle-frontend
|
hostname: shuffle-frontend
|
||||||
ports:
|
ports:
|
||||||
@@ -17,7 +17,7 @@ services:
|
|||||||
- backend
|
- backend
|
||||||
backend:
|
backend:
|
||||||
#build: ./backend
|
#build: ./backend
|
||||||
image: ghcr.io/frikky/shuffle-backend:0.8.71
|
image: ghcr.io/frikky/shuffle-backend:nightly
|
||||||
container_name: shuffle-backend
|
container_name: shuffle-backend
|
||||||
hostname: ${BACKEND_HOSTNAME}
|
hostname: ${BACKEND_HOSTNAME}
|
||||||
# Here for debugging:
|
# Here for debugging:
|
||||||
@@ -29,25 +29,18 @@ services:
|
|||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ${SHUFFLE_APP_HOTLOAD_LOCATION}:/shuffle-apps
|
- ${SHUFFLE_APP_HOTLOAD_LOCATION}:/shuffle-apps
|
||||||
- ${SHUFFLE_FILE_LOCATION}:/shuffle-files
|
- ${SHUFFLE_FILE_LOCATION}:/shuffle-files
|
||||||
|
#- ${SHUFFLE_OPENSEARCH_CERTIFICATE_FILE}:/shuffle-files/es_certificate
|
||||||
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
- DATASTORE_EMULATOR_HOST=shuffle-database:8000
|
|
||||||
- SHUFFLE_APP_HOTLOAD_FOLDER=/shuffle-apps
|
- SHUFFLE_APP_HOTLOAD_FOLDER=/shuffle-apps
|
||||||
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
- SHUFFLE_FILE_LOCATION=/shuffle-files
|
||||||
- ORG_ID=${ORG_ID}
|
|
||||||
- SHUFFLE_APP_DOWNLOAD_LOCATION=${SHUFFLE_APP_DOWNLOAD_LOCATION}
|
|
||||||
- SHUFFLE_DOWNLOAD_AUTH_BRANCH=${SHUFFLE_DOWNLOAD_AUTH_BRANCH}
|
|
||||||
- SHUFFLE_DEFAULT_USERNAME=${SHUFFLE_DEFAULT_USERNAME}
|
|
||||||
- SHUFFLE_DEFAULT_PASSWORD=${SHUFFLE_DEFAULT_PASSWORD}
|
|
||||||
- SHUFFLE_DEFAULT_APIKEY=${SHUFFLE_DEFAULT_APIKEY}
|
|
||||||
- SHUFFLE_APP_FORCE_UPDATE=${SHUFFLE_APP_FORCE_UPDATE}
|
|
||||||
- HTTP_PROXY=${SHUFFLE_HTTP_PROXY}
|
|
||||||
- HTTPS_PROXY=${SHUFFLE_HTTPS_PROXY}
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
#depends_on:
|
||||||
- database
|
#- opensearch
|
||||||
|
#- database
|
||||||
orborus:
|
orborus:
|
||||||
#build: ./functions/onprem/orborus
|
#build: ./functions/onprem/orborus
|
||||||
image: ghcr.io/frikky/shuffle-orborus:0.8.71
|
image: ghcr.io/frikky/shuffle-orborus:nightly
|
||||||
container_name: shuffle-orborus
|
container_name: shuffle-orborus
|
||||||
hostname: shuffle-orborus
|
hostname: shuffle-orborus
|
||||||
networks:
|
networks:
|
||||||
@@ -55,33 +48,65 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
- SHUFFLE_APP_SDK_VERSION=0.8.60
|
- SHUFFLE_APP_SDK_VERSION=0.8.97
|
||||||
- SHUFFLE_WORKER_VERSION=0.8.71
|
- SHUFFLE_WORKER_VERSION=nightly
|
||||||
- ORG_ID=${ORG_ID}
|
- ORG_ID=${ORG_ID}
|
||||||
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
|
||||||
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
|
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
|
||||||
- DOCKER_API_VERSION=1.40
|
- DOCKER_API_VERSION=1.35
|
||||||
- HTTP_PROXY=${SHUFFLE_HTTP_PROXY}
|
|
||||||
- HTTPS_PROXY=${SHUFFLE_HTTPS_PROXY}
|
|
||||||
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
|
||||||
- SHUFFLE_ORBORUS_EXECUTION_TIMEOUT=600
|
|
||||||
- SHUFFLE_BASE_IMAGE_NAME=${SHUFFLE_BASE_IMAGE_NAME}
|
- SHUFFLE_BASE_IMAGE_NAME=${SHUFFLE_BASE_IMAGE_NAME}
|
||||||
- SHUFFLE_BASE_IMAGE_REGISTRY=${SHUFFLE_BASE_IMAGE_REGISTRY}
|
- SHUFFLE_BASE_IMAGE_REGISTRY=${SHUFFLE_BASE_IMAGE_REGISTRY}
|
||||||
- SHUFFLE_BASE_IMAGE_TAG_SUFFIX=${SHUFFLE_BASE_IMAGE_TAG_SUFFIX}
|
- SHUFFLE_BASE_IMAGE_TAG_SUFFIX=${SHUFFLE_BASE_IMAGE_TAG_SUFFIX}
|
||||||
|
- HTTP_PROXY=${HTTP_PROXY}
|
||||||
|
- HTTPS_PROXY=${HTTPS_PROXY}
|
||||||
|
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
|
||||||
|
- SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY}
|
||||||
|
- SHUFFLE_ORBORUS_EXECUTION_TIMEOUT=600
|
||||||
|
- SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=50
|
||||||
- CLEANUP=${SHUFFLE_CONTAINER_AUTO_CLEANUP}
|
- CLEANUP=${SHUFFLE_CONTAINER_AUTO_CLEANUP}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
database:
|
opensearch:
|
||||||
#build: ./backend/database
|
image: opensearchproject/opensearch:1.0.0-beta1
|
||||||
image: frikky/shuffle:database
|
hostname: shuffle-opensearch
|
||||||
container_name: shuffle-database
|
container_name: shuffle-opensearch
|
||||||
hostname: shuffle-database
|
environment:
|
||||||
|
- bootstrap.memory_lock=true
|
||||||
|
- "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
|
||||||
|
- opendistro_security.disabled=true
|
||||||
|
- cluster.routing.allocation.disk.threshold_enabled=false
|
||||||
|
- cluster.name=shuffle-cluster
|
||||||
|
- node.name=shuffle-opensearch
|
||||||
|
- discovery.seed_hosts=shuffle-opensearch
|
||||||
|
- cluster.initial_master_nodes=shuffle-opensearch
|
||||||
|
ulimits:
|
||||||
|
memlock:
|
||||||
|
soft: -1
|
||||||
|
hard: -1
|
||||||
|
nofile:
|
||||||
|
soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
|
||||||
|
hard: 65536
|
||||||
|
volumes:
|
||||||
|
- ${DB_LOCATION}:/usr/share/opensearch/data:rw
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- 9200:9200
|
||||||
networks:
|
networks:
|
||||||
- shuffle
|
- shuffle
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
#database:
|
||||||
- ${DB_LOCATION}:/etc/shuffle
|
# #build: ./backend/database
|
||||||
|
# image: frikky/shuffle:database
|
||||||
|
# container_name: shuffle-database
|
||||||
|
# hostname: shuffle-database
|
||||||
|
# ports:
|
||||||
|
# - "8000:8000"
|
||||||
|
# networks:
|
||||||
|
# - shuffle
|
||||||
|
# environment:
|
||||||
|
# - _JAVA_OPTIONS="-Xmx2g"
|
||||||
|
# restart: unless-stopped
|
||||||
|
# volumes:
|
||||||
|
# - ${DB_LOCATION}:/etc/shuffle
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
shuffle:
|
shuffle:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ COPY ./*.json /usr/src/app/
|
|||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
# Production environment
|
# Production environment
|
||||||
FROM nginx:1.19
|
FROM nginx:1.21
|
||||||
|
|
||||||
RUN mkdir -p /usr/share/nginx/html/build
|
RUN mkdir -p /usr/share/nginx/html/build
|
||||||
RUN mkdir -p /usr/share/nginx/html/css
|
RUN mkdir -p /usr/share/nginx/html/css
|
||||||
|
|||||||
@@ -28,6 +28,28 @@ http {
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name "localhost";
|
server_name "localhost";
|
||||||
|
|
||||||
|
#location /static/js/* {
|
||||||
|
# # avoid clickjacking
|
||||||
|
# add_header X-Frame-Options DENY;
|
||||||
|
# add_header X-Content-Type-Options nosniff;
|
||||||
|
# add_header ;
|
||||||
|
# # block MIME sniffing
|
||||||
|
|
||||||
|
# # security headers
|
||||||
|
# add_header X-XSS-Protection "1; mode=block";
|
||||||
|
# # add_header Content-Security-Policy "default-src 'self'";
|
||||||
|
# add_header Referrer-Policy "no-referrer";
|
||||||
|
# server_tokens off;
|
||||||
|
|
||||||
|
# root /usr/share/nginx/html;
|
||||||
|
# gzip_static on;
|
||||||
|
# expires 1y;
|
||||||
|
# add_header Cache-Control public;
|
||||||
|
# add_header ETag "";
|
||||||
|
# try_files $uri /index.html;
|
||||||
|
#}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
# avoid clickjacking
|
# avoid clickjacking
|
||||||
add_header X-Frame-Options DENY;
|
add_header X-Frame-Options DENY;
|
||||||
|
|||||||
Generated
-20920
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "shuffler",
|
"name": "shuffler",
|
||||||
"homepage": "https://shuffler.io",
|
"homepage": "https://shuffler.io",
|
||||||
"version": "0.8.53",
|
"version": "0.8.92",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.5.2",
|
"@material-ui/core": "^4.5.2",
|
||||||
"@material-ui/data-grid": "^4.0.0-alpha.22",
|
"@material-ui/data-grid": "^4.0.0-alpha.22",
|
||||||
"@material-ui/icons": "^4.5.1",
|
"@material-ui/icons": "^4.5.1",
|
||||||
|
"@material-ui/lab": "^4.0.0-alpha.58",
|
||||||
"@material-ui/styles": "^4.5.2",
|
"@material-ui/styles": "^4.5.2",
|
||||||
"@use-it/interval": "^1.0.0",
|
"@use-it/interval": "^1.0.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ const App = (message, props) => {
|
|||||||
<Route exact path="/webhooks/:key" render={props => <EditWebhook isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/webhooks/:key" render={props => <EditWebhook isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/schedules" render={props => <Schedules globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/schedules" render={props => <Schedules globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/dashboard" render={props => <Dashboard isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/dashboard" render={props => <Dashboard isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/apps" render={props => <Apps isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} userdata={userdata} {...props} />} />
|
|
||||||
<Route exact path="/apps/new" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/apps/new" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||||
|
<Route exact path="/apps" render={props => <Apps isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} userdata={userdata} {...props} />} />
|
||||||
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/apps/edit/:appid" render={props => <AppCreator isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/schedules/:key" render={props => <EditSchedule globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/workflows" render={props => <Workflows cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} userdata={userdata} {...props} />} />
|
<Route exact path="/workflows" render={props => <Workflows cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} userdata={userdata} {...props} />} />
|
||||||
@@ -125,7 +125,6 @@ const App = (message, props) => {
|
|||||||
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
|
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
|
||||||
<Route exact path="/introduction" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/introduction" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/introduction/:key" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
<Route exact path="/introduction/:key" render={props => <Introduction isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
|
||||||
<Route exact path="/myview" render={props => <MyView cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} userdata={userdata} {...props} />} />
|
|
||||||
<Route exact path="/" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
<Route exact path="/" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 449 KiB |
@@ -1,13 +1,26 @@
|
|||||||
import React, {useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
|
|
||||||
import {Typography, } from '@material-ui/core';
|
import { InputAdornment, Tooltip, TextField, CircularProgress, ButtonGroup, Button, Avatar, ListItemAvatar, Typography, List, ListItem, ListItemText} from '@material-ui/core';
|
||||||
|
import {FavoriteBorder as FavoriteBorderIcon} from '@material-ui/icons';
|
||||||
|
import { FixName } from "../views/Apps.jsx";
|
||||||
|
|
||||||
|
// Handles workflow updates on first open to highlight the issues of the workflow
|
||||||
|
// Variables
|
||||||
|
// Action (exists, missing fields)
|
||||||
|
// Action auth
|
||||||
|
// Triggers
|
||||||
|
//
|
||||||
|
// Specifically used for UNSAVED workflows only?
|
||||||
const Workflow = (props) => {
|
const Workflow = (props) => {
|
||||||
const { workflow, appAuthentication, apps } = props
|
const { globalUrl, theme, workflow, appAuthentication, setSelectedAction, setAuthenticationModalOpen, setSelectedApp, apps, selectedAction,setConfigureWorkflowModalOpen, saveWorkflow, newWebhook, submitSchedule, referenceUrl, isCloud, } = props
|
||||||
const [requiredActions, setRequiredActions] = React.useState([])
|
const [requiredActions, setRequiredActions] = React.useState([])
|
||||||
|
const [requiredVariables, setRequiredVariables] = React.useState([])
|
||||||
|
const [requiredTriggers, setRequiredTriggers] = React.useState([])
|
||||||
|
const [previousAuth, setPreviousAuth] = React.useState(appAuthentication)
|
||||||
const [firstLoad, setFirstLoad] = React.useState("")
|
const [firstLoad, setFirstLoad] = React.useState("")
|
||||||
|
const [itemChanged, setItemChanged] = React.useState(false)
|
||||||
// Rofl
|
var finished = false
|
||||||
|
|
||||||
if (workflow === undefined || workflow === null) {
|
if (workflow === undefined || workflow === null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -20,76 +33,436 @@ const Workflow = (props) => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getApp = (actionId, appId) => {
|
||||||
|
fetch(globalUrl+"/api/v1/apps/"+appId+"/config?openapi=false", {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
//alert.success("Successfully GOT app "+appId)
|
||||||
|
} else {
|
||||||
|
alert.error("Failed getting app")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
console.log("ACTION: ", responseJson)
|
||||||
|
if (responseJson.actions !== undefined && responseJson.actions !== null) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert.error(error.toString())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (firstLoad.length === 0 || firstLoad !== workflow.id) {
|
if (firstLoad.length === 0 || firstLoad !== workflow.id) {
|
||||||
|
if (finished) {
|
||||||
|
setConfigureWorkflowModalOpen(false)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apps === undefined || apps === null || apps.length === 0) {
|
||||||
|
console.log("No apps loaded: ", apps)
|
||||||
|
setConfigureWorkflowModalOpen(false)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
setFirstLoad(workflow.id)
|
setFirstLoad(workflow.id)
|
||||||
const newactions = []
|
const newactions = []
|
||||||
for (var key in workflow.actions) {
|
for (var key in workflow.actions) {
|
||||||
|
const action = workflow.actions[key]
|
||||||
var newaction = {
|
var newaction = {
|
||||||
"large_image": "",
|
"large_image": action.large_image,
|
||||||
"app_name": "",
|
"app_name": action.app_name,
|
||||||
"app_version": "",
|
"app_version": action.app_version,
|
||||||
|
"activation_done": false,
|
||||||
"must_activate": false,
|
"must_activate": false,
|
||||||
"must_authenticate": false,
|
"must_authenticate": false,
|
||||||
|
"auth_done": false,
|
||||||
"action_ids": [],
|
"action_ids": [],
|
||||||
|
"action": action,
|
||||||
|
"app": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = workflow.actions[key]
|
const app = apps.find(app => app.name === action.app_name && (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version))))
|
||||||
console.log(action)
|
|
||||||
const app = apps.find(app => app.name === action.app_name && app.app_version === action.app_version)
|
|
||||||
if (app === undefined || app === null) {
|
if (app === undefined || app === null) {
|
||||||
console.log("COULDNT FIND APP - SEARCH BACKEND")
|
console.log("App not found!")
|
||||||
|
|
||||||
newaction.app_name = action.app_name
|
newaction.must_activate = true
|
||||||
newaction.app_version = action.app_version
|
|
||||||
} else {
|
} else {
|
||||||
newaction.app_name = app.name
|
|
||||||
newaction.app_version = app.app_version
|
|
||||||
|
|
||||||
console.log("APP: ", app)
|
|
||||||
if (action.authentication_id === "" && app.authentication.required === true) {
|
if (action.authentication_id === "" && app.authentication.required === true) {
|
||||||
console.log("Requires auth!")
|
// Check if configuration is filled or not
|
||||||
newaction.must_authenticate = true
|
var filled = true
|
||||||
newaction.action_ids.push(action.id)
|
for (var key in action.parameters) {
|
||||||
|
if (action.parameters[key].configuration) {
|
||||||
|
//console.log("Found config: ", action.parameters[key])
|
||||||
|
if (action.parameters[key].value === null || action.parameters[key].value.length === 0) {
|
||||||
|
filled = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filled) {
|
||||||
|
newaction.must_authenticate = true
|
||||||
|
newaction.action_ids.push(action.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//newaction.app_name = action.app_name
|
newaction.app = app
|
||||||
//newaction.app_name = action.app_version
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.errors !== undefined && action.errors !== null && action.errors.length > 0) {
|
if (action.errors !== undefined && action.errors !== null && action.errors.length > 0) {
|
||||||
console.log("Has errors!")
|
//console.log("Node has errors!: ", action.errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("NEWACTION: ", newaction)
|
if (newaction.must_authenticate) {
|
||||||
if (newaction.must_authenticate || newaction.must_activate) {
|
var authenticationOptions = []
|
||||||
newactions.push(newaction)
|
for (var key in appAuthentication) {
|
||||||
|
const auth = appAuthentication[key]
|
||||||
|
if (auth.app.name === app.name && auth.active) {
|
||||||
|
//console.log("Found auth: ", auth)
|
||||||
|
authenticationOptions.push(auth)
|
||||||
|
newaction.authenticationId = auth.id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newaction.authenticationId === null || newaction.authenticationId === undefined || newaction.authenticationId.length === "") {
|
||||||
|
//console.log("FAILED to authenticate node!")
|
||||||
|
|
||||||
|
if (newactions.find(tmpaction => tmpaction.app_id === newaction.app_id && tmpaction.app_name === newaction.app_name) !== undefined) {
|
||||||
|
console.log("Action already found.")
|
||||||
|
} else {
|
||||||
|
newactions.push(newaction)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//console.log("Skipping node as it's already authenticated.")
|
||||||
|
newaction.authentication = authenticationOptions
|
||||||
|
workflow.actions[key] = newaction
|
||||||
|
}
|
||||||
|
} else if (newaction.must_activate) {
|
||||||
|
|
||||||
|
if (newactions.find(tmpaction => tmpaction.app_id === newaction.app_id && tmpaction.app_name === newaction.app_name) !== undefined) {
|
||||||
|
console.log("Action already found.")
|
||||||
|
} else {
|
||||||
|
newactions.push(newaction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("ACTIONS: ", newactions)
|
for (var key in workflow.workflow_variables) {
|
||||||
|
const variable = workflow.workflow_variables[key]
|
||||||
|
if (variable.value === undefined || variable.value === undefined || variable.value.length < 2) {
|
||||||
|
variable.value = ""
|
||||||
|
variable.index = key
|
||||||
|
requiredVariables.push(variable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in workflow.triggers) {
|
||||||
|
var trigger = workflow.triggers[key]
|
||||||
|
trigger.index = key
|
||||||
|
|
||||||
|
if (trigger.status === "running") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trigger.trigger_type === "SUBFLOW" || trigger.trigger_type === "USERINPUT") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredTriggers.push(trigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredTriggers.length === 0 && requiredVariables.length === 0 && newactions.length === 0) {
|
||||||
|
setConfigureWorkflowModalOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequiredTriggers(requiredTriggers)
|
||||||
|
setRequiredVariables(requiredVariables)
|
||||||
setRequiredActions(newactions)
|
setRequiredActions(newactions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appAuthentication.length !== previousAuth.length) {
|
||||||
|
var newactions = []
|
||||||
|
for (var actionkey in requiredActions) {
|
||||||
|
var newaction = requiredActions[actionkey]
|
||||||
|
const app = newaction.app
|
||||||
|
|
||||||
|
for (var key in appAuthentication) {
|
||||||
|
const auth = appAuthentication[key]
|
||||||
|
if (auth.app.name === app.name && auth.active) {
|
||||||
|
newaction.auth_done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newactions.push(newaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequiredActions(newactions)
|
||||||
|
setPreviousAuth(appAuthentication)
|
||||||
|
// Set auth done to true
|
||||||
|
//"auth_done": false
|
||||||
|
}
|
||||||
|
|
||||||
|
const TriggerSection = (props) => {
|
||||||
|
const {trigger} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar variant="rounded">
|
||||||
|
<img alt={trigger.label} src={trigger.large_image} style={{width: 50}} />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={trigger.name}
|
||||||
|
secondary={trigger.description}
|
||||||
|
style={{}}
|
||||||
|
/>
|
||||||
|
{trigger.trigger_type === "WEBHOOK" && trigger.status !== "running" ?
|
||||||
|
<Button disabled={trigger.status === "running"} color="primary" variant="contained" onClick={() => {
|
||||||
|
workflow.triggers[trigger.index].status = "running"
|
||||||
|
if (workflow.triggers[trigger.index].parameters === null) {
|
||||||
|
workflow.triggers[trigger.index].parameters = [
|
||||||
|
{"name": "url", "value": referenceUrl+"webhook_"+trigger.id},
|
||||||
|
{"name": "tmp", "value": "webhook_"+trigger.id},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
newWebhook(workflow.triggers[trigger.index])
|
||||||
|
saveWorkflow(workflow)
|
||||||
|
setItemChanged(true)
|
||||||
|
}}>
|
||||||
|
{trigger.status !== "running" ? "Start" : "Running"}
|
||||||
|
</Button>
|
||||||
|
:
|
||||||
|
trigger.trigger_type === "SCHEDULE" && trigger.status !== "running" ?
|
||||||
|
<Button disabled={trigger.status === "running"} color="primary" variant="contained" onClick={() => {
|
||||||
|
workflow.triggers[trigger.index].status = "running"
|
||||||
|
if (workflow.triggers[trigger.index].parameters === null) {
|
||||||
|
workflow.triggers[trigger.index].parameters = [
|
||||||
|
{"name": "cron", "value": isCloud ? "*/15 * * * *" : "120"},
|
||||||
|
{"name": "execution_argument", "value": '{"example": {"json": "is cool"}}'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
submitSchedule(workflow.triggers[trigger.index], trigger.index)
|
||||||
|
saveWorkflow(workflow)
|
||||||
|
setItemChanged(true)
|
||||||
|
}}>
|
||||||
|
{trigger.status !== "running" ? "Start" : "Running"}
|
||||||
|
</Button>
|
||||||
|
:
|
||||||
|
null}
|
||||||
|
{/*
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<TextField
|
||||||
|
style={{backgroundColor: theme.palette.inputColor, borderRadius: 5,}}
|
||||||
|
InputProps={{
|
||||||
|
style:{
|
||||||
|
color: "white",
|
||||||
|
minHeight: 50,
|
||||||
|
marginLeft: 5,
|
||||||
|
maxWidth: "95%",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
color="primary"
|
||||||
|
type={"text"}
|
||||||
|
placeholder={`New value for ${trigger.name}`}
|
||||||
|
onChange={(event) => {
|
||||||
|
console.log("NEW VALUE ON INDEX", trigger.value)
|
||||||
|
}}
|
||||||
|
onBlur={(event) => {
|
||||||
|
//workflow.variables[variable.index] = event.target.value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={{}}
|
||||||
|
/>
|
||||||
|
*/}
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const VariableSection = (props) => {
|
||||||
|
const {variable} = props
|
||||||
|
|
||||||
|
//<Typography variant="body2">Name: {variable.name} - {variable.value}. </Typography>
|
||||||
|
return (
|
||||||
|
<ListItem>
|
||||||
|
<ListItemAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<FavoriteBorderIcon />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={variable.name}
|
||||||
|
secondary={variable.description}
|
||||||
|
style={{}}
|
||||||
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<TextField
|
||||||
|
style={{backgroundColor: theme.palette.inputColor, borderRadius: 5,}}
|
||||||
|
InputProps={{
|
||||||
|
style:{
|
||||||
|
color: "white",
|
||||||
|
minHeight: 50,
|
||||||
|
marginLeft: 5,
|
||||||
|
maxWidth: "95%",
|
||||||
|
fontSize: "1em",
|
||||||
|
},
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
color="primary"
|
||||||
|
type={"text"}
|
||||||
|
placeholder={`New value for ${variable.name}`}
|
||||||
|
onChange={(event) => {
|
||||||
|
console.log("NEW VALUE ON INDEX", variable.index, variable.value)
|
||||||
|
}}
|
||||||
|
onBlur={(event) => {
|
||||||
|
workflow.workflow_variables[variable.index].value = event.target.value
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
style={{}}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const AppSection = (props) => {
|
const AppSection = (props) => {
|
||||||
const {action} = props
|
const {action} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<ListItem>
|
||||||
<Typography variant="body2">Name: {action.app_name}:{action.app_version}. </Typography>
|
<ListItemAvatar>
|
||||||
</div>
|
<Avatar variant="rounded">
|
||||||
|
<img alt={action.app_name} src={action.large_image} style={{width: 50}} />
|
||||||
|
</Avatar>
|
||||||
|
</ListItemAvatar>
|
||||||
|
<ListItemText
|
||||||
|
primary={FixName(action.app_name)}
|
||||||
|
secondary={action.app_version}
|
||||||
|
style={{}}
|
||||||
|
/>
|
||||||
|
{action.must_authenticate ?
|
||||||
|
action.auth_done ?
|
||||||
|
<Button color="primary" variant="outlined" onClick={() => {}}>
|
||||||
|
Authenticated
|
||||||
|
</Button>
|
||||||
|
:
|
||||||
|
selectedAction.app_name === action.app_name ?
|
||||||
|
<CircularProgress />
|
||||||
|
:
|
||||||
|
<Button color="primary" variant="contained" onClick={() => {
|
||||||
|
setItemChanged(true)
|
||||||
|
setSelectedAction(action.action)
|
||||||
|
setSelectedApp(action.app)
|
||||||
|
setAuthenticationModalOpen(true)
|
||||||
|
}}>
|
||||||
|
Authenticate
|
||||||
|
</Button>
|
||||||
|
:
|
||||||
|
null}
|
||||||
|
{action.must_activate ?
|
||||||
|
<Button disabled={true} color="primary" variant="contained" onClick={() => {
|
||||||
|
console.log("SHOULD ACTIVATE: ", action)
|
||||||
|
setItemChanged(true)
|
||||||
|
}}>
|
||||||
|
Activate
|
||||||
|
</Button>
|
||||||
|
: null}
|
||||||
|
</ListItem>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(requiredActions)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="h6">Workflow: {workflow.id}</Typography>
|
<Typography variant="h6">{workflow.name}</Typography>
|
||||||
{requiredActions.map((data, index) => {
|
<Typography variant="body1" color="textSecondary">
|
||||||
return (
|
The following configuration makes the workflow ready immediately.
|
||||||
<AppSection key={index} action={data} />
|
</Typography>
|
||||||
)
|
{requiredActions.length > 0 ?
|
||||||
})}
|
<span>
|
||||||
|
<Typography variant="body1" style={{marginTop: 10}}>Actions</Typography>
|
||||||
|
<List>
|
||||||
|
{requiredActions.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<AppSection key={index} action={data} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</span>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{requiredVariables.length > 0 ?
|
||||||
|
<span>
|
||||||
|
<Typography variant="body1" style={{marginTop: 10}}>Variables</Typography>
|
||||||
|
<List>
|
||||||
|
{requiredVariables.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<VariableSection key={index} variable={data} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</span>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
|
||||||
|
{requiredTriggers.length > 0 ?
|
||||||
|
<span>
|
||||||
|
<Typography variant="body1" style={{marginTop: 10}}>Triggers</Typography>
|
||||||
|
<List>
|
||||||
|
{requiredTriggers.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<TriggerSection key={index} trigger={data} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</span>
|
||||||
|
: null }
|
||||||
|
<div style={{textAlign: "center", display: "flex", marginTop: 20, }}>
|
||||||
|
<ButtonGroup style={{margin: "auto",}}>
|
||||||
|
{/*
|
||||||
|
<Button color="primary" variant={"outlined"} style={{
|
||||||
|
}} onClick={() => {
|
||||||
|
setConfigureWorkflowModalOpen(false)
|
||||||
|
}}>
|
||||||
|
Skip
|
||||||
|
</Button>
|
||||||
|
*/}
|
||||||
|
<Button color="primary" variant={"contained"} style={{
|
||||||
|
}} onClick={() => {
|
||||||
|
if (itemChanged) {
|
||||||
|
saveWorkflow(workflow)
|
||||||
|
window.location.reload()
|
||||||
|
} else {
|
||||||
|
setConfigureWorkflowModalOpen(false)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
Finish setup
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import React, {useState, useRef, useImperativeHandle} from 'react'
|
||||||
|
import {makeStyles} from '@material-ui/core/styles'
|
||||||
|
import Menu, {MenuProps} from '@material-ui/core/Menu'
|
||||||
|
import MenuItem, {MenuItemProps} from '@material-ui/core/MenuItem'
|
||||||
|
import ArrowRight from '@material-ui/icons/ArrowRight'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
//<MenuItemProps, 'button'>
|
||||||
|
|
||||||
|
//export interface NestedMenuItemProps {
|
||||||
|
// /**
|
||||||
|
// * Open state of parent `<Menu />`, used to close decendent menus when the
|
||||||
|
// * root menu is closed.
|
||||||
|
// */
|
||||||
|
// parentMenuOpen: boolean;
|
||||||
|
// /**
|
||||||
|
// * Component for the container element.
|
||||||
|
// * @default 'div'
|
||||||
|
// */
|
||||||
|
// component: React.ElementType;
|
||||||
|
// /**
|
||||||
|
// * Effectively becomes the `children` prop passed to the `<MenuItem/>`
|
||||||
|
// * element.
|
||||||
|
// */
|
||||||
|
// label: React.ReactNode;
|
||||||
|
// /**
|
||||||
|
// * @default <ArrowRight />
|
||||||
|
// */
|
||||||
|
// rightIcon: React.ReactNode;
|
||||||
|
// /**
|
||||||
|
// * Props passed to container element.
|
||||||
|
// */
|
||||||
|
// ContainerProps: React.HTMLAttributes;
|
||||||
|
// //<HTMLElement> &React.RefAttributes<HTMLElement | null>
|
||||||
|
// /**
|
||||||
|
// * Props passed to sub `<Menu/>` element
|
||||||
|
// */
|
||||||
|
// MenuProps: Omit<MenuProps, 'children'>;
|
||||||
|
// /**
|
||||||
|
// * @see https://material-ui.com/api/list-item/
|
||||||
|
// */
|
||||||
|
// button: true;
|
||||||
|
//}
|
||||||
|
|
||||||
|
const TRANSPARENT = 'rgba(0,0,0,0)'
|
||||||
|
const useMenuItemStyles = makeStyles((theme) => ({
|
||||||
|
root: (props: any) => ({
|
||||||
|
backgroundColor: props.open ? theme.palette.action.hover : TRANSPARENT
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use as a drop-in replacement for `<MenuItem>` when you need to add cascading
|
||||||
|
* menu elements as children to this component.
|
||||||
|
*/
|
||||||
|
//const NestedMenuItem = React.forwardRef<NestedMenuItemProps>(
|
||||||
|
const NestedMenuItem = (props, ref) => {
|
||||||
|
console.log(props, ref)
|
||||||
|
//function NestedMenuItem(props, ref) {
|
||||||
|
const {
|
||||||
|
parentMenuOpen,
|
||||||
|
component = 'div',
|
||||||
|
label,
|
||||||
|
rightIcon = <ArrowRight />,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
tabIndex: tabIndexProp,
|
||||||
|
MenuProps = {},
|
||||||
|
ContainerProps: ContainerPropsProp = {},
|
||||||
|
...MenuItemProps
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
|
||||||
|
|
||||||
|
const {ref: containerRefProp, ...ContainerProps} = ContainerPropsProp
|
||||||
|
|
||||||
|
|
||||||
|
const menuItemRef = useRef<HTMLLIElement>(null)
|
||||||
|
useImperativeHandle(ref, () => menuItemRef.current)
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
useImperativeHandle(containerRefProp, () => containerRef.current)
|
||||||
|
const menuContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
console.log("PAST THIS: ", containerRefProp, menuItemRef, containerRef, menuContainerRef, ContainerProps)
|
||||||
|
|
||||||
|
const handleMouseEnter = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setIsSubMenuOpen(true)
|
||||||
|
|
||||||
|
if (ContainerProps?.onMouseEnter) {
|
||||||
|
ContainerProps.onMouseEnter(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleMouseLeave = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setIsSubMenuOpen(false)
|
||||||
|
|
||||||
|
if (ContainerProps?.onMouseLeave) {
|
||||||
|
ContainerProps.onMouseLeave(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any immediate children are active
|
||||||
|
const isSubmenuFocused = () => {
|
||||||
|
const active = containerRef.current?.ownerDocument?.activeElement
|
||||||
|
for (const child of menuContainerRef.current?.children ?? []) {
|
||||||
|
if (child === active) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFocus = (event: React.FocusEvent<HTMLElement>) => {
|
||||||
|
if (event.target === containerRef.current) {
|
||||||
|
setIsSubMenuOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ContainerProps?.onFocus) {
|
||||||
|
ContainerProps.onFocus(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSubmenuFocused()) {
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
const active = containerRef.current?.ownerDocument?.activeElement
|
||||||
|
|
||||||
|
if (event.key === 'ArrowLeft' && isSubmenuFocused()) {
|
||||||
|
containerRef.current?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.key === 'ArrowRight' &&
|
||||||
|
event.target === containerRef.current &&
|
||||||
|
event.target === active
|
||||||
|
) {
|
||||||
|
console.log("MENU: ", menuContainerRef)
|
||||||
|
const firstChild = menuContainerRef.current.children[0]
|
||||||
|
console.log("FIRST: ", firstChild)
|
||||||
|
firstChild.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = isSubMenuOpen && parentMenuOpen
|
||||||
|
const menuItemClasses = useMenuItemStyles({open})
|
||||||
|
|
||||||
|
// Root element must have a `tabIndex` attribute for keyboard navigation
|
||||||
|
let tabIndex
|
||||||
|
if (!props.disabled) {
|
||||||
|
tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("PAST 2! ", tabIndex)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...ContainerProps}
|
||||||
|
ref={containerRef}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
{...MenuItemProps}
|
||||||
|
className={clsx(menuItemClasses.root, className)}
|
||||||
|
ref={menuItemRef}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
{rightIcon}
|
||||||
|
</MenuItem>
|
||||||
|
<Menu
|
||||||
|
// Set pointer events to 'none' to prevent the invisible Popover div
|
||||||
|
// from capturing events for clicks and hovers
|
||||||
|
style={{pointerEvents: 'none'}}
|
||||||
|
anchorEl={menuItemRef.current}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right'
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left'
|
||||||
|
}}
|
||||||
|
open={open}
|
||||||
|
autoFocus={false}
|
||||||
|
disableAutoFocus
|
||||||
|
disableEnforceFocus
|
||||||
|
onClose={() => {
|
||||||
|
setIsSubMenuOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div ref={menuContainerRef} style={{pointerEvents: 'auto'}}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NestedMenuItem
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ const data = [{
|
|||||||
'curve-style': 'unbundled-bezier',
|
'curve-style': 'unbundled-bezier',
|
||||||
'label': 'data(label)',
|
'label': 'data(label)',
|
||||||
'text-margin-y': '-15px',
|
'text-margin-y': '-15px',
|
||||||
'width': '2px',
|
'width': '5px',
|
||||||
"color": "white",
|
"color": "white",
|
||||||
"line-fill": "linear-gradient",
|
"line-fill": "linear-gradient",
|
||||||
"line-gradient-stop-positions": ["0.0", "100"],
|
"line-gradient-stop-positions": ["0.0", "100"],
|
||||||
@@ -40,6 +40,7 @@ const data = [{
|
|||||||
'background-width': '100%',
|
'background-width': '100%',
|
||||||
'background-height': '100%',
|
'background-height': '100%',
|
||||||
'border-radius': '5px',
|
'border-radius': '5px',
|
||||||
|
'z-index': 5001,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -47,13 +48,23 @@ const data = [{
|
|||||||
css: {
|
css: {
|
||||||
'width': '30px',
|
'width': '30px',
|
||||||
'height': '30px',
|
'height': '30px',
|
||||||
},
|
'z-index': 5000,
|
||||||
|
'font-size': '0px',
|
||||||
|
'background-width': '75%',
|
||||||
|
'background-height': '75%',
|
||||||
|
'background-color': 'data(iconBackground)',
|
||||||
|
'background-fill': 'data(fillstyle)',
|
||||||
|
'background-gradient-direction': 'to-right',
|
||||||
|
'background-gradient-stop-colors': 'data(fillGradient)',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: `node[app_name="Testing"]`,
|
selector: `node[app_name="Testing"]`,
|
||||||
css: {
|
css: {
|
||||||
'width': '30px',
|
'width': '30px',
|
||||||
'height': '30px',
|
'height': '30px',
|
||||||
|
'z-index': 5000,
|
||||||
|
'font-size': '0px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -112,6 +123,37 @@ const data = [{
|
|||||||
'background-color': '#edbd21',
|
'background-color': '#edbd21',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: 'node[?isButton]',
|
||||||
|
css: {
|
||||||
|
'shape': 'ellipse',
|
||||||
|
'width': '15px',
|
||||||
|
'height': '15px',
|
||||||
|
'z-index': '5002',
|
||||||
|
'font-size': '0px',
|
||||||
|
'border': '1px solid rgba(255,255,255,0.9)',
|
||||||
|
'background-image': 'data(icon)',
|
||||||
|
'background-color': 'data(iconBackground)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'node[?isDescriptor]',
|
||||||
|
css: {
|
||||||
|
'shape': 'ellipse',
|
||||||
|
'border-color': '#80deea',
|
||||||
|
'width': '5px',
|
||||||
|
'height': '5px',
|
||||||
|
'z-index': '5002',
|
||||||
|
'font-size': '10px',
|
||||||
|
'text-valign': 'center',
|
||||||
|
'text-halign': 'center',
|
||||||
|
'border': '1px solid black',
|
||||||
|
'margin-right': '0px',
|
||||||
|
'text-margin-x': '0px',
|
||||||
|
'background-color': 'data(imageColor)',
|
||||||
|
'background-image': 'data(image)',
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
selector: 'node[?isStartNode]',
|
selector: 'node[?isStartNode]',
|
||||||
css: {
|
css: {
|
||||||
@@ -119,6 +161,9 @@ const data = [{
|
|||||||
'border-color': '#80deea',
|
'border-color': '#80deea',
|
||||||
'width': '80px',
|
'width': '80px',
|
||||||
'height': '80px',
|
'height': '80px',
|
||||||
|
'font-size': '18px',
|
||||||
|
'background-width': '100%',
|
||||||
|
'background-height': '100%',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -204,8 +249,9 @@ const data = [{
|
|||||||
'border-width': '12px',
|
'border-width': '12px',
|
||||||
'transition-property': 'border-width',
|
'transition-property': 'border-width',
|
||||||
'transition-duration': '0.25s',
|
'transition-duration': '0.25s',
|
||||||
'font-size': '30px',
|
|
||||||
'label': 'data(label)',
|
'label': 'data(label)',
|
||||||
|
'font-size': '18px',
|
||||||
|
'color': 'white',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -227,6 +273,17 @@ const data = [{
|
|||||||
'transition-duration': '0.25s',
|
'transition-duration': '0.25s',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: `edge[?decorator]`,
|
||||||
|
css: {
|
||||||
|
'width': '1px',
|
||||||
|
'line-style': 'dashed',
|
||||||
|
"line-fill": "linear-gradient",
|
||||||
|
'target-arrow-color': '#f34079',
|
||||||
|
"line-gradient-stop-positions": ["0.0", "100"],
|
||||||
|
"line-gradient-stop-colors": ["#f86a3e", "#f34079"],
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
selector: 'edge.success-highlight',
|
selector: 'edge.success-highlight',
|
||||||
css: {
|
css: {
|
||||||
@@ -240,17 +297,6 @@ const data = [{
|
|||||||
"line-gradient-stop-colors": ["#41dcab", "#41dcab"],
|
"line-gradient-stop-colors": ["#41dcab", "#41dcab"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
selector: 'edge[?hasErrors]',
|
|
||||||
css: {
|
|
||||||
'target-arrow-color': '#991818',
|
|
||||||
'line-color': '#991818',
|
|
||||||
'line-style': 'dashed',
|
|
||||||
"line-fill": "linear-gradient",
|
|
||||||
"line-gradient-stop-positions": ["0.0", "100"],
|
|
||||||
"line-gradient-stop-colors": ["#991818", "#991818"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
selector: '.eh-handle',
|
selector: '.eh-handle',
|
||||||
style: {
|
style: {
|
||||||
@@ -290,7 +336,34 @@ const data = [{
|
|||||||
css: {
|
css: {
|
||||||
'target-arrow-color': '#f85a3e',
|
'target-arrow-color': '#f85a3e',
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
selector: `edge[?source_workflow]`,
|
||||||
|
css: {
|
||||||
|
"background-opacity": "1",
|
||||||
|
'font-size': '0px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: `node[?source_workflow]`,
|
||||||
|
css: {
|
||||||
|
"background-opacity": "0",
|
||||||
|
'font-size': '0px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
//{
|
||||||
|
// selector: 'edge[?hasErrors]',
|
||||||
|
// css: {
|
||||||
|
// 'target-arrow-color': '#991818',
|
||||||
|
// 'line-color': '#991818',
|
||||||
|
// 'line-style': 'dashed',
|
||||||
|
// "line-fill": "linear-gradient",
|
||||||
|
// "line-gradient-stop-positions": ["0.0", "100"],
|
||||||
|
// "line-gradient-stop-colors": ["#991818", "#991818"],
|
||||||
|
// },
|
||||||
|
//},
|
||||||
|
|
||||||
|
|
||||||
export default data
|
export default data
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ const theme = createMuiTheme({
|
|||||||
maxWidth: "95%",
|
maxWidth: "95%",
|
||||||
fontSize: "1em",
|
fontSize: "1em",
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
}
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: "white",
|
||||||
|
color: 'rgba(0, 0, 0, 0.87)',
|
||||||
|
boxShadow: 1,
|
||||||
|
fontSize: 11,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
"fontFamily": `"Roboto", "Helvetica", "Arial", sans-serif`,
|
"fontFamily": `"Roboto", "Helvetica", "Arial", sans-serif`,
|
||||||
|
|||||||
+206
-125
@@ -66,7 +66,7 @@ const Admin = (props) => {
|
|||||||
|
|
||||||
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
|
||||||
const getApps = () => {
|
const getApps = () => {
|
||||||
fetch(globalUrl+"/api/v1/workflows/apps", {
|
fetch(globalUrl+"/api/v1/apps", {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -149,9 +149,12 @@ const Admin = (props) => {
|
|||||||
response.json().then(responseJson => {
|
response.json().then(responseJson => {
|
||||||
console.log("RESP: ", responseJson)
|
console.log("RESP: ", responseJson)
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
alert.error("Failed stopping schedule")
|
alert.error("Failed deleting auth")
|
||||||
} else {
|
} else {
|
||||||
getAppAuthentication()
|
// Need to wait because query in ES is too fast
|
||||||
|
setTimeout(() => {
|
||||||
|
getAppAuthentication()
|
||||||
|
}, 1000)
|
||||||
alert.success("Successfully deleted authentication!")
|
alert.success("Successfully deleted authentication!")
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -223,7 +226,9 @@ const Admin = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
handleGetOrg(org_id)
|
setTimeout(() => {
|
||||||
|
handleGetOrg(org_id)
|
||||||
|
}, 1000)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
alert.error("Err: " + error.toString())
|
alert.error("Err: " + error.toString())
|
||||||
@@ -260,9 +265,12 @@ const Admin = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
|
//setTimeout(() => {
|
||||||
|
//}, 1000)
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (!responseJson.success && responseJson.reason !== undefined) {
|
console.log("RESP: ", responseJson)
|
||||||
|
if (responseJson.success === false && responseJson.reason !== undefined) {
|
||||||
setOrgSyncResponse(responseJson.reason)
|
setOrgSyncResponse(responseJson.reason)
|
||||||
alert.error("Failed to handle sync: "+responseJson.reason)
|
alert.error("Failed to handle sync: "+responseJson.reason)
|
||||||
} else if (!responseJson.success) {
|
} else if (!responseJson.success) {
|
||||||
@@ -346,7 +354,9 @@ const Admin = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
alert.success("Successfully updated auth everywhere!")
|
alert.success("Successfully updated auth everywhere!")
|
||||||
setSelectedUserModalOpen(false)
|
setSelectedUserModalOpen(false)
|
||||||
getAppAuthentication()
|
setTimeout(() => {
|
||||||
|
getAppAuthentication()
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -373,7 +383,11 @@ const Admin = (props) => {
|
|||||||
.then(response =>
|
.then(response =>
|
||||||
response.json().then(responseJson => {
|
response.json().then(responseJson => {
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
alert.error("Failed setting new password")
|
if (responseJson.reason !== undefined) {
|
||||||
|
alert.error(responseJson.reason)
|
||||||
|
} else {
|
||||||
|
alert.error("Failed setting new password")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert.success("Successfully updated password!")
|
alert.success("Successfully updated password!")
|
||||||
setSelectedUserModalOpen(false)
|
setSelectedUserModalOpen(false)
|
||||||
@@ -387,7 +401,7 @@ const Admin = (props) => {
|
|||||||
|
|
||||||
const deleteUser = (data) => {
|
const deleteUser = (data) => {
|
||||||
// Just use this one?
|
// Just use this one?
|
||||||
const userId = isCloud ? data.username : data.id
|
const userId = data.id
|
||||||
|
|
||||||
const url = globalUrl + '/api/v1/users/' + userId
|
const url = globalUrl + '/api/v1/users/' + userId
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
@@ -459,9 +473,9 @@ const Admin = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
// FIXME: Set up features
|
// FIXME: Set up features
|
||||||
Object.keys(responseJson.sync_features).map(function(key, index) {
|
//Object.keys(responseJson.sync_features).map(function(key, index) {
|
||||||
//console.log(responseJson.sync_features[key])
|
// //console.log(responseJson.sync_features[key])
|
||||||
})
|
//})
|
||||||
|
|
||||||
//setOrgName(responseJson.name)
|
//setOrgName(responseJson.name)
|
||||||
//setOrgDescription(responseJson.description)
|
//setOrgDescription(responseJson.description)
|
||||||
@@ -474,6 +488,41 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inviteUser = (data) => {
|
||||||
|
console.log("INPUT: ", data)
|
||||||
|
setLoginInfo("")
|
||||||
|
|
||||||
|
// Just use this one?
|
||||||
|
var data = { "username": data.Username, "type": "invite", "org_id": selectedOrganization.id}
|
||||||
|
var baseurl = globalUrl
|
||||||
|
const url = baseurl + '/api/v1/users/register_org';
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: "include",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(response =>
|
||||||
|
response.json().then(responseJson => {
|
||||||
|
if (responseJson["success"] === false) {
|
||||||
|
setLoginInfo("Error: " + responseJson.reason)
|
||||||
|
} else {
|
||||||
|
setLoginInfo("")
|
||||||
|
setModalOpen(false)
|
||||||
|
setTimeout(() => {
|
||||||
|
getUsers()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch(error => {
|
||||||
|
console.log("Error in userdata: ", error)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const submitUser = (data) => {
|
const submitUser = (data) => {
|
||||||
console.log("INPUT: ", data)
|
console.log("INPUT: ", data)
|
||||||
setLoginInfo("")
|
setLoginInfo("")
|
||||||
@@ -498,7 +547,9 @@ const Admin = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
setLoginInfo("")
|
setLoginInfo("")
|
||||||
setModalOpen(false)
|
setModalOpen(false)
|
||||||
getUsers()
|
setTimeout(() => {
|
||||||
|
getUsers()
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -816,7 +867,6 @@ const Admin = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
console.log(responseJson)
|
|
||||||
setSchedules(responseJson)
|
setSchedules(responseJson)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -844,7 +894,7 @@ const Admin = (props) => {
|
|||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (responseJson.success) {
|
if (responseJson.success) {
|
||||||
//console.log(responseJson.data)
|
//console.log(responseJson.data)
|
||||||
console.log(responseJson)
|
//console.log(responseJson)
|
||||||
setAuthentication(responseJson.data)
|
setAuthentication(responseJson.data)
|
||||||
} else {
|
} else {
|
||||||
alert.error("Failed getting authentications")
|
alert.error("Failed getting authentications")
|
||||||
@@ -955,34 +1005,45 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const views = {
|
||||||
|
0: "organization",
|
||||||
|
1: "users",
|
||||||
|
2: "app_auth",
|
||||||
|
3: "files",
|
||||||
|
4: "schedules",
|
||||||
|
5: "environments",
|
||||||
|
6: "categories",
|
||||||
|
}
|
||||||
const setConfig = (event, newValue) => {
|
const setConfig = (event, newValue) => {
|
||||||
|
//console.log("Value: ", newValue)
|
||||||
|
|
||||||
|
setCurTab(parseInt(newValue))
|
||||||
if (newValue === 1) {
|
if (newValue === 1) {
|
||||||
|
document.title = "Shuffle - admin - users"
|
||||||
getUsers()
|
getUsers()
|
||||||
} else if (newValue === 2) {
|
} else if (newValue === 2) {
|
||||||
|
document.title = "Shuffle - admin - app authentication"
|
||||||
getAppAuthentication()
|
getAppAuthentication()
|
||||||
} else if (newValue === 3) {
|
} else if (newValue === 3) {
|
||||||
|
document.title = "Shuffle - admin - files"
|
||||||
getFiles()
|
getFiles()
|
||||||
} else if (newValue === 4) {
|
} else if (newValue === 4) {
|
||||||
|
document.title = "Shuffle - admin - schedules"
|
||||||
getSchedules()
|
getSchedules()
|
||||||
} else if (newValue === 5) {
|
} else if (newValue === 5) {
|
||||||
|
document.title = "Shuffle - admin - environments"
|
||||||
getEnvironments()
|
getEnvironments()
|
||||||
} else if (newValue === 6) {
|
} else if (newValue === 6) {
|
||||||
|
document.title = "Shuffle - admin - orgs"
|
||||||
getOrgs()
|
getOrgs()
|
||||||
|
} else {
|
||||||
|
document.title = "Shuffle - admin"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue === 6) {
|
if (newValue === 6) {
|
||||||
console.log("Should get apps for categories.")
|
console.log("Should get apps for categories.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const views = {
|
|
||||||
0: "organization",
|
|
||||||
1: "users",
|
|
||||||
2: "app_auth",
|
|
||||||
3: "environments",
|
|
||||||
4: "schedules",
|
|
||||||
5: "files",
|
|
||||||
6: "categories",
|
|
||||||
}
|
|
||||||
|
|
||||||
//var theURL = window.location.pathname
|
//var theURL = window.location.pathname
|
||||||
//FIXME: Add url edits
|
//FIXME: Add url edits
|
||||||
@@ -994,33 +1055,21 @@ const Admin = (props) => {
|
|||||||
//window.location.pathame = newpath
|
//window.location.pathame = newpath
|
||||||
|
|
||||||
setModalUser({})
|
setModalUser({})
|
||||||
setCurTab(newValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (firstRequest) {
|
if (firstRequest) {
|
||||||
setFirstRequest(false)
|
setFirstRequest(false)
|
||||||
|
document.title = "Shuffle - admin"
|
||||||
if (!isCloud) {
|
if (!isCloud) {
|
||||||
getUsers()
|
getUsers()
|
||||||
} else {
|
} else {
|
||||||
getSettings()
|
getSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
const views = {
|
|
||||||
"organization": 0,
|
|
||||||
"users": 1,
|
|
||||||
"app_auth": 2,
|
|
||||||
"environments": 3,
|
|
||||||
"schedules": 4,
|
|
||||||
"files": 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.match.params.key !== undefined) {
|
if (props.match.params.key !== undefined) {
|
||||||
const tmpitem = views[props.match.params.key]
|
//const tmpitem = views[props.match.params.key]
|
||||||
if (tmpitem !== undefined) {
|
setConfig("", props.match.params.key)
|
||||||
//setCurTab(tmpitem)
|
|
||||||
setConfig("", tmpitem)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1045,7 +1094,6 @@ const Admin = (props) => {
|
|||||||
const setUser = (userId, field, value) => {
|
const setUser = (userId, field, value) => {
|
||||||
const data = { "user_id": userId }
|
const data = { "user_id": userId }
|
||||||
data[field] = value
|
data[field] = value
|
||||||
console.log("DATA: ", data)
|
|
||||||
|
|
||||||
fetch(globalUrl + "/api/v1/users/updateuser", {
|
fetch(globalUrl + "/api/v1/users/updateuser", {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
@@ -1080,7 +1128,7 @@ const Admin = (props) => {
|
|||||||
|
|
||||||
|
|
||||||
const generateApikey = (user) => {
|
const generateApikey = (user) => {
|
||||||
const userId = isCloud ? user.username : user.id
|
const userId = user.id
|
||||||
const data = { "user_id": userId }
|
const data = { "user_id": userId }
|
||||||
|
|
||||||
fetch(globalUrl + "/api/v1/generateapikey", {
|
fetch(globalUrl + "/api/v1/generateapikey", {
|
||||||
@@ -1207,37 +1255,41 @@ const Admin = (props) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle><span style={{ color: "white" }}><EditIcon /> Edit user</span></DialogTitle>
|
<DialogTitle><span style={{ color: "white" }}><EditIcon style={{marginTop: 5}}/> Editing {selectedUser.username}</span></DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div style={{ display: "flex" }}>
|
{isCloud ?
|
||||||
<TextField
|
null
|
||||||
style={{ marginTop: 0, backgroundColor: theme.palette.inputColor, flex: 3 , marginRight: 10,}}
|
:
|
||||||
InputProps={{
|
<div style={{ display: "flex" }}>
|
||||||
style: {
|
<TextField
|
||||||
height: 50,
|
style={{ marginTop: 0, backgroundColor: theme.palette.inputColor, flex: 3 , marginRight: 10,}}
|
||||||
color: "white",
|
InputProps={{
|
||||||
},
|
style: {
|
||||||
}}
|
height: 50,
|
||||||
color="primary"
|
color: "white",
|
||||||
required
|
},
|
||||||
fullWidth={true}
|
}}
|
||||||
placeholder="New password"
|
color="primary"
|
||||||
type="password"
|
required
|
||||||
id="standard-required"
|
fullWidth={true}
|
||||||
autoComplete="password"
|
placeholder="New password"
|
||||||
margin="normal"
|
type="password"
|
||||||
variant="outlined"
|
id="standard-required"
|
||||||
onChange={e => setNewPassword(e.target.value)}
|
autoComplete="password"
|
||||||
/>
|
margin="normal"
|
||||||
<Button
|
variant="outlined"
|
||||||
style={{ maxHeight: 50, flex: 1 }}
|
onChange={e => setNewPassword(e.target.value)}
|
||||||
variant="outlined"
|
/>
|
||||||
color="primary"
|
<Button
|
||||||
onClick={() => onPasswordChange()}
|
style={{ maxHeight: 50, flex: 1 }}
|
||||||
>
|
variant="outlined"
|
||||||
Submit
|
color="primary"
|
||||||
</Button>
|
onClick={() => onPasswordChange()}
|
||||||
</div>
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
<Divider style={{ marginTop: 20, marginBottom: 20, backgroundColor: theme.palette.inputColor }} />
|
||||||
<Button
|
<Button
|
||||||
style={{}}
|
style={{}}
|
||||||
@@ -1471,14 +1523,20 @@ const Admin = (props) => {
|
|||||||
const org_id = selectedOrganization.id
|
const org_id = selectedOrganization.id
|
||||||
var copyText = document.getElementById(elementName);
|
var copyText = document.getElementById(elementName);
|
||||||
if (copyText !== null && copyText !== undefined) {
|
if (copyText !== null && copyText !== undefined) {
|
||||||
|
const clipboard = navigator.clipboard
|
||||||
|
if (clipboard === undefined) {
|
||||||
|
alert.error("Can only copy over HTTPS (port 3443)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
navigator.clipboard.writeText(org_id)
|
navigator.clipboard.writeText(org_id)
|
||||||
copyText.select();
|
copyText.select();
|
||||||
copyText.setSelectionRange(0, 99999); /* For mobile devices */
|
copyText.setSelectionRange(0, 99999); /* For mobile devices */
|
||||||
|
|
||||||
/* Copy the text inside the text field */
|
/* Copy the text inside the text field */
|
||||||
document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
|
|
||||||
alert.info(org_id + " copied to clipboard")
|
alert.info(org_id + " copied to clipboard")
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<FileCopyIcon style={{color: "rgba(255,255,255,0.8)"}}/>
|
<FileCopyIcon style={{color: "rgba(255,255,255,0.8)"}}/>
|
||||||
@@ -1592,7 +1650,7 @@ const Admin = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
{orgSyncResponse.length > 0 ?
|
{orgSyncResponse.length > 0 ?
|
||||||
<Typography style={{marginTop: 5, marginBottom: 10}}>
|
<Typography style={{marginTop: 5, marginBottom: 10}}>
|
||||||
Message from Shuffle: <b>{orgSyncResponse}</b>
|
Message from Shuffle Cloud: <b>{orgSyncResponse}</b>
|
||||||
</Typography>
|
</Typography>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
@@ -1606,7 +1664,7 @@ const Admin = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const item = selectedOrganization.sync_features[key]
|
const item = selectedOrganization.sync_features[key]
|
||||||
const newkey = key.replace("_", " ")
|
const newkey = key.replaceAll("_", " ")
|
||||||
const griditem = {
|
const griditem = {
|
||||||
"primary": newkey,
|
"primary": newkey,
|
||||||
"secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
|
"secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description,
|
||||||
@@ -1689,6 +1747,11 @@ const Admin = (props) => {
|
|||||||
{curTab === 1 ? "Add user" : "Add environment"}
|
{curTab === 1 ? "Add user" : "Add environment"}
|
||||||
</span></DialogTitle>
|
</span></DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
{curTab === 1 && isCloud ?
|
||||||
|
<Typography variant="body1" style={{marginBottom: 10}}>
|
||||||
|
We'll send an email to invite them to your organization.
|
||||||
|
</Typography>
|
||||||
|
: null}
|
||||||
{curTab === 1 ?
|
{curTab === 1 ?
|
||||||
<div>
|
<div>
|
||||||
Username
|
Username
|
||||||
@@ -1712,32 +1775,36 @@ const Admin = (props) => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={(event) => changeModalData("Username", event.target.value)}
|
onChange={(event) => changeModalData("Username", event.target.value)}
|
||||||
/>
|
/>
|
||||||
Password
|
{isCloud ? null :
|
||||||
<TextField
|
<span>
|
||||||
color="primary"
|
Password
|
||||||
style={{ backgroundColor: theme.palette.inputColor }}
|
<TextField
|
||||||
InputProps={{
|
color="primary"
|
||||||
style: {
|
style={{ backgroundColor: theme.palette.inputColor }}
|
||||||
height: "50px",
|
InputProps={{
|
||||||
color: "white",
|
style: {
|
||||||
fontSize: "1em",
|
height: "50px",
|
||||||
},
|
color: "white",
|
||||||
}}
|
fontSize: "1em",
|
||||||
required
|
},
|
||||||
fullWidth={true}
|
}}
|
||||||
autoComplete="password"
|
required
|
||||||
type="password"
|
fullWidth={true}
|
||||||
placeholder="********"
|
autoComplete="password"
|
||||||
id="pwfield"
|
type="password"
|
||||||
margin="normal"
|
placeholder="********"
|
||||||
variant="outlined"
|
id="pwfield"
|
||||||
onChange={(event) => changeModalData("Password", event.target.value)}
|
margin="normal"
|
||||||
/>
|
variant="outlined"
|
||||||
|
onChange={(event) => changeModalData("Password", event.target.value)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
: curTab === 3 ?
|
: curTab === 5 ?
|
||||||
<div>
|
<div>
|
||||||
Environment Name
|
Environment Name
|
||||||
<TextField
|
<TextField
|
||||||
color="primary"
|
color="primary"
|
||||||
style={{ backgroundColor: theme.palette.inputColor }}
|
style={{ backgroundColor: theme.palette.inputColor }}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -1766,8 +1833,12 @@ const Admin = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" style={{ borderRadius: "0px" }} onClick={() => {
|
<Button variant="contained" style={{ borderRadius: "0px" }} onClick={() => {
|
||||||
if (curTab === 1) {
|
if (curTab === 1) {
|
||||||
submitUser(modalUser)
|
if (isCloud) {
|
||||||
} else if (curTab === 3) {
|
inviteUser(modalUser)
|
||||||
|
} else {
|
||||||
|
submitUser(modalUser)
|
||||||
|
}
|
||||||
|
} else if (curTab === 5) {
|
||||||
submitEnvironment(modalUser)
|
submitEnvironment(modalUser)
|
||||||
}
|
}
|
||||||
}} color="primary">
|
}} color="primary">
|
||||||
@@ -1804,12 +1875,12 @@ const Admin = (props) => {
|
|||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Username"
|
primary="Username"
|
||||||
style={{ minWidth: 300, maxWidth: 300}}
|
style={{ minWidth: 350, maxWidth: 350}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="API key"
|
primary="API key"
|
||||||
style={{ minWidth: 100, maxWidth: 100, overflow: "hidden" }}
|
style={{ marginleft: 10, minWidth: 100, maxWidth: 100, overflow: "hidden" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
@@ -1835,17 +1906,23 @@ const Admin = (props) => {
|
|||||||
<ListItem key={index} style={{backgroundColor: bgColor}}>
|
<ListItem key={index} style={{backgroundColor: bgColor}}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={data.username}
|
primary={data.username}
|
||||||
style={{ minWidth: 300, maxWidth: 300, overflow: "hidden",}}
|
style={{ minWidth: 350, maxWidth: 350, overflow: "hidden",}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{ maxWidth: 100, minWidth: 100, }}
|
style={{ marginLeft: 10, maxWidth: 100, minWidth: 100, }}
|
||||||
primary={data.apikey === undefined || data.apikey.length === 0 ? "" :
|
primary={data.apikey === undefined || data.apikey.length === 0 ? "" :
|
||||||
<Tooltip title={"Copy Api Key"} style={{}} aria-label={"Copy APIkey"}>
|
<Tooltip title={"Copy Api Key"} style={{}} aria-label={"Copy APIkey"}>
|
||||||
<IconButton style={{}} onClick={() => {
|
<IconButton style={{}} onClick={() => {
|
||||||
const elementName = "copy_element_shuffle"
|
const elementName = "copy_element_shuffle"
|
||||||
var copyText = document.getElementById(elementName);
|
var copyText = document.getElementById(elementName);
|
||||||
if (copyText !== null && copyText !== undefined) {
|
if (copyText !== null && copyText !== undefined) {
|
||||||
|
const clipboard = navigator.clipboard
|
||||||
|
if (clipboard === undefined) {
|
||||||
|
alert.error("Can only copy over HTTPS (port 3443)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
navigator.clipboard.writeText(data.apikey)
|
navigator.clipboard.writeText(data.apikey)
|
||||||
copyText.select();
|
copyText.select();
|
||||||
copyText.setSelectionRange(0, 99999); /* For mobile devices */
|
copyText.setSelectionRange(0, 99999); /* For mobile devices */
|
||||||
@@ -1874,11 +1951,7 @@ const Admin = (props) => {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
console.log("VALUE: ", e.target.value)
|
console.log("VALUE: ", e.target.value)
|
||||||
|
|
||||||
if (isCloud) {
|
setUser(data.id, "role", e.target.value)
|
||||||
setUser(data.username, "role", e.target.value)
|
|
||||||
} else {
|
|
||||||
setUser(data.id, "role", e.target.value)
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }}
|
style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }}
|
||||||
>
|
>
|
||||||
@@ -1997,7 +2070,7 @@ const Admin = (props) => {
|
|||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Name"
|
primary="Name"
|
||||||
style={{maxWidth: 150, minWidth: 150, overflow: "hidden",}}
|
style={{maxWidth: 150, minWidth: 150, overflow: "hidden", marginLeft: 10,}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Workflow"
|
primary="Workflow"
|
||||||
@@ -2031,11 +2104,11 @@ const Admin = (props) => {
|
|||||||
return (
|
return (
|
||||||
<ListItem key={index} style={{backgroundColor: bgColor}} >
|
<ListItem key={index} style={{backgroundColor: bgColor}} >
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{maxWidth: 225, minWidth: 225}}
|
style={{maxWidth: 225, minWidth: 225, overflow: "hidden",}}
|
||||||
primary={new Date(file.created_at*1000).toISOString()}
|
primary={new Date(file.created_at*1000).toISOString()}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{maxWidth: 150, minWidth: 150, overflow: "hidden",}}
|
style={{maxWidth: 150, minWidth: 150, overflow: "hidden", marginLeft: 10, }}
|
||||||
primary={file.filename}
|
primary={file.filename}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
@@ -2046,11 +2119,13 @@ const Admin = (props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
:
|
:
|
||||||
<Tooltip title={"Go to workflow"} style={{}} aria-label={"Download"}>
|
<Tooltip title={"Go to workflow"} style={{}} aria-label={"Download"}>
|
||||||
<a style={{textDecoration: "none", color: "#f85a3e"}} href={`/workflows/${file.workflow_id}`} target="_blank">
|
<span>
|
||||||
<IconButton disabled={file.workflow_id === "global"}>
|
<a style={{textDecoration: "none", color: "#f85a3e"}} href={`/workflows/${file.workflow_id}`} target="_blank">
|
||||||
<OpenInNewIcon style={{color: file.workflow_id !== "global" ? "white" : "grey",}} />
|
<IconButton disabled={file.workflow_id === "global"}>
|
||||||
</IconButton>
|
<OpenInNewIcon style={{color: file.workflow_id !== "global" ? "white" : "grey",}} />
|
||||||
</a>
|
</IconButton>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
style={{minWidth: 100, maxWidth: 100, overflow: "hidden"}}
|
style={{minWidth: 100, maxWidth: 100, overflow: "hidden"}}
|
||||||
@@ -2070,11 +2145,13 @@ const Admin = (props) => {
|
|||||||
<ListItemText
|
<ListItemText
|
||||||
primary=
|
primary=
|
||||||
<Tooltip title={"Download file"} style={{}} aria-label={"Download"}>
|
<Tooltip title={"Download file"} style={{}} aria-label={"Download"}>
|
||||||
<IconButton disabled={file.status !== "active"} onClick={() => {
|
<span>
|
||||||
downloadFile(file)
|
<IconButton disabled={file.status !== "active"} onClick={() => {
|
||||||
}}>
|
downloadFile(file)
|
||||||
<CloudDownloadIcon style={{color: file.status === "active" ? "white" : "grey",}} />
|
}}>
|
||||||
</IconButton>
|
<CloudDownloadIcon style={{color: file.status === "active" ? "white" : "grey",}} />
|
||||||
|
</IconButton>
|
||||||
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
style={{minWidth: 75, maxWidth: 75, overflow: "hidden"}}
|
style={{minWidth: 75, maxWidth: 75, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
@@ -2097,6 +2174,12 @@ const Admin = (props) => {
|
|||||||
const elementName = "copy_element_shuffle"
|
const elementName = "copy_element_shuffle"
|
||||||
var copyText = document.getElementById(elementName);
|
var copyText = document.getElementById(elementName);
|
||||||
if (copyText !== null && copyText !== undefined) {
|
if (copyText !== null && copyText !== undefined) {
|
||||||
|
const clipboard = navigator.clipboard
|
||||||
|
if (clipboard === undefined) {
|
||||||
|
alert.error("Can only copy over HTTPS (port 3443)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
navigator.clipboard.writeText(file.id)
|
navigator.clipboard.writeText(file.id)
|
||||||
copyText.select();
|
copyText.select();
|
||||||
copyText.setSelectionRange(0, 99999); /* For mobile devices */
|
copyText.setSelectionRange(0, 99999); /* For mobile devices */
|
||||||
@@ -2353,9 +2436,7 @@ const Admin = (props) => {
|
|||||||
style={{minWidth: 110, maxWidth: 110, overflow: "hidden"}}
|
style={{minWidth: 110, maxWidth: 110, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={data.fields.map(data => {
|
primary={data.fields === null || data.fields === undefined ? "" : data.fields.map(data => {return data.key}).join(", ")}
|
||||||
return data.key
|
|
||||||
}).join(", ")}
|
|
||||||
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
|
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
<ListItemText>
|
<ListItemText>
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
import React, {useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
import { makeStyles } from '@material-ui/styles';
|
import { makeStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
import TextField from '@material-ui/core/TextField';
|
import {CircularProgress, TextField, Button, Paper, Typography} from '@material-ui/core'
|
||||||
import Button from '@material-ui/core/Button';
|
|
||||||
import Paper from '@material-ui/core/Paper';
|
|
||||||
|
|
||||||
const bodyDivStyle = {
|
const bodyDivStyle = {
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
@@ -35,6 +33,7 @@ const AdminAccount = props => {
|
|||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [firstRequest, setFirstRequest] = useState(true);
|
const [firstRequest, setFirstRequest] = useState(true);
|
||||||
|
const [loginLoading, setLoginLoading] = useState(false);
|
||||||
|
|
||||||
// Used to swap from login to register. True = login, false = register
|
// Used to swap from login to register. True = login, false = register
|
||||||
const register = true
|
const register = true
|
||||||
@@ -81,6 +80,7 @@ const AdminAccount = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = (e) => {
|
const onSubmit = (e) => {
|
||||||
|
setLoginLoading(true)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
// FIXME - add some check here ROFL
|
// FIXME - add some check here ROFL
|
||||||
|
|
||||||
@@ -97,6 +97,7 @@ const AdminAccount = props => {
|
|||||||
})
|
})
|
||||||
.then(response =>
|
.then(response =>
|
||||||
response.json().then(responseJson => {
|
response.json().then(responseJson => {
|
||||||
|
setLoginLoading(false)
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
setLoginInfo(responseJson["reason"])
|
setLoginInfo(responseJson["reason"])
|
||||||
} else {
|
} else {
|
||||||
@@ -106,6 +107,7 @@ const AdminAccount = props => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
setLoginLoading(false)
|
||||||
setLoginInfo("Error in userdata: ", error)
|
setLoginInfo("Error in userdata: ", error)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -161,7 +163,7 @@ const AdminAccount = props => {
|
|||||||
id="emailfield"
|
id="emailfield"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={onChangeUser}
|
onChange={onChangeUser}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
Password
|
Password
|
||||||
@@ -191,7 +193,9 @@ const AdminAccount = props => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{display: "flex", marginTop: "15px"}}>
|
<div style={{display: "flex", marginTop: "15px"}}>
|
||||||
<Button color="primary" variant="contained" type="submit" style={{flex: "1", marginRight: "5px"}} disabled={!handleValidateForm()}>SUBMIT</Button>
|
<Button color="primary" variant="contained" type="submit" style={{flex: "1", marginRight: "5px"}} disabled={!handleValidateForm() || loginLoading}>
|
||||||
|
{loginLoading ? <CircularProgress color="secondary" style={{color: "white",}} /> : "SUBMIT"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{marginTop: "10px"}}>
|
<div style={{marginTop: "10px"}}>
|
||||||
|
|||||||
+2839
-1869
File diff suppressed because one or more lines are too long
+968
-234
File diff suppressed because it is too large
Load Diff
+169
-51
@@ -3,7 +3,7 @@ import React, { useEffect } from 'react';
|
|||||||
import { useInterval } from 'react-powerhooks';
|
import { useInterval } from 'react-powerhooks';
|
||||||
|
|
||||||
import {IconButton, Typography, Grid, Select, Paper, Divider, ButtonBase, Button, TextField, FormControl, MenuItem, Tooltip, FormControlLabel, Switch, Input, Breadcrumbs, Chip, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress} from '@material-ui/core';
|
import {IconButton, Typography, Grid, Select, Paper, Divider, ButtonBase, Button, TextField, FormControl, MenuItem, Tooltip, FormControlLabel, Switch, Input, Breadcrumbs, Chip, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress} from '@material-ui/core';
|
||||||
import {OpenInNew as OpenInNewIcon,Apps as AppsIcon, Cached as CachedIcon, Publish as PublishIcon, CloudDownload as CloudDownloadIcon, Edit as EditIcon, Delete as DeleteIcon} from '@material-ui/icons';
|
import {LockOpen as LockOpenIcon, OpenInNew as OpenInNewIcon,Apps as AppsIcon, Cached as CachedIcon, Publish as PublishIcon, CloudDownload as CloudDownloadIcon, Edit as EditIcon, Delete as DeleteIcon} from '@material-ui/icons';
|
||||||
|
|
||||||
import { useTheme } from '@material-ui/core/styles';
|
import { useTheme } from '@material-ui/core/styles';
|
||||||
|
|
||||||
@@ -20,9 +20,16 @@ const chipStyle = {
|
|||||||
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
|
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixes names by making them uppercase and such
|
||||||
|
// Used for labels. A lot of places don't use this yet
|
||||||
|
export const FixName = (name) => {
|
||||||
|
const newAppname = (name.charAt(0).toUpperCase()+name.substring(1)).replaceAll("_", " ")
|
||||||
|
return newAppname
|
||||||
|
}
|
||||||
|
|
||||||
// Parses JSON data into keys that can be used everywhere :)
|
// Parses JSON data into keys that can be used everywhere :)
|
||||||
export const GetParsedPaths = (inputdata, basekey) => {
|
export const GetParsedPaths = (inputdata, basekey) => {
|
||||||
const splitkey = " > "
|
const splitkey = "."
|
||||||
var parsedValues = []
|
var parsedValues = []
|
||||||
if (inputdata === undefined || inputdata === null) {
|
if (inputdata === undefined || inputdata === null) {
|
||||||
return parsedValues
|
return parsedValues
|
||||||
@@ -40,8 +47,8 @@ export const GetParsedPaths = (inputdata, basekey) => {
|
|||||||
// Handle direct loop!
|
// Handle direct loop!
|
||||||
if (!isNaN(key) && basekey === "") {
|
if (!isNaN(key) && basekey === "") {
|
||||||
console.log("Handling direct loop.")
|
console.log("Handling direct loop.")
|
||||||
parsedValues.push({"type": "object", "name": "Node", "autocomplete": `${basekey}`})
|
parsedValues.push({"type": "object", "name": "Node", "autocomplete": `${basekey.replaceAll(" ", "_")}`})
|
||||||
parsedValues.push({"type": "list", "name": `${splitkey}list`, "autocomplete": `${basekey}.#`})
|
parsedValues.push({"type": "list", "name": `${splitkey}list`, "autocomplete": `${basekey.replaceAll(" ", "_")}.#`})
|
||||||
const returnValues = GetParsedPaths(value, `${basekey}.#`)
|
const returnValues = GetParsedPaths(value, `${basekey}.#`)
|
||||||
for (var subkey in returnValues) {
|
for (var subkey in returnValues) {
|
||||||
parsedValues.push(returnValues[subkey])
|
parsedValues.push(returnValues[subkey])
|
||||||
@@ -54,8 +61,8 @@ export const GetParsedPaths = (inputdata, basekey) => {
|
|||||||
if (typeof(value) === 'object') {
|
if (typeof(value) === 'object') {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// Check if each item is object
|
// Check if each item is object
|
||||||
parsedValues.push({"type": "object", "name": basekeyname, "autocomplete": `${basekey}.${key}`})
|
parsedValues.push({"type": "object", "name": basekeyname, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}`})
|
||||||
parsedValues.push({"type": "list", "name": `${basekeyname}${splitkey}list`, "autocomplete": `${basekey}.${key}.#`})
|
parsedValues.push({"type": "list", "name": `${basekeyname}${splitkey}list`, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}.#`})
|
||||||
|
|
||||||
// Only check the first. This would be probably be dumb otherwise.
|
// Only check the first. This would be probably be dumb otherwise.
|
||||||
for (var subkey in value) {
|
for (var subkey in value) {
|
||||||
@@ -72,14 +79,14 @@ export const GetParsedPaths = (inputdata, basekey) => {
|
|||||||
}
|
}
|
||||||
//console.log(key+" is array")
|
//console.log(key+" is array")
|
||||||
} else {
|
} else {
|
||||||
parsedValues.push({"type": "object", "name": basekeyname, "autocomplete": `${basekey}.${key}`})
|
parsedValues.push({"type": "object", "name": basekeyname, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}`})
|
||||||
const returnValues = GetParsedPaths(value, `${basekey}.${key}`)
|
const returnValues = GetParsedPaths(value, `${basekey}.${key}`)
|
||||||
for (var subkey in returnValues) {
|
for (var subkey in returnValues) {
|
||||||
parsedValues.push(returnValues[subkey])
|
parsedValues.push(returnValues[subkey])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parsedValues.push({"type": "value", "name": basekeyname, "autocomplete": `${basekey}.${key}`, "value": value,})
|
parsedValues.push({"type": "value", "name": basekeyname, "autocomplete": `${basekey}.${key.replaceAll(" ", "_")}`, "value": value,})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +194,7 @@ const Apps = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getApps = () => {
|
const getApps = () => {
|
||||||
fetch(globalUrl+"/api/v1/workflows/apps", {
|
fetch(globalUrl+"/api/v1/apps", {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -236,6 +243,10 @@ const Apps = (props) => {
|
|||||||
privateapps.push(...invalid)
|
privateapps.push(...invalid)
|
||||||
|
|
||||||
setApps(privateapps)
|
setApps(privateapps)
|
||||||
|
setCursearch("")
|
||||||
|
|
||||||
|
//handleSearchChange(event.target.value)
|
||||||
|
//setCursearch(event.target.value)
|
||||||
setFilteredApps(privateapps)
|
setFilteredApps(privateapps)
|
||||||
if (privateapps.length > 0) {
|
if (privateapps.length > 0) {
|
||||||
if (selectedApp.id === undefined || selectedApp.id === null) {
|
if (selectedApp.id === undefined || selectedApp.id === null) {
|
||||||
@@ -280,11 +291,19 @@ const Apps = (props) => {
|
|||||||
if (!responseJson.success) {
|
if (!responseJson.success) {
|
||||||
alert.error("Failed to download file")
|
alert.error("Failed to download file")
|
||||||
} else {
|
} else {
|
||||||
const inputdata = YAML.parse(responseJson.body)
|
console.log(responseJson)
|
||||||
|
const basedata = atob(responseJson.openapi)
|
||||||
|
console.log("BASE: ", basedata)
|
||||||
|
var inputdata = JSON.parse(basedata)
|
||||||
|
console.log("POST INPUT: ", inputdata)
|
||||||
|
inputdata = JSON.parse(inputdata.body)
|
||||||
|
|
||||||
const newpaths = {}
|
const newpaths = {}
|
||||||
Object.keys(inputdata["paths"]).forEach(function(key) {
|
if (inputdata["paths"] !== undefined) {
|
||||||
newpaths[key.split("?")[0]] = inputdata.paths[key]
|
Object.keys(inputdata["paths"]).forEach(function(key) {
|
||||||
})
|
newpaths[key.split("?")[0]] = inputdata.paths[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
inputdata.paths = newpaths
|
inputdata.paths = newpaths
|
||||||
console.log("INPUT: ", inputdata)
|
console.log("INPUT: ", inputdata)
|
||||||
@@ -318,6 +337,10 @@ const Apps = (props) => {
|
|||||||
|
|
||||||
// dropdown with copy etc I guess
|
// dropdown with copy etc I guess
|
||||||
const appPaper = (data) => {
|
const appPaper = (data) => {
|
||||||
|
if (data.name === "" && data.id === "") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
var boxWidth = "2px"
|
var boxWidth = "2px"
|
||||||
if (selectedApp.id === data.id) {
|
if (selectedApp.id === data.id) {
|
||||||
boxWidth = "4px"
|
boxWidth = "4px"
|
||||||
@@ -338,7 +361,7 @@ const Apps = (props) => {
|
|||||||
|
|
||||||
//<div style={{backgroundColor: theme.palette.inputColor, height: 100, width: 100, borderRadius: 3, verticalAlign: "middle", textAlign: "center", display: "table-cell"}}>
|
//<div style={{backgroundColor: theme.palette.inputColor, height: 100, width: 100, borderRadius: 3, verticalAlign: "middle", textAlign: "center", display: "table-cell"}}>
|
||||||
// <div style={{width: "100px", height: "100px", border: "1px solid black", verticalAlign: "middle", textAlign: "center", display: "table-cell"}}>
|
// <div style={{width: "100px", height: "100px", border: "1px solid black", verticalAlign: "middle", textAlign: "center", display: "table-cell"}}>
|
||||||
var imageline = data.large_image.length === 0 ?
|
var imageline = data.large_image === undefined || data.large_image.length === 0 ?
|
||||||
<img alt={data.title} style={{borderRadius: borderRadius, width: 100, height: 100, backgroundColor: theme.palette.inputColor,}} />
|
<img alt={data.title} style={{borderRadius: borderRadius, width: 100, height: 100, backgroundColor: theme.palette.inputColor,}} />
|
||||||
:
|
:
|
||||||
<img alt={data.title} src={data.large_image} style={{borderRadius: borderRadius, maxWidth: 100, minWidth: 100, maxHeight: "100%", display: "block", margin: "0 auto"}} onLoad={(event) => {
|
<img alt={data.title} src={data.large_image} style={{borderRadius: borderRadius, maxWidth: 100, minWidth: 100, maxHeight: "100%", display: "block", margin: "0 auto"}} onLoad={(event) => {
|
||||||
@@ -419,6 +442,17 @@ const Apps = (props) => {
|
|||||||
label={tag}
|
label={tag}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
//console.log("SEARCH: ", event.target.value)
|
||||||
|
handleSearchChange(tag)
|
||||||
|
setCursearch(tag)
|
||||||
|
//id="app_search_field"
|
||||||
|
const searchfield = document.getElementById("app_search_field")
|
||||||
|
if (searchfield !== null && searchfield !== undefined) {
|
||||||
|
console.log("SEARCHFIELD: ", searchfield)
|
||||||
|
searchfield.value = tag
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -428,11 +462,11 @@ const Apps = (props) => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ?
|
{data.activated && data.private_id !== undefined && data.private_id.length > 0 && data.generated ?
|
||||||
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}} onClick={() => {downloadApp(data)}}>
|
<Grid container style={{margin: "10px 10px 10px 10px", flex: "1"}} onClick={() => {downloadApp(data)}}>
|
||||||
<Tooltip title={"Download OpenAPI"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
|
<Tooltip title={"Download OpenAPI"} style={{marginTop: "28px", width: "100%"}} aria-label={data.name}>
|
||||||
<CloudDownloadIcon />
|
<CloudDownloadIcon />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Grid>
|
</Grid>
|
||||||
: null}
|
: null}
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
@@ -631,6 +665,37 @@ const Apps = (props) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 1 ?
|
||||||
|
<Select
|
||||||
|
defaultValue={selectedApp.app_version}
|
||||||
|
onChange={(event) => {
|
||||||
|
console.log("Changing version to index "+event.target.value)
|
||||||
|
const newversion = selectedApp.versions.find(tmpApp => tmpApp.version == event.target.value)
|
||||||
|
console.log("New version: ", newversion)
|
||||||
|
selectedApp.app_version = selectedApp.app_version
|
||||||
|
setSelectedApp(selectedApp)
|
||||||
|
|
||||||
|
if (newversion !== undefined && newversion !== null) {
|
||||||
|
getApp(newversion.id, true)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{position: "absolute", top: -10, right: isCloud ? 50 : 0, backgroundColor: theme.palette.surfaceColor, backgroundColor: inputColor, color: "white", height: 35, marginleft: 10,}}
|
||||||
|
SelectDisplayProps={{
|
||||||
|
style: {
|
||||||
|
marginLeft: 10,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedApp.versions.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={data.version} style={{backgroundColor: inputColor, color: "white"}} value={data.version}>
|
||||||
|
{data.version}
|
||||||
|
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
: null }
|
||||||
{isCloud ?
|
{isCloud ?
|
||||||
<a href={"https://shuffler.io/apps/"+selectedApp.id} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">
|
<a href={"https://shuffler.io/apps/"+selectedApp.id} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">
|
||||||
<IconButton style={{top: -10, right: 0, position: "absolute", color: "#f85a3e"}} >
|
<IconButton style={{top: -10, right: 0, position: "absolute", color: "#f85a3e"}} >
|
||||||
@@ -755,9 +820,14 @@ const Apps = (props) => {
|
|||||||
const circleSize = 10
|
const circleSize = 10
|
||||||
return (
|
return (
|
||||||
<MenuItem key={data.name} style={{backgroundColor: inputColor, color: "white"}} value={data}>
|
<MenuItem key={data.name} style={{backgroundColor: inputColor, color: "white"}} value={data}>
|
||||||
<div style={{width: circleSize, height: circleSize, borderRadius: circleSize / 2, backgroundColor: itemColor, marginRight: "10px"}}/>
|
{data.configuration === true ?
|
||||||
|
<Tooltip color="primary" title={`Authenticate ${selectedApp.name}`} placement="top">
|
||||||
|
<LockOpenIcon style={{cursor: "pointer", width: 24, height: 24, marginRight: 10, }} />
|
||||||
|
</Tooltip>
|
||||||
|
:
|
||||||
|
<div style={{width: 17, height: 17, borderRadius: 17 / 2, backgroundColor: itemColor, marginRight: 10, marginTop: 2, marginTop: "auto", marginBottom: "auto",}}/>
|
||||||
|
}
|
||||||
{data.name}
|
{data.name}
|
||||||
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -831,11 +901,11 @@ const Apps = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchfield = search.toLowerCase()
|
const searchfield = search.toLowerCase()
|
||||||
var newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield))
|
var newapps = apps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield) || (data.tags !== null && data.tags.includes(search)))
|
||||||
var tmpapps = searchableApps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield))
|
var tmpapps = searchableApps.filter(data => data.name.toLowerCase().includes(searchfield) || data.description.toLowerCase().includes(searchfield) || (data.tags !== null && data.tags.includes(search)))
|
||||||
newapps.push(...tmpapps)
|
newapps.push(...tmpapps)
|
||||||
|
|
||||||
console.log(newapps)
|
//console.log(newapps)
|
||||||
setFilteredApps(newapps)
|
setFilteredApps(newapps)
|
||||||
//if ((newapps.length === 0 || searchBackend) && !appSearchLoading) {
|
//if ((newapps.length === 0 || searchBackend) && !appSearchLoading) {
|
||||||
|
|
||||||
@@ -940,26 +1010,30 @@ const Apps = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<TextField
|
<div style={{height: 50}}>
|
||||||
style={{backgroundColor: inputColor, borderRadius: 5,}}
|
<TextField
|
||||||
InputProps={{
|
style={{backgroundColor: inputColor, borderRadius: 5,}}
|
||||||
style:{
|
InputProps={{
|
||||||
color: "white",
|
style:{
|
||||||
minHeight: "50px",
|
color: "white",
|
||||||
marginLeft: "5px",
|
minHeight: "50px",
|
||||||
maxWidth: "95%",
|
marginLeft: "5px",
|
||||||
fontSize: "1em",
|
maxWidth: "95%",
|
||||||
borderRadius: 5,
|
fontSize: "1em",
|
||||||
},
|
borderRadius: 5,
|
||||||
}}
|
},
|
||||||
fullWidth
|
}}
|
||||||
color="primary"
|
disabled={apps === undefined || apps === null || apps.length === 0}
|
||||||
placeholder={"Search apps"}
|
fullWidth
|
||||||
onChange={(event) => {
|
color="primary"
|
||||||
handleSearchChange(event.target.value)
|
id="app_search_field"
|
||||||
setCursearch(event.target.value)
|
placeholder={"Search apps"}
|
||||||
}}
|
onChange={(event) => {
|
||||||
/>
|
handleSearchChange(event.target.value)
|
||||||
|
setCursearch(event.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div style={{marginTop: 15}}>
|
<div style={{marginTop: 15}}>
|
||||||
{apps.length > 0 ?
|
{apps.length > 0 ?
|
||||||
filteredApps.length > 0 ?
|
filteredApps.length > 0 ?
|
||||||
@@ -1083,7 +1157,7 @@ const Apps = (props) => {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
alert.success("Hotloaded apps!")
|
//alert.success("Hotloaded apps!")
|
||||||
getApps()
|
getApps()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1108,7 +1182,7 @@ const Apps = (props) => {
|
|||||||
setValidation(true)
|
setValidation(true)
|
||||||
|
|
||||||
var cors = "cors"
|
var cors = "cors"
|
||||||
if (openApi.includes("localhost")) {
|
if (openApi.includes("= localhost")) {
|
||||||
cors = "no-cors"
|
cors = "no-cors"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1126,6 +1200,48 @@ const Apps = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getApp = (appId, setApp) => {
|
||||||
|
fetch(globalUrl+"/api/v1/apps/"+appId+"/config?openapi=false", {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: "include",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
//alert.success("Successfully GOT app "+appId)
|
||||||
|
} else {
|
||||||
|
alert.error("Failed getting app")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then((responseJson) => {
|
||||||
|
console.log(responseJson)
|
||||||
|
|
||||||
|
if (setApp) {
|
||||||
|
if (selectedApp.versions !== undefined && selectedApp.versions !== null) {
|
||||||
|
responseJson.versions = selectedApp.versions
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedApp.loop_versions !== undefined && selectedApp.loop_versions !== null) {
|
||||||
|
responseJson.loop_versions = selectedApp.loop_versions
|
||||||
|
}
|
||||||
|
|
||||||
|
//alert.info("Should set app to selected")
|
||||||
|
if (responseJson.actions !== undefined && responseJson.actions !== null && responseJson.actions.length > 0) {
|
||||||
|
setSelectedAction(responseJson.actions[0])
|
||||||
|
} else {
|
||||||
|
setSelectedAction({})
|
||||||
|
}
|
||||||
|
setSelectedApp(responseJson)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert.error(error.toString())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const deleteApp = (appId) => {
|
const deleteApp = (appId) => {
|
||||||
alert.info("Attempting to delete app")
|
alert.info("Attempting to delete app")
|
||||||
fetch(globalUrl+"/api/v1/apps/"+appId, {
|
fetch(globalUrl+"/api/v1/apps/"+appId, {
|
||||||
@@ -1133,12 +1249,14 @@ const Apps = (props) => {
|
|||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
},
|
},
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
alert.success("Successfully deleted app")
|
alert.success("Successfully deleted app")
|
||||||
getApps()
|
setTimeout(() => {
|
||||||
|
getApps()
|
||||||
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
alert.error("Failed deleting app")
|
alert.error("Failed deleting app")
|
||||||
}
|
}
|
||||||
@@ -1380,7 +1498,7 @@ const Apps = (props) => {
|
|||||||
style={{backgroundColor: inputColor}}
|
style={{backgroundColor: inputColor}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
defaultValue={userdata.active_org.defaults.app_download_repo !== undefined && userdata.active_org.defaults.app_download_repo.length > 0 ? userdata.active_org.defaults.app_download_repo : "https://github.com/frikky/shuffle-apps"}
|
defaultValue={"https://github.com/frikky/shuffle-apps"}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style:{
|
style:{
|
||||||
color: "white",
|
color: "white",
|
||||||
@@ -1398,7 +1516,7 @@ const Apps = (props) => {
|
|||||||
style={{backgroundColor: inputColor}}
|
style={{backgroundColor: inputColor}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
defaultValue={userdata.active_org.defaults.app_download_branch !== undefined && userdata.active_org.defaults.app_download_branch.length > 0 ? userdata.active_org.defaults.app_download_branch : downloadBranch}
|
defaultValue={downloadBranch}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style:{
|
style:{
|
||||||
color: "white",
|
color: "white",
|
||||||
@@ -1521,7 +1639,7 @@ const Apps = (props) => {
|
|||||||
hidden
|
hidden
|
||||||
type="file"
|
type="file"
|
||||||
ref={upload}
|
ref={upload}
|
||||||
accept="application/JSON, text/yaml, text/x-yaml, application/x-yaml, application/vnd.yaml"
|
accept="application/JSON, application/YAML, text/yaml, text/x-yaml, application/x-yaml, application/vnd.yaml"
|
||||||
multiple={false}
|
multiple={false}
|
||||||
onChange={uploadFile}
|
onChange={uploadFile}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { makeStyles } from '@material-ui/styles';
|
import { makeStyles } from '@material-ui/styles';
|
||||||
|
|
||||||
import TextField from '@material-ui/core/TextField';
|
import {CircularProgress, TextField, Button, Paper, Typography} from '@material-ui/core'
|
||||||
import Button from '@material-ui/core/Button';
|
|
||||||
import Paper from '@material-ui/core/Paper';
|
|
||||||
import { useTheme } from '@material-ui/core/styles';
|
import { useTheme } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
|
||||||
@@ -15,7 +13,7 @@ const hrefStyle = {
|
|||||||
|
|
||||||
const bodyDivStyle = {
|
const bodyDivStyle = {
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
marginTop: "100px",
|
marginTop: 150,
|
||||||
width: "500px",
|
width: "500px",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +31,7 @@ const LoginDialog = props => {
|
|||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [firstRequest, setFirstRequest] = useState(true);
|
const [firstRequest, setFirstRequest] = useState(true);
|
||||||
|
const [loginLoading, setLoginLoading] = useState(false);
|
||||||
|
|
||||||
// Used to swap from login to register. True = login, false = register
|
// Used to swap from login to register. True = login, false = register
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ const LoginDialog = props => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
setLoginInfo("Error logging in: ", error)
|
setLoginInfo("Error logging in - please refresh in a minute ", error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +78,7 @@ const LoginDialog = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = (e) => {
|
const onSubmit = (e) => {
|
||||||
|
setLoginLoading(true)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setLoginInfo("")
|
setLoginInfo("")
|
||||||
// FIXME - add some check here ROFL
|
// FIXME - add some check here ROFL
|
||||||
@@ -101,6 +101,7 @@ const LoginDialog = props => {
|
|||||||
})
|
})
|
||||||
.then(response =>
|
.then(response =>
|
||||||
response.json().then(responseJson => {
|
response.json().then(responseJson => {
|
||||||
|
setLoginLoading(false)
|
||||||
if (responseJson["success"] === false) {
|
if (responseJson["success"] === false) {
|
||||||
setLoginInfo(responseJson["reason"])
|
setLoginInfo(responseJson["reason"])
|
||||||
} else {
|
} else {
|
||||||
@@ -110,11 +111,13 @@ const LoginDialog = props => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsLoggedIn(true)
|
setIsLoggedIn(true)
|
||||||
|
|
||||||
window.location.pathname = "/workflows"
|
window.location.pathname = "/workflows"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
setLoginLoading(false)
|
||||||
setLoginInfo("Error logging in: " + error)
|
setLoginInfo("Error logging in: " + error)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -136,7 +139,7 @@ const LoginDialog = props => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
setLoginInfo("Error in userdata: ", error)
|
setLoginInfo("Error in from backend: ", error)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,6 +164,7 @@ const LoginDialog = props => {
|
|||||||
|
|
||||||
//var loginChange = register ? (<div><p onClick={setLoginCheck(false)}>Want to register? Click here.</p></div>) : (<div><p onClick={setLoginCheck(true)}>Go back to login? Click here.</p></div>);
|
//var loginChange = register ? (<div><p onClick={setLoginCheck(false)}>Want to register? Click here.</p></div>) : (<div><p onClick={setLoginCheck(true)}>Go back to login? Click here.</p></div>);
|
||||||
var formtitle = register ? <div>Login</div> : <div>Register</div>
|
var formtitle = register ? <div>Login</div> : <div>Register</div>
|
||||||
|
const imgsize = 100
|
||||||
const basedata =
|
const basedata =
|
||||||
<div style={bodyDivStyle}>
|
<div style={bodyDivStyle}>
|
||||||
<Paper style={{
|
<Paper style={{
|
||||||
@@ -168,8 +172,12 @@ const LoginDialog = props => {
|
|||||||
paddingRight: "30px",
|
paddingRight: "30px",
|
||||||
paddingBottom: "30px",
|
paddingBottom: "30px",
|
||||||
paddingTop: "30px",
|
paddingTop: "30px",
|
||||||
|
position: "relative",
|
||||||
backgroundColor: theme.palette.surfaceColor,
|
backgroundColor: theme.palette.surfaceColor,
|
||||||
}}>
|
}}>
|
||||||
|
<div style={{position: "absolute", top: -imgsize/2-10, left: 250-imgsize/2, height: imgsize, width: imgsize, }}>
|
||||||
|
<img src="images/Shuffle_logo.png" style={{height: imgsize+10, width: imgsize+10, border: "2px solid rgba(255,255,255,0.6)", borderRadius: imgsize,}}/>
|
||||||
|
</div>
|
||||||
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px", color: "white", }}>
|
<form onSubmit={onSubmit} style={{ margin: "15px 15px 15px 15px", color: "white", }}>
|
||||||
<h2>{formtitle}</h2>
|
<h2>{formtitle}</h2>
|
||||||
Username
|
Username
|
||||||
@@ -225,8 +233,9 @@ const LoginDialog = props => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", marginTop: "15px" }}>
|
<div style={{ display: "flex", marginTop: "15px" }}>
|
||||||
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", marginRight: "5px" }} disabled={!handleValidateForm()}>SUBMIT</Button>
|
<Button color="primary" variant="contained" type="submit" style={{ flex: "1", marginRight: "5px" }} disabled={!handleValidateForm() || loginLoading}>
|
||||||
|
{loginLoading ? <CircularProgress color="secondary" style={{color: "white",}} /> : "SUBMIT"}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: "10px" }}>
|
<div style={{ marginTop: "10px" }}>
|
||||||
{loginInfo}
|
{loginInfo}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const Settings = (props) => {
|
|||||||
|
|
||||||
const [userInfo, ] = useState(userdata)
|
const [userInfo, ] = useState(userdata)
|
||||||
const [userSettings, setUserSettings] = useState({})
|
const [userSettings, setUserSettings] = useState({})
|
||||||
|
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
|
||||||
|
|
||||||
const bodyDivStyle = {
|
const bodyDivStyle = {
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
@@ -421,7 +422,7 @@ const Settings = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
disabled={(newPassword.length < 10 || newPassword2.length < 10) || newPassword !== newPassword2 || currentPassword.length < 10}
|
disabled={(isCloud && (newPassword.length < 10 || newPassword2.length < 10 || currentPassword.length < 10)) || newPassword !== newPassword2 || newPassword.length === 0}
|
||||||
style={{width: "100%", height: "60px", marginTop: "10px"}}
|
style={{width: "100%", height: "60px", marginTop: "10px"}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { useInterval } from 'react-powerhooks';
|
|||||||
import { makeStyles } from '@material-ui/core/styles';
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
import {Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core';
|
import {Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core';
|
||||||
import {FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons';
|
import {Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons';
|
||||||
//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
|
//import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons';
|
||||||
|
|
||||||
|
//https://next.material-ui.com/components/material-icons/
|
||||||
import {DataGrid, GridToolbarContainer, GridDensitySelector, GridToolbar} from '@material-ui/data-grid';
|
import {DataGrid, GridToolbarContainer, GridDensitySelector, GridToolbar} from '@material-ui/data-grid';
|
||||||
|
|
||||||
//import JSONPretty from 'react-json-pretty';
|
//import JSONPretty from 'react-json-pretty';
|
||||||
@@ -25,6 +26,7 @@ import CytoscapeWrapper from '../components/RenderCytoscape'
|
|||||||
|
|
||||||
const inputColor = "#383B40"
|
const inputColor = "#383B40"
|
||||||
const surfaceColor = "#27292D"
|
const surfaceColor = "#27292D"
|
||||||
|
const svgSize = 24
|
||||||
|
|
||||||
const flexContainerStyle = {
|
const flexContainerStyle = {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -75,6 +77,196 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
// Takes an action in Shuffle and
|
||||||
|
// Returns information about the icon, the color etc to be used
|
||||||
|
// This can be used for actions of all types
|
||||||
|
export const GetIconInfo = (action) => {
|
||||||
|
// Finds the icon based on the action. Should be verbs.
|
||||||
|
const iconList = [
|
||||||
|
{"key": "cache_add", "values": ["set_cache"]},
|
||||||
|
{"key": "cache_get", "values": ["get_cache"]},
|
||||||
|
{"key": "filter", "values": ["filter", "route", "router",]},
|
||||||
|
{"key": "merge", "values": ["join", "merge"]},
|
||||||
|
{"key": "search", "values": ["search", "find", "locate", "index",]},
|
||||||
|
{"key": "list", "values": ["list", "head", "options"]},
|
||||||
|
{"key": "download", "values": ["get", "download", "return", "hello_world", "curl",]},
|
||||||
|
{"key": "add", "values": ["add"]},
|
||||||
|
{"key": "delete", "values": ["delete", "remove", "clear", "clean",]},
|
||||||
|
{"key": "send", "values": ["send", "dispatch", "mail", "forward", "post", "submit", "mark", "set"]},
|
||||||
|
{"key": "repeat", "values": ["repeat", "retry", "pause", "skip",]},
|
||||||
|
{"key": "execute", "values": ["execute", "run", "play", "raise",]},
|
||||||
|
{"key": "extract", "values": ["extract", "unpack", "decompress", "open"]},
|
||||||
|
{"key": "inflate", "values": ["inflate", "pack", "compress",]},
|
||||||
|
{"key": "edit", "values": ["update", "create", "edit", "put", "patch", "change", "replace", "conver", "map", "format", "escape"]},
|
||||||
|
{"key": "compare", "values": ["compare", "convert", "to", "filter", "translate", "parse"]},
|
||||||
|
{"key": "close", "values": ["close", "stop", "cancel",]},
|
||||||
|
]
|
||||||
|
|
||||||
|
var selectedKey = ""
|
||||||
|
if (action.name === undefined || action.name === null) {
|
||||||
|
} else {
|
||||||
|
const actionname = action.name.toLowerCase()
|
||||||
|
for (var key in iconList) {
|
||||||
|
//console.log(iconList[key], actionname)
|
||||||
|
const found = iconList[key].values.find(value => actionname.includes(value))
|
||||||
|
if (found !== null && found !== undefined) {
|
||||||
|
selectedKey = iconList[key].key
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some of these are manually parsed or created instead of material ui
|
||||||
|
//M8 0C3.58 0 0 1.79 0 4C0 6.21 3.58 8 8 8C12.42 8 16 6.21 16 4C16 1.79 12.42 0 8 0ZM0 6V9C0 11.21 3.58 13 8 13C12.42 13 16 11.21 16 9V6C16 8.21 12.42 10 8 10C3.58 10 0 8.21 0 6ZM0 11V14C0 16.21 3.58 18 8 18C9.41 18 10.79 17.81 12 17.46V14.46C10.79 14.81 9.41 15 8 15C3.58 15 0 13.21 0 11ZM17 11V14H14V16H17V19H19V16H22V14H19V11
|
||||||
|
//https://www.figma.com/file/uCfnMs5w6wnLx6ehPHEV74/Figma-Material-Design-System-v3_0?node-id=834%3A21
|
||||||
|
//COLORS: https://www.pinterest.co.uk/pin/326299935499972946/
|
||||||
|
const defaultColor = "#f76b1c"
|
||||||
|
const defaultGradient = ["#fad961", "#f76b1c"]
|
||||||
|
const parsedIcons = {
|
||||||
|
"cache_add": {
|
||||||
|
"icon": "M11 3C6.58 3 3 4.79 3 7C3 9.21 6.58 11 11 11C15.42 11 19 9.21 19 7C19 4.79 15.42 3 11 3ZM3 9V12C3 14.21 6.58 16 11 16C15.42 16 19 14.21 19 12V9C19 11.21 15.42 13 11 13C6.58 13 3 11.21 3 9ZM3 14V17C3 19.21 6.58 21 11 21C12.41 21 13.79 20.81 15 20.46V17.46C13.79 17.81 12.41 18 11 18C6.58 18 3 16.21 3 14ZM20 14V17H17V19H20V22H22V19H25V17H22V14",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#8acc3f",
|
||||||
|
"originalIcon": "",
|
||||||
|
"fillGradient": ["#8acc3f", "#459622"]
|
||||||
|
},
|
||||||
|
"cache_get": {
|
||||||
|
"icon": "M12 2C7.58 2 4 3.79 4 6C4 8.06 7.13 9.74 11.15 9.96C12.45 8.7 14.19 8 16 8C16.8 8 17.59 8.14 18.34 8.41C19.37 7.74 20 6.91 20 6C20 3.79 16.42 2 12 2ZM4 8V11C4 12.68 6.08 14.11 9 14.71C9.06 13.7 9.32 12.72 9.77 11.82C6.44 11.34 4 9.82 4 8ZM15.93 9.94C14.75 9.95 13.53 10.4 12.46 11.46C8.21 15.71 13.71 22.5 18.75 19.17L23.29 23.71L24.71 22.29L20.17 17.75C22.66 13.97 19.47 9.93 15.93 9.94ZM15.9 12C17.47 11.95 19 13.16 19 15C19 15.7956 18.6839 16.5587 18.1213 17.1213C17.5587 17.6839 16.7956 18 16 18C13.33 18 12 14.77 13.88 12.88C14.47 12.29 15.19 12 15.9 12ZM4 13V16C4 18.05 7.09 19.72 11.06 19.95C10.17 19.07 9.54 17.95 9.22 16.74C6.18 16.17 4 14.72 4 13Z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#8acc3f",
|
||||||
|
"originalIcon": "",
|
||||||
|
"fillGradient": ["#8acc3f", "#459622"]
|
||||||
|
},
|
||||||
|
"repeat": {
|
||||||
|
"icon": "M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <CachedIcon />,
|
||||||
|
},
|
||||||
|
"add": {
|
||||||
|
"icon": "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <AddIcon />,
|
||||||
|
},
|
||||||
|
"edit": {
|
||||||
|
"icon": "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <EditIcon />,
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"icon": "M4.25 5.61C6.27 8.2 10 13 10 13v6c0 .55.45 1 1 1h2c.55 0 1-.45 1-1v-6s3.72-4.8 5.74-7.39c.51-.66.04-1.61-.79-1.61H5.04c-.83 0-1.3.95-.79 1.61z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#f5515f",
|
||||||
|
"originalIcon": "",
|
||||||
|
"fillGradient": ["#f5515f", "#a1051d"],
|
||||||
|
},
|
||||||
|
"merge": {
|
||||||
|
"icon": "M17 20.41 18.41 19 15 15.59 13.59 17 17 20.41zM7.5 8H11v5.59L5.59 19 7 20.41l6-6V8h3.5L12 3.5 7.5 8z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#f5515f",
|
||||||
|
"originalIcon": "",
|
||||||
|
"fillGradient": ["#f5515f", "#a1051d"],
|
||||||
|
},
|
||||||
|
"compare": {
|
||||||
|
"icon": "M10 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h5v2h2V1h-2v2zm0 15H5l5-6v6zm9-15h-5v2h5v13l-5-6v9h5c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <CompareIcon />,
|
||||||
|
},
|
||||||
|
"extract": {
|
||||||
|
"icon": "M3 3h18v2H3z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <MaximizeIcon />,
|
||||||
|
},
|
||||||
|
"inflate": {
|
||||||
|
"icon": "M6 19h12v2H6z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <MinimizeIcon />,
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
"icon": "M3 9h14V7H3v2zm0 4h14v-2H3v2zm0 4h14v-2H3v2zm16 0h2v-2h-2v2zm0-10v2h2V7h-2zm0 6h2v-2h-2v2z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <TocIcon />,
|
||||||
|
},
|
||||||
|
"execute": {
|
||||||
|
"icon": "M8 5v14l11-7z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": defaultColor,
|
||||||
|
"originalIcon": <PlayArrowIcon />,
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"icon": "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#03030e",
|
||||||
|
"originalIcon": <DeleteIcon />,
|
||||||
|
"fillGradient": ["#03030e", "#205d66"]
|
||||||
|
},
|
||||||
|
"close": {
|
||||||
|
"icon": "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#03030e",
|
||||||
|
"originalIcon": <CloseIcon />,
|
||||||
|
"fillGradient": ["#03030e", "#205d66"]
|
||||||
|
},
|
||||||
|
"send": {
|
||||||
|
"icon": "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#0373da",
|
||||||
|
"originalIcon": <SendIcon />,
|
||||||
|
"fillGradient": ["#0bc8bf", "#0373da"]
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"icon": "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "#0373da",
|
||||||
|
"originalIcon": <GetAppIcon />,
|
||||||
|
"fillGradient": ["#0bc8bf", "#0373da"]
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"icon": "M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z",
|
||||||
|
"iconColor": "white",
|
||||||
|
"iconBackgroundColor": "green",
|
||||||
|
"originalIcon": <SearchIcon />,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var selectedItem = parsedIcons[selectedKey]
|
||||||
|
if (selectedItem === undefined || selectedItem === null) {
|
||||||
|
return {
|
||||||
|
"icon": "",
|
||||||
|
"iconColor": "",
|
||||||
|
"iconBackground": "black",
|
||||||
|
"originalIcon": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedItem.fillGradient === undefined) {
|
||||||
|
selectedItem.fillGradient = defaultGradient
|
||||||
|
selectedItem.iconBackgroundColor = defaultColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedItem.icon === "" || selectedItem.icon === undefined) {
|
||||||
|
console.log(`MISSING PATH FOR ${selectedKey} (find in scope): `, selectedItem.originalIcon.type.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedItem.originalIcon === undefined || selectedItem.originalIcon === "" && (selectedItem.icon !== "" && selectedItem.icon !== undefined)) {
|
||||||
|
const svg_pin = <svg width={svgSize} height={svgSize} viewBox={`0 0 ${svgSize} ${svgSize}`} version="1.1" xmlns="http://www.w3.org/2000/svg"><path d={selectedItem.icon} fill={selectedItem.iconColor}></path></svg>
|
||||||
|
//const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin)
|
||||||
|
selectedItem.originalIcon = svg_pin
|
||||||
|
//parsedIcons[selectedKey].originalIcon = svg_pin
|
||||||
|
//console.log("ADDED ICON: ", svg_pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedItem
|
||||||
|
}
|
||||||
|
|
||||||
//const activeWorkflowStyle = {backgroundColor: "#FFF5EE"}
|
//const activeWorkflowStyle = {backgroundColor: "#FFF5EE"}
|
||||||
//const notificationStyle = {backgroundColor: "#E5F9FF"}
|
//const notificationStyle = {backgroundColor: "#E5F9FF"}
|
||||||
//const activeWorkflowStyle = {backgroundColor: "#3d3f43"}
|
//const activeWorkflowStyle = {backgroundColor: "#3d3f43"}
|
||||||
@@ -87,7 +279,7 @@ const counterStyle = {fontSize: "36px",fontWeight:"bold"}
|
|||||||
const blockRightStyle = {textAlign: "right",padding: "20px 20px 0px 0px",width:"100%"}
|
const blockRightStyle = {textAlign: "right",padding: "20px 20px 0px 0px",width:"100%"}
|
||||||
|
|
||||||
const chipStyle = {
|
const chipStyle = {
|
||||||
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
|
backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",
|
||||||
}
|
}
|
||||||
|
|
||||||
const flexContentStyle = {
|
const flexContentStyle = {
|
||||||
@@ -136,6 +328,7 @@ export const validateJson = (showResult) => {
|
|||||||
const Workflows = (props) => {
|
const Workflows = (props) => {
|
||||||
const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies, userdata} = props;
|
const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies, userdata} = props;
|
||||||
document.title = "Shuffle - Workflows"
|
document.title = "Shuffle - Workflows"
|
||||||
|
const referenceUrl = globalUrl+"/api/v1/hooks/"
|
||||||
|
|
||||||
const alert = useAlert()
|
const alert = useAlert()
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -166,6 +359,7 @@ const Workflows = (props) => {
|
|||||||
const [newWorkflowTags, setNewWorkflowTags] = React.useState([]);
|
const [newWorkflowTags, setNewWorkflowTags] = React.useState([]);
|
||||||
const [update, setUpdate] = React.useState("test");
|
const [update, setUpdate] = React.useState("test");
|
||||||
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
|
||||||
|
const [publishModalOpen, setPublishModalOpen] = React.useState(false);
|
||||||
const [editingWorkflow, setEditingWorkflow] = React.useState({})
|
const [editingWorkflow, setEditingWorkflow] = React.useState({})
|
||||||
const [executionLoading, setExecutionLoading] = React.useState(false)
|
const [executionLoading, setExecutionLoading] = React.useState(false)
|
||||||
const [importLoading, setImportLoading] = React.useState(false)
|
const [importLoading, setImportLoading] = React.useState(false)
|
||||||
@@ -188,8 +382,29 @@ const Workflows = (props) => {
|
|||||||
if (curWorkflow.tags === undefined || curWorkflow.tags === null) {
|
if (curWorkflow.tags === undefined || curWorkflow.tags === null) {
|
||||||
found = filters.map(filter => curWorkflow.name.toLowerCase().includes(filter))
|
found = filters.map(filter => curWorkflow.name.toLowerCase().includes(filter))
|
||||||
} else {
|
} else {
|
||||||
found = filters.map(filter => curWorkflow.name.toLowerCase().includes(filter.toLowerCase()) || curWorkflow.tags.includes(filter))
|
found = filters.map(filter => {
|
||||||
|
if (filter === undefined) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curWorkflow.name.toLowerCase().includes(filter.toLowerCase())) {
|
||||||
|
return true
|
||||||
|
} else if (curWorkflow.tags.includes(filter)) {
|
||||||
|
return true
|
||||||
|
} else if (curWorkflow.actions !== null && curWorkflow.actions !== undefined) {
|
||||||
|
const newfilter = filter.toLowerCase()
|
||||||
|
for (var key in curWorkflow.actions) {
|
||||||
|
const action = curWorkflow.actions[key]
|
||||||
|
if (action.app_name.toLowerCase().includes(newfilter)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log("FOUND: ", found)
|
//console.log("FOUND: ", found)
|
||||||
//if (found) {
|
//if (found) {
|
||||||
if (found.every(v => v === true)) {
|
if (found.every(v => v === true)) {
|
||||||
@@ -246,6 +461,49 @@ const Workflows = (props) => {
|
|||||||
findWorkflow(newfilters)
|
findWorkflow(newfilters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const publishModal = publishModalOpen ?
|
||||||
|
<Dialog
|
||||||
|
open={publishModalOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setPublishModalOpen(false)
|
||||||
|
setSelectedWorkflow({})
|
||||||
|
}}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
backgroundColor: surfaceColor,
|
||||||
|
color: "white",
|
||||||
|
minWidth: 500,
|
||||||
|
padding: 50,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle style={{marginBottom: 0, }}>
|
||||||
|
<div style={{textAlign: "center", color: "rgba(255,255,255,0.9)"}}>
|
||||||
|
Are you sure you want to PUBLISH this workflow?
|
||||||
|
</div>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent style={{color: "rgba(255,255,255,0.65)", textAlign: "center"}}>
|
||||||
|
<div>
|
||||||
|
<Typography variant="body1" style={{marginBottom: 20,}}>
|
||||||
|
Before publishing, we will sanitize all inputs, remove references to you, randomize ID's and remove your authentication.
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<Button variant="contained" style={{}} onClick={() => {
|
||||||
|
publishWorkflow(selectedWorkflow)
|
||||||
|
setPublishModalOpen(false)
|
||||||
|
}} color="primary">
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
<Button style={{}} onClick={() => {
|
||||||
|
setPublishModalOpen(false)
|
||||||
|
}} color="primary">
|
||||||
|
No
|
||||||
|
</Button>
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
</Dialog>
|
||||||
|
: null
|
||||||
|
|
||||||
const deleteModal = deleteModalOpen ?
|
const deleteModal = deleteModalOpen ?
|
||||||
<Dialog
|
<Dialog
|
||||||
open={deleteModalOpen}
|
open={deleteModalOpen}
|
||||||
@@ -263,7 +521,7 @@ const Workflows = (props) => {
|
|||||||
>
|
>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<div style={{textAlign: "center", color: "rgba(255,255,255,0.9)"}}>
|
<div style={{textAlign: "center", color: "rgba(255,255,255,0.9)"}}>
|
||||||
Are you sure? <div/>Other workflows relying on this one may stop working
|
Are you sure you want to delete this workflow? <div/>Other workflows relying on this one may stop working
|
||||||
</div>
|
</div>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent style={{color: "rgba(255,255,255,0.65)", textAlign: "center"}}>
|
<DialogContent style={{color: "rgba(255,255,255,0.65)", textAlign: "center"}}>
|
||||||
@@ -271,7 +529,9 @@ const Workflows = (props) => {
|
|||||||
console.log("Editing: ", editingWorkflow)
|
console.log("Editing: ", editingWorkflow)
|
||||||
if (selectedWorkflowId) {
|
if (selectedWorkflowId) {
|
||||||
deleteWorkflow(selectedWorkflowId)
|
deleteWorkflow(selectedWorkflowId)
|
||||||
getAvailableWorkflows()
|
setTimeout(() => {
|
||||||
|
getAvailableWorkflows()
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
setDeleteModalOpen(false)
|
setDeleteModalOpen(false)
|
||||||
}} color="primary">
|
}} color="primary">
|
||||||
@@ -314,7 +574,7 @@ const Workflows = (props) => {
|
|||||||
const ret = setNewWorkflow(data.name, data.description, data.tags, data, false)
|
const ret = setNewWorkflow(data.name, data.description, data.tags, data, false)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response !== undefined) {
|
if (response !== undefined) {
|
||||||
alert.success("Successfully imported "+data.name)
|
alert.success(`Successfully imported ${data.name}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -438,6 +698,8 @@ const Workflows = (props) => {
|
|||||||
|
|
||||||
const paperAppStyle = {
|
const paperAppStyle = {
|
||||||
minHeight: 130,
|
minHeight: 130,
|
||||||
|
maxHeight: 130,
|
||||||
|
overflow: "hidden",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
color: "white",
|
color: "white",
|
||||||
backgroundColor: surfaceColor,
|
backgroundColor: surfaceColor,
|
||||||
@@ -448,6 +710,7 @@ const Workflows = (props) => {
|
|||||||
position: "relative",
|
position: "relative",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const gridContainer = {
|
const gridContainer = {
|
||||||
height: "auto",
|
height: "auto",
|
||||||
color: "white",
|
color: "white",
|
||||||
@@ -480,7 +743,7 @@ const Workflows = (props) => {
|
|||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (!responseJson.success) {
|
if (!responseJson.success) {
|
||||||
alert.error(responseJson.reason)
|
alert.error(responseJson.reason)
|
||||||
}
|
}
|
||||||
@@ -500,9 +763,7 @@ const Workflows = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sanitizeWorkflow = (data) => {
|
const deduplicateIds = (data) => {
|
||||||
data["owner"] = ""
|
|
||||||
console.log("Sanitize start: ", data)
|
|
||||||
if (data.triggers !== null && data.triggers !== undefined) {
|
if (data.triggers !== null && data.triggers !== undefined) {
|
||||||
for (var key in data.triggers) {
|
for (var key in data.triggers) {
|
||||||
const trigger = data.triggers[key]
|
const trigger = data.triggers[key]
|
||||||
@@ -517,15 +778,27 @@ const Workflows = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newId = uuid.v4()
|
const newId = uuid.v4()
|
||||||
|
if (trigger.trigger_type === "WEBHOOK") {
|
||||||
|
const hookname = "webhook_"+newId
|
||||||
|
if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length === 2) {
|
||||||
|
trigger.parameters[0].value = referenceUrl+"webhook_"+trigger.id
|
||||||
|
trigger.parameters[1].value = "webhook_"+trigger.id
|
||||||
|
} else if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length === 3) {
|
||||||
|
trigger.parameters[0].value = referenceUrl+"webhook_"+trigger.id
|
||||||
|
trigger.parameters[1].value = "webhook_"+trigger.id
|
||||||
|
// FIXME: Add auth here?
|
||||||
|
} else {
|
||||||
|
alert.info("Something is wrong with the webhook in the copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var branchkey in data.branches) {
|
for (var branchkey in data.branches) {
|
||||||
const branch = data.branches[branchkey]
|
const branch = data.branches[branchkey]
|
||||||
if (branch.source_id === trigger.id) {
|
if (branch.source_id === trigger.id) {
|
||||||
//console.log("CHANGING SOURCE ID")
|
|
||||||
branch.source_id = newId
|
branch.source_id = newId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (branch.destination_id === trigger.id) {
|
if (branch.destination_id === trigger.id) {
|
||||||
//console.log("CHANGING DESTINATION ID")
|
|
||||||
branch.destination_id = newId
|
branch.destination_id = newId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -541,7 +814,7 @@ const Workflows = (props) => {
|
|||||||
|
|
||||||
for (var subkey in data.actions[key].parameters) {
|
for (var subkey in data.actions[key].parameters) {
|
||||||
const param = data.actions[key].parameters[subkey]
|
const param = data.actions[key].parameters[subkey]
|
||||||
if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret")) {
|
if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret") || param.name.includes("domain") || param.name.includes("url") || param.name.includes("mail")) {
|
||||||
// FIXME: This may be a vuln if api-keys are generated that start with $
|
// FIXME: This may be a vuln if api-keys are generated that start with $
|
||||||
if (param.value.startsWith("$")) {
|
if (param.value.startsWith("$")) {
|
||||||
console.log("Skipping field, as it's referencing a variable")
|
console.log("Skipping field, as it's referencing a variable")
|
||||||
@@ -556,16 +829,18 @@ const Workflows = (props) => {
|
|||||||
for (var branchkey in data.branches) {
|
for (var branchkey in data.branches) {
|
||||||
const branch = data.branches[branchkey]
|
const branch = data.branches[branchkey]
|
||||||
if (branch.source_id === data.actions[key].id) {
|
if (branch.source_id === data.actions[key].id) {
|
||||||
//console.log("CHANGING SOURCE ID IN ACTION")
|
|
||||||
branch.source_id = newId
|
branch.source_id = newId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (branch.destination_id === data.actions[key].id) {
|
if (branch.destination_id === data.actions[key].id) {
|
||||||
//console.log("CHANGING DESTINATION ID IN ACTION")
|
|
||||||
branch.destination_id = newId
|
branch.destination_id = newId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.actions[key].id === data.start) {
|
||||||
|
data.start = newId
|
||||||
|
}
|
||||||
|
|
||||||
//data.actions[key].environment = isCloud ? "cloud" : "Shuffle"
|
//data.actions[key].environment = isCloud ? "cloud" : "Shuffle"
|
||||||
data.actions[key].environment = ""
|
data.actions[key].environment = ""
|
||||||
data.actions[key].id = newId
|
data.actions[key].id = newId
|
||||||
@@ -575,15 +850,20 @@ const Workflows = (props) => {
|
|||||||
if (data.workflow_variables !== null && data.workflow_variables !== undefined) {
|
if (data.workflow_variables !== null && data.workflow_variables !== undefined) {
|
||||||
for (var key in data.workflow_variables) {
|
for (var key in data.workflow_variables) {
|
||||||
const param = data.workflow_variables[key]
|
const param = data.workflow_variables[key]
|
||||||
if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret")) {
|
if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret") || param.name.includes("email")) {
|
||||||
param.value = ""
|
param.value = ""
|
||||||
param.is_valid = false
|
param.is_valid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(data)
|
return data
|
||||||
//return
|
}
|
||||||
|
|
||||||
|
const sanitizeWorkflow = (data) => {
|
||||||
|
data["owner"] = ""
|
||||||
|
console.log("Sanitize start: ", data)
|
||||||
|
data = deduplicateIds(data)
|
||||||
|
|
||||||
data["org"] = []
|
data["org"] = []
|
||||||
data["org_id"] = ""
|
data["org_id"] = ""
|
||||||
@@ -600,10 +880,11 @@ const Workflows = (props) => {
|
|||||||
const exportWorkflow = (data) => {
|
const exportWorkflow = (data) => {
|
||||||
let exportFileDefaultName = data.name+'.json';
|
let exportFileDefaultName = data.name+'.json';
|
||||||
data = sanitizeWorkflow(data)
|
data = sanitizeWorkflow(data)
|
||||||
|
|
||||||
//console.log("EXPORT: ", data)
|
|
||||||
//return
|
//return
|
||||||
|
|
||||||
|
// Add correct ID's for triggers
|
||||||
|
// Add mag
|
||||||
|
|
||||||
let dataStr = JSON.stringify(data)
|
let dataStr = JSON.stringify(data)
|
||||||
let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
||||||
let linkElement = document.createElement('a');
|
let linkElement = document.createElement('a');
|
||||||
@@ -657,7 +938,8 @@ const Workflows = (props) => {
|
|||||||
alert.success("Copying workflow "+data.name)
|
alert.success("Copying workflow "+data.name)
|
||||||
data.id = ""
|
data.id = ""
|
||||||
data.name = data.name+"_copy"
|
data.name = data.name+"_copy"
|
||||||
console.log("COPIED DATA: ", data)
|
data = deduplicateIds(data)
|
||||||
|
//console.log("COPIED DATA: ", data)
|
||||||
//return
|
//return
|
||||||
|
|
||||||
fetch(globalUrl+"/api/v1/workflows", {
|
fetch(globalUrl+"/api/v1/workflows", {
|
||||||
@@ -677,7 +959,9 @@ const Workflows = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
getAvailableWorkflows()
|
setTimeout(() => {
|
||||||
|
getAvailableWorkflows()
|
||||||
|
}, 1000)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
alert.error(error.toString())
|
alert.error(error.toString())
|
||||||
@@ -705,7 +989,9 @@ const Workflows = (props) => {
|
|||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
getAvailableWorkflows()
|
setTimeout(() => {
|
||||||
|
getAvailableWorkflows()
|
||||||
|
}, 1000)
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
alert.error(error.toString())
|
alert.error(error.toString())
|
||||||
@@ -717,6 +1003,39 @@ const Workflows = (props) => {
|
|||||||
addFilter(e.target.innerHTML)
|
addFilter(e.target.innerHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const NewWorkflowPaper = (props) => {
|
||||||
|
const [hover, setHover] = React.useState(false)
|
||||||
|
|
||||||
|
const innerColor = "rgba(255,255,255,0.3)"
|
||||||
|
const setupPaperStyle = {
|
||||||
|
minHeight: paperAppStyle.minHeight,
|
||||||
|
width: paperAppStyle.width,
|
||||||
|
color: innerColor,
|
||||||
|
padding: paperAppStyle.padding,
|
||||||
|
borderRadius: paperAppStyle.borderRadius,
|
||||||
|
display: "flex",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
position: "relative",
|
||||||
|
border: `2px solid ${innerColor}`,
|
||||||
|
cursor: "pointer",
|
||||||
|
backgroundColor: hover ? "rgba(39,41,45,0.5)" : "rgba(39,41,45,1)",
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Grid item xs={4} style={{padding: "12px 10px 12px 10px",}}>
|
||||||
|
<Paper square style={setupPaperStyle} onClick={() => setModalOpen(true)} onMouseOver={() => {setHover(true)}} onMouseOut={() => {setHover(false)}}>
|
||||||
|
<Tooltip title={`New Workflow`} placement="bottom">
|
||||||
|
<span style={{textAlign: "center", width: 300, margin: "auto",}}>
|
||||||
|
<AddCircleIcon style={{height: 65, width: 65, }}/>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const WorkflowPaper = (props) => {
|
const WorkflowPaper = (props) => {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
@@ -888,7 +1207,8 @@ const Workflows = (props) => {
|
|||||||
{"Change details"}
|
{"Change details"}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
|
||||||
publishWorkflow(data)
|
setSelectedWorkflow(data)
|
||||||
|
setPublishModalOpen(true)
|
||||||
}} key={"publish"}>
|
}} key={"publish"}>
|
||||||
<CloudUploadIcon style={{marginLeft: 0, marginRight: 8}}/>
|
<CloudUploadIcon style={{marginLeft: 0, marginRight: 8}}/>
|
||||||
{"Publish Workflow"}
|
{"Publish Workflow"}
|
||||||
@@ -956,7 +1276,7 @@ const Workflows = (props) => {
|
|||||||
|
|
||||||
const resultsPaper = (data) => {
|
const resultsPaper = (data) => {
|
||||||
var boxWidth = "2px"
|
var boxWidth = "2px"
|
||||||
var boxColor = "orange"
|
var boxColor = "orange"
|
||||||
if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){
|
if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){
|
||||||
boxColor = "red"
|
boxColor = "red"
|
||||||
} else if (data.status === "FINISHED" || data.status === "SUCCESS") {
|
} else if (data.status === "FINISHED" || data.status === "SUCCESS") {
|
||||||
@@ -1177,7 +1497,9 @@ const Workflows = (props) => {
|
|||||||
window.location.pathname = "/workflows/"+responseJson["id"]
|
window.location.pathname = "/workflows/"+responseJson["id"]
|
||||||
} else if (!redirect) {
|
} else if (!redirect) {
|
||||||
// Update :)
|
// Update :)
|
||||||
getAvailableWorkflows()
|
setTimeout(() => {
|
||||||
|
getAvailableWorkflows()
|
||||||
|
}, 1000)
|
||||||
setImportLoading(false)
|
setImportLoading(false)
|
||||||
} else {
|
} else {
|
||||||
alert.info("Successfully changed basic info for workflow")
|
alert.info("Successfully changed basic info for workflow")
|
||||||
@@ -1287,7 +1609,8 @@ const Workflows = (props) => {
|
|||||||
const data = params.row.record;
|
const data = params.row.record;
|
||||||
let [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data);
|
let [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data);
|
||||||
|
|
||||||
return <Grid item>
|
return (
|
||||||
|
<Grid item>
|
||||||
<Link to={"/workflows/"+data.id}>
|
<Link to={"/workflows/"+data.id}>
|
||||||
<EditIcon style={{background: "#F85A3E",
|
<EditIcon style={{background: "#F85A3E",
|
||||||
boxShadow: "0px 1px 2px rgba(0, 0, 0, 0.16), 0px 2px 4px rgba(0, 0, 0, 0.12), 0px 1px 8px rgba(0, 0, 0, 0.1)",
|
boxShadow: "0px 1px 2px rgba(0, 0, 0, 0.16), 0px 2px 4px rgba(0, 0, 0, 0.12), 0px 1px 8px rgba(0, 0, 0, 0.1)",
|
||||||
@@ -1313,13 +1636,15 @@ const Workflows = (props) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
: null}
|
: null}
|
||||||
</Grid>
|
</Grid>
|
||||||
}
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ field: 'tags', headerName: 'Tags', width: 390, sortable: false,
|
{ field: 'tags', headerName: 'Tags', maxHeight: 15, width: 390, sortable: false,
|
||||||
disableClickEventBubbling: true,
|
disableClickEventBubbling: true,
|
||||||
renderCell: (params) => {
|
renderCell: (params) => {
|
||||||
const data = params.row.record;
|
const data = params.row.record;
|
||||||
return <Grid item>
|
return (
|
||||||
|
<Grid item>
|
||||||
{data.tags !== undefined ?
|
{data.tags !== undefined ?
|
||||||
data.tags.map((tag, index) => {
|
data.tags.map((tag, index) => {
|
||||||
if (index >= 3) {
|
if (index >= 3) {
|
||||||
@@ -1338,17 +1663,24 @@ const Workflows = (props) => {
|
|||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let rows = [];
|
let rows = [];
|
||||||
rows = workflows.map((data, index) => {
|
rows = workflows.map((data, index) => {
|
||||||
let obj = {"id":index+1, "title":data.name, "record":data,};
|
let obj = {
|
||||||
|
"id":index+1,
|
||||||
|
"title":data.name,
|
||||||
|
"record":data,
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
});
|
});
|
||||||
workflowData = <DataGrid color="primary" className={classes.root} rows={rows} columns={columns} pageSize={5} checkboxSelection autoHeight density="standard" components={{
|
workflowData =
|
||||||
Toolbar: GridToolbar,
|
<DataGrid color="primary" className={classes.root} rows={rows} columns={columns} pageSize={5} checkboxSelection autoHeight density="standard" components={{
|
||||||
}} />
|
Toolbar: GridToolbar,
|
||||||
|
}} />
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div style={gridContainer}>
|
<div style={gridContainer}>
|
||||||
@@ -1406,6 +1738,8 @@ const Workflows = (props) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
defaultValue={newWorkflowDescription}
|
defaultValue={newWorkflowDescription}
|
||||||
placeholder="Description"
|
placeholder="Description"
|
||||||
|
rows="6"
|
||||||
|
multiline
|
||||||
margin="dense"
|
margin="dense"
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
@@ -1481,11 +1815,11 @@ const Workflows = (props) => {
|
|||||||
|
|
||||||
const workflowButtons =
|
const workflowButtons =
|
||||||
<span>
|
<span>
|
||||||
{workflows.length > 0 ?
|
{/*workflows.length > 0 ?
|
||||||
<Tooltip color="primary" title={"Create new workflow"} placement="top">
|
<Tooltip color="primary" title={"Create new workflow"} placement="top">
|
||||||
<Button color="primary" style={{}} variant="text" onClick={() => setModalOpen(true)}><AddIcon /></Button>
|
<Button color="primary" style={{}} variant="text" onClick={() => setModalOpen(true)}><AddIcon /></Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
: null}
|
: null*/}
|
||||||
<Tooltip color="primary" title={"Import workflows"} placement="top">
|
<Tooltip color="primary" title={"Import workflows"} placement="top">
|
||||||
{importLoading ?
|
{importLoading ?
|
||||||
<Button color="primary" style={{}} variant="text" onClick={() => {}}>
|
<Button color="primary" style={{}} variant="text" onClick={() => {}}>
|
||||||
@@ -1636,6 +1970,7 @@ const Workflows = (props) => {
|
|||||||
<div style={{marginTop: 15,}} />
|
<div style={{marginTop: 15,}} />
|
||||||
{view === "grid" && (
|
{view === "grid" && (
|
||||||
<Grid container spacing={4} style={paperAppContainer}>
|
<Grid container spacing={4} style={paperAppContainer}>
|
||||||
|
<NewWorkflowPaper />
|
||||||
{filteredWorkflows.map((data, index) => {
|
{filteredWorkflows.map((data, index) => {
|
||||||
return (
|
return (
|
||||||
<WorkflowPaper key={index} data={data} />
|
<WorkflowPaper key={index} data={data} />
|
||||||
@@ -1644,9 +1979,9 @@ const Workflows = (props) => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{view === "list" && (
|
{/*view === "list" && (
|
||||||
<WorkflowGridView />
|
<WorkflowGridView />
|
||||||
)}
|
)*/}
|
||||||
|
|
||||||
<div style={{marginBottom: 100}}/>
|
<div style={{marginBottom: 100}}/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1684,7 +2019,9 @@ const Workflows = (props) => {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
alert.success("Successfully loaded workflows from "+downloadUrl)
|
alert.success("Successfully loaded workflows from "+downloadUrl)
|
||||||
getAvailableWorkflows()
|
setTimeout(() => {
|
||||||
|
getAvailableWorkflows()
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
@@ -1741,7 +2078,7 @@ const Workflows = (props) => {
|
|||||||
style={{backgroundColor: inputColor}}
|
style={{backgroundColor: inputColor}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
defaultValue={userdata.active_org.defaults.workflow_download_repo !== undefined && userdata.active_org.defaults.workflow_download_repo.length > 0 ? userdata.active_org.defaults.workflow_download_repo : downloadUrl}
|
defaultValue={downloadUrl}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style:{
|
style:{
|
||||||
color: "white",
|
color: "white",
|
||||||
@@ -1760,7 +2097,7 @@ const Workflows = (props) => {
|
|||||||
style={{backgroundColor: inputColor}}
|
style={{backgroundColor: inputColor}}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
defaultValue={userdata.active_org.defaults.workflow_download_branch !== undefined && userdata.active_org.defaults.workflow_download_branch.length > 0 ? userdata.active_org.defaults.workflow_download_branch : downloadBranch}
|
defaultValue={downloadBranch}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
style:{
|
style:{
|
||||||
color: "white",
|
color: "white",
|
||||||
@@ -1830,6 +2167,7 @@ const Workflows = (props) => {
|
|||||||
</Dropzone>
|
</Dropzone>
|
||||||
{modalView}
|
{modalView}
|
||||||
{deleteModal}
|
{deleteModal}
|
||||||
|
{publishModal}
|
||||||
{workflowDownloadModalOpen}
|
{workflowDownloadModalOpen}
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
```
|
||||||
|
curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-kafka/master/docker-compose.yml > docker-compose.yml
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
version: "2"
|
||||||
|
|
||||||
|
services:
|
||||||
|
zookeeper:
|
||||||
|
image: docker.io/bitnami/zookeeper:3
|
||||||
|
ports:
|
||||||
|
- "2181:2181"
|
||||||
|
volumes:
|
||||||
|
- "zookeeper_data:/bitnami"
|
||||||
|
environment:
|
||||||
|
- ALLOW_ANONYMOUS_LOGIN=yes
|
||||||
|
kafka:
|
||||||
|
image: docker.io/bitnami/kafka:2
|
||||||
|
ports:
|
||||||
|
- "9092:9092"
|
||||||
|
volumes:
|
||||||
|
- "kafka_data:/bitnami"
|
||||||
|
environment:
|
||||||
|
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
|
||||||
|
- ALLOW_PLAINTEXT_LISTENER=yes
|
||||||
|
depends_on:
|
||||||
|
- zookeeper
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
zookeeper_data:
|
||||||
|
driver: local
|
||||||
|
kafka_data:
|
||||||
|
driver: local
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
from time import sleep
|
||||||
|
from kafka import KafkaProducer, KafkaConsumer
|
||||||
|
import kafka
|
||||||
|
|
||||||
|
shuffle_url = os.getenv("SHUFFLE_URL")
|
||||||
|
shuffle_apikey = os.getenv("SHUFFLE_APIKEY")
|
||||||
|
shuffle_workflow = os.getenv("SHUFFLE_WORKFLOW")
|
||||||
|
|
||||||
|
headers = {"Authorization": "Bearer %s" % shuffle_apikey}
|
||||||
|
topic = "workflow_%s" % shuffle_workflow
|
||||||
|
server = "localhost:9092"
|
||||||
|
def produce():
|
||||||
|
print("Starting producer")
|
||||||
|
producer = KafkaProducer(
|
||||||
|
bootstrap_servers=[server],
|
||||||
|
value_serializer=lambda x:
|
||||||
|
json.dumps(x).encode('utf-8')
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Adding data!")
|
||||||
|
for e in range(15):
|
||||||
|
data = {"some": e, "data": "luuuuul"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
ret = producer.send(topic, value=data)
|
||||||
|
print(ret.get())
|
||||||
|
except kafka.errors.KafkaTimeoutError as e:
|
||||||
|
print("Kafka error: %s" % e)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def consume():
|
||||||
|
print("Starting consumer")
|
||||||
|
consumer = KafkaConsumer(
|
||||||
|
topic,
|
||||||
|
bootstrap_servers=[server],
|
||||||
|
auto_offset_reset="earliest",
|
||||||
|
enable_auto_commit=True,
|
||||||
|
value_deserializer=lambda x: json.loads(x.decode('utf-8'))
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Getting data")
|
||||||
|
for message in consumer:
|
||||||
|
message = message.value
|
||||||
|
print("MSG: ", message)
|
||||||
|
ret = requests.post("%s/api/v1/%s/execute" % shuffle_url, headers=headers, data=message)
|
||||||
|
print(ret.status_code)
|
||||||
|
print(ret.text)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
produce()
|
||||||
|
#consume()
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
kafka
|
||||||
|
kafka-python
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
curl -XPUT -u admin:admin https://localhost:9200/_cluster/settings -H "Content-Type:application/json" -k -d \
|
||||||
|
'{
|
||||||
|
"transient": {
|
||||||
|
"cluster.routing.allocation.disk.threshold_enabled": false
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
|
||||||
|
curl -XPUT -u admin:admin https://localhost:9200/_all/_settings -H "Content-Type: application/json" -k -d \
|
||||||
|
'{
|
||||||
|
"index.blocks.read_only_allow_delete": null
|
||||||
|
}'
|
||||||
@@ -74,8 +74,7 @@ def debug(msg):
|
|||||||
# Skips container kills to stop self-recursion
|
# Skips container kills to stop self-recursion
|
||||||
def filter_msg(alert):
|
def filter_msg(alert):
|
||||||
# These are things that recursively happen because Shuffle starts Docker containers
|
# These are things that recursively happen because Shuffle starts Docker containers
|
||||||
# Docker integration rules: https://github.com/wazuh/wazuh-ruleset/blob/ae36745db1d3f312db0392f5925c2f2b0ec009a9/rules/0560-docker_integration_rules.xml
|
skip = ["87924", "87900", "87901", "87902", "87903", "87904", "86001", "86002", "86003", "87932", "80710", "87929", "87928", "5710"]
|
||||||
skip = ["87924", "87900", "87901", "87902", "87903", "87904", "86001", "86002", "86003", "87932", "80710", "87929", "87928",]
|
|
||||||
if alert["rule"]["id"] in skip:
|
if alert["rule"]["id"] in skip:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -96,14 +95,14 @@ def generate_msg(alert):
|
|||||||
level = alert['rule']['level']
|
level = alert['rule']['level']
|
||||||
|
|
||||||
if (level <= 4):
|
if (level <= 4):
|
||||||
color = "good"
|
severity = 1
|
||||||
elif (level >= 5 and level <= 7):
|
elif (level >= 5 and level <= 7):
|
||||||
color = "warning"
|
severity = 2
|
||||||
else:
|
else:
|
||||||
color = "danger"
|
severity = 3
|
||||||
|
|
||||||
msg = {}
|
msg = {}
|
||||||
msg['color'] = color
|
msg['severity'] = severity
|
||||||
msg['pretext'] = "WAZUH Alert"
|
msg['pretext'] = "WAZUH Alert"
|
||||||
msg['title'] = alert['rule']['description'] if 'description' in alert['rule'] else "N/A"
|
msg['title'] = alert['rule']['description'] if 'description' in alert['rule'] else "N/A"
|
||||||
msg['text'] = alert.get('full_log')
|
msg['text'] = alert.get('full_log')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<integration>
|
<integration>
|
||||||
<name>custom-shuffle</name>
|
<name>custom-shuffle</name>
|
||||||
<hook_url>http://<IP>:3001/api/v1/hooks/webhook_<HOOK_ID></hook_url>
|
<level>9</level>
|
||||||
|
<hook_url>http://<IP>:<PORT>/api/v1/hooks/webhook_<HOOK_ID></hook_url>
|
||||||
<alert_format>json</alert_format>
|
<alert_format>json</alert_format>
|
||||||
</integration>
|
</integration>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
NAME=shuffle-orborus
|
NAME=shuffle-orborus
|
||||||
VERSION=0.8.71
|
VERSION=0.8.98
|
||||||
|
|
||||||
echo "Running docker build with $NAME:$VERSION"
|
echo "Running docker build with $NAME:$VERSION"
|
||||||
#docker rmi frikky/shuffle:$NAME --force
|
#docker rmi frikky/shuffle:$NAME --force
|
||||||
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly
|
||||||
|
|
||||||
#docker push frikky/$NAME:$VERSION
|
#docker push frikky/$NAME:$VERSION
|
||||||
# docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
# docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
|
||||||
docker push frikky/shuffle:$NAME
|
docker push frikky/shuffle:$NAME
|
||||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
docker push ghcr.io/frikky/$NAME:nightly
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ module orborus
|
|||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.4.16 // indirect
|
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||||
|
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect
|
||||||
|
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
|
||||||
github.com/containerd/containerd v1.4.3 // indirect
|
github.com/containerd/containerd v1.4.3 // indirect
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.1+incompatible
|
github.com/docker/docker v20.10.1+incompatible
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/frikky/shuffle-shared v0.0.23 // indirect
|
github.com/frikky/shuffle-shared v0.0.40
|
||||||
github.com/gogo/protobuf v1.3.1 // indirect
|
github.com/gogo/protobuf v1.3.1 // indirect
|
||||||
github.com/mackerelio/go-osstat v0.1.0
|
github.com/mackerelio/go-osstat v0.1.0
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
|||||||
@@ -41,8 +41,15 @@ cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7ni
|
|||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
|
github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
|
||||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
|
github.com/algolia/algoliasearch-client-go v2.25.0+incompatible h1:FGQr9l++u4uQPDXrW8jM5kNJm3Iw5SxEJJtYXSFmPRY=
|
||||||
|
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ=
|
||||||
|
github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk=
|
||||||
|
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y=
|
||||||
|
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
@@ -71,6 +78,12 @@ github.com/frikky/kin-openapi v0.38.0 h1:V7ttwIJS8Vks4KL+mZVj1ZSqhIcQtgaG8akeqXE
|
|||||||
github.com/frikky/kin-openapi v0.38.0/go.mod h1:Fr28TtCHL4K0kIqtqui8HWxN1LG5uAh3z/tDfFyiA1s=
|
github.com/frikky/kin-openapi v0.38.0/go.mod h1:Fr28TtCHL4K0kIqtqui8HWxN1LG5uAh3z/tDfFyiA1s=
|
||||||
github.com/frikky/shuffle-shared v0.0.12 h1:+0EIfThmK47Po+LogPYZR4XjbS4Ds19WNMFu2YUSjhw=
|
github.com/frikky/shuffle-shared v0.0.12 h1:+0EIfThmK47Po+LogPYZR4XjbS4Ds19WNMFu2YUSjhw=
|
||||||
github.com/frikky/shuffle-shared v0.0.12/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww=
|
github.com/frikky/shuffle-shared v0.0.12/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww=
|
||||||
|
github.com/frikky/shuffle-shared v0.0.23 h1:Pnlc2M6fHnFRLFd5K1iLTVv4/t4P04Ri1GJ5CMxzq0U=
|
||||||
|
github.com/frikky/shuffle-shared v0.0.23/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ=
|
||||||
|
github.com/frikky/shuffle-shared v0.0.28 h1:VQqL3+ePwKSUxCOiCC8DpOEgbb2GhXI8XzFB/YlHbps=
|
||||||
|
github.com/frikky/shuffle-shared v0.0.28/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ=
|
||||||
|
github.com/frikky/shuffle-shared v0.0.40 h1:H0au2np5xSy9mZEUWN+a29IORk+YP95FipENBD7iJRw=
|
||||||
|
github.com/frikky/shuffle-shared v0.0.40/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ=
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
@@ -162,6 +175,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
|||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
@@ -194,6 +209,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -485,6 +501,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ var baseimagetagsuffix = os.Getenv("SHUFFLE_BASE_IMAGE_TAG_SUFFIX")
|
|||||||
|
|
||||||
var orgId = os.Getenv("ORG_ID")
|
var orgId = os.Getenv("ORG_ID")
|
||||||
var baseUrl = os.Getenv("BASE_URL")
|
var baseUrl = os.Getenv("BASE_URL")
|
||||||
|
|
||||||
var environment = os.Getenv("ENVIRONMENT_NAME")
|
var environment = os.Getenv("ENVIRONMENT_NAME")
|
||||||
var dockerApiVersion = os.Getenv("DOCKER_API_VERSION")
|
var dockerApiVersion = os.Getenv("DOCKER_API_VERSION")
|
||||||
var runningMode = strings.ToLower(os.Getenv("RUNNING_MODE"))
|
var runningMode = strings.ToLower(os.Getenv("RUNNING_MODE"))
|
||||||
@@ -122,11 +123,17 @@ func getThisContainerId() {
|
|||||||
// https://docs.docker.com/engine/api/sdk/examples/
|
// https://docs.docker.com/engine/api/sdk/examples/
|
||||||
func deployWorker(image string, identifier string, env []string) {
|
func deployWorker(image string, identifier string, env []string) {
|
||||||
// Binds is the actual "-v" volume.
|
// Binds is the actual "-v" volume.
|
||||||
|
// Max 20% CPU every second
|
||||||
|
|
||||||
|
//CPUQuota: 25000,
|
||||||
|
//CPUPeriod: 100000,
|
||||||
|
//CPUShares: 256,
|
||||||
hostConfig := &container.HostConfig{
|
hostConfig := &container.HostConfig{
|
||||||
LogConfig: container.LogConfig{
|
LogConfig: container.LogConfig{
|
||||||
Type: "json-file",
|
Type: "json-file",
|
||||||
Config: map[string]string{},
|
Config: map[string]string{},
|
||||||
},
|
},
|
||||||
|
Resources: container.Resources{},
|
||||||
Binds: []string{
|
Binds: []string{
|
||||||
"/var/run/docker.sock:/var/run/docker.sock:rw",
|
"/var/run/docker.sock:/var/run/docker.sock:rw",
|
||||||
},
|
},
|
||||||
@@ -244,11 +251,11 @@ func initializeImages() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if appSdkVersion == "" {
|
if appSdkVersion == "" {
|
||||||
appSdkVersion = "0.8.60"
|
appSdkVersion = "0.8.97"
|
||||||
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
|
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
|
||||||
}
|
}
|
||||||
if workerVersion == "" {
|
if workerVersion == "" {
|
||||||
workerVersion = "0.8.70"
|
workerVersion = "nightly"
|
||||||
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
|
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +439,6 @@ func main() {
|
|||||||
//log.Printf("Prerequest")
|
//log.Printf("Prerequest")
|
||||||
//go getStats()
|
//go getStats()
|
||||||
newresp, err := client.Do(req)
|
newresp, err := client.Do(req)
|
||||||
executionCount := getRunningWorkers(ctx, workerTimeout)
|
|
||||||
//log.Printf("Postrequest")
|
//log.Printf("Postrequest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARNING] Failed making request: %s", err)
|
log.Printf("[WARNING] Failed making request: %s", err)
|
||||||
@@ -495,7 +501,8 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anything below here verifies concurrency virification
|
// Anything below here verifies concurrency
|
||||||
|
executionCount := getRunningWorkers(ctx, workerTimeout)
|
||||||
if executionCount >= maxConcurrency {
|
if executionCount >= maxConcurrency {
|
||||||
if zombiecounter*sleepTime > workerTimeout {
|
if zombiecounter*sleepTime > workerTimeout {
|
||||||
go zombiecheck(ctx, workerTimeout)
|
go zombiecheck(ctx, workerTimeout)
|
||||||
@@ -553,9 +560,11 @@ func main() {
|
|||||||
fmt.Sprintf("ENVIRONMENT_NAME=%s", environment),
|
fmt.Sprintf("ENVIRONMENT_NAME=%s", environment),
|
||||||
fmt.Sprintf("BASE_URL=%s", baseUrl),
|
fmt.Sprintf("BASE_URL=%s", baseUrl),
|
||||||
fmt.Sprintf("CLEANUP=%s", cleanupEnv),
|
fmt.Sprintf("CLEANUP=%s", cleanupEnv),
|
||||||
|
fmt.Sprintf("SHUFFLE_PASS_APP_PROXY=%s", os.Getenv("SHUFFLE_PASS_APP_PROXY")),
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(os.Getenv("SHUFFLE_PASS_WORKER_PROXY")) != "false" {
|
//log.Printf("Running worker with proxy? %s", os.Getenv("SHUFFLE_PASS_WORKER_PROXY"))
|
||||||
|
if strings.ToLower(os.Getenv("SHUFFLE_PASS_WORKER_PROXY")) == "true" {
|
||||||
env = append(env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY")))
|
env = append(env, fmt.Sprintf("HTTP_PROXY=%s", os.Getenv("HTTP_PROXY")))
|
||||||
env = append(env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY")))
|
env = append(env, fmt.Sprintf("HTTPS_PROXY=%s", os.Getenv("HTTPS_PROXY")))
|
||||||
}
|
}
|
||||||
@@ -633,12 +642,17 @@ func getRunningWorkers(ctx context.Context, workerTimeout int) int {
|
|||||||
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
|
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
|
||||||
All: true,
|
All: true,
|
||||||
})
|
})
|
||||||
//Filters: filters.Args{
|
|
||||||
// map[string][]string{"ancestor": {"<imagename>:<version>"}},
|
|
||||||
//},
|
|
||||||
|
|
||||||
|
// Automatically updates the version
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Error getting containers: %s", err)
|
log.Printf("[ERROR] Error getting containers: %s", err)
|
||||||
|
|
||||||
|
newVersionSplit := strings.Split(fmt.Sprintf("%s", err), "version is")
|
||||||
|
if len(newVersionSplit) > 1 {
|
||||||
|
dockerApiVersion = strings.TrimSpace(newVersionSplit[1])
|
||||||
|
log.Printf("[INFO] Changed the API version to default to %s", dockerApiVersion)
|
||||||
|
}
|
||||||
|
|
||||||
return maxConcurrency
|
return maxConcurrency
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -715,7 +729,7 @@ func zombiecheck(ctx context.Context, workerTimeout int) error {
|
|||||||
|
|
||||||
// Check image name
|
// Check image name
|
||||||
if !shuffleFound {
|
if !shuffleFound {
|
||||||
log.Printf("Skipping: %s, %s", container.Labels, container.Image)
|
//log.Printf("[WARNING] Zombie container skip: %#v, %s", container.Labels, container.Image)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
//} else {
|
//} else {
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
FROM golang:1.16.0-buster as builder
|
FROM golang:1.16.0-buster as builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
|
#RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
|
||||||
|
|
||||||
#RUN go env -w GO111MODULE=auto
|
#RUN go env -w GO111MODULE=auto
|
||||||
COPY worker.go /app/worker.go
|
COPY worker.go /app/worker.go
|
||||||
RUN go mod init worker
|
COPY go.mod /app/go.mod
|
||||||
RUN go get github.com/docker/docker/api/types && \
|
COPY go.sum /app/go.sum
|
||||||
go get github.com/docker/docker/api/types/container && \
|
RUN go get
|
||||||
go get github.com/docker/docker/client && \
|
|
||||||
go get github.com/gorilla/mux && \
|
#RUN go mod init worker
|
||||||
go get github.com/patrickmn/go-cache && \
|
#RUN go get
|
||||||
go get github.com/frikky/shuffle-shared && \
|
#RUN go get github.com/docker/docker/api/types && \
|
||||||
go get github.com/satori/go.uuid
|
# go get github.com/docker/docker/api/types/container && \
|
||||||
|
# go get github.com/docker/docker/client && \
|
||||||
|
# go get github.com/gorilla/mux && \
|
||||||
|
# go get github.com/patrickmn/go-cache && \
|
||||||
|
# go get github.com/frikky/shuffle-shared && \
|
||||||
|
# go get github.com/satori/go.uuid && \
|
||||||
|
# go get github.com/fsouza/go-dockerclient && \
|
||||||
|
# go get google.golang.org/grpc/balancer/grpclb@v1.37.1
|
||||||
|
|
||||||
RUN go build
|
RUN go build
|
||||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker .
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker .
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
NAME=shuffle-worker
|
NAME=shuffle-worker
|
||||||
VERSION=0.8.71
|
VERSION=0.8.101
|
||||||
|
|
||||||
echo "Running docker build with $NAME:$VERSION"
|
echo "Running docker build with $NAME:$VERSION"
|
||||||
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
|
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
|
||||||
docker build . -t frikky/shuffle:$NAME -t frikky/shuffle:$NAME_$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
|
docker build . -t frikky/shuffle:$NAME -t frikky/shuffle:$NAME_$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly
|
||||||
|
|
||||||
# Push both for now..
|
# Push both for now..
|
||||||
#docker push frikky/$NAME:$VERSION
|
#docker push frikky/$NAME:$VERSION
|
||||||
@@ -12,3 +12,4 @@ docker build . -t frikky/shuffle:$NAME -t frikky/shuffle:$NAME_$VERSION -t docke
|
|||||||
#docker tag frikky/shuffle:0.8.51 ghcr.io/frikky/shuffle-worker:0.8.5
|
#docker tag frikky/shuffle:0.8.51 ghcr.io/frikky/shuffle-worker:0.8.5
|
||||||
#docker tag frikky/shuffle:$NAME ghcr.io/frikky/shuffle-worker:0.8.52
|
#docker tag frikky/shuffle:$NAME ghcr.io/frikky/shuffle-worker:0.8.52
|
||||||
docker push ghcr.io/frikky/$NAME:$VERSION
|
docker push ghcr.io/frikky/$NAME:$VERSION
|
||||||
|
docker push ghcr.io/frikky/$NAME:nightly
|
||||||
|
|||||||
@@ -3,16 +3,24 @@ module worker
|
|||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.4.16 // indirect
|
||||||
|
github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect
|
||||||
|
github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
|
||||||
github.com/containerd/containerd v1.4.4 // indirect
|
github.com/containerd/containerd v1.4.4 // indirect
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.5+incompatible // indirect
|
github.com/docker/docker v20.10.5+incompatible
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/frikky/shuffle-shared v0.0.20 // indirect
|
github.com/frikky/shuffle-shared v0.0.63
|
||||||
|
github.com/fsouza/go-dockerclient v1.7.2
|
||||||
|
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/google/go-github/v28 v28.1.1 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
google.golang.org/grpc v1.37.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+486
-681
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user