Merge pull request #191 from frikky/launch

0.7.6
This commit is contained in:
Frikky
2020-10-31 17:38:47 +01:00
committed by GitHub
26 changed files with 1949 additions and 3142 deletions
@@ -1,235 +0,0 @@
components:
schemas:
tmp0:
properties:
cb_version:
type: string
company_name:
type: string
copied_mod_len:
type: string
digsig_issuer:
type: string
digsig_prog_name:
type: string
digsig_publisher:
type: string
digsig_result:
type: string
digsig_result_code:
type: string
digsig_sign_time:
type: string
digsig_subject:
type: string
endpoint:
type: string
event_partition_id:
type: string
facet_id:
type: string
file_desc:
type: string
file_version:
type: string
group:
type: string
host_count:
type: string
internal_name:
type: string
is_64bit:
type: string
is_executable_image:
type: string
last_seen:
type: string
legal_copyright:
type: string
md5:
type: string
observed_filename:
type: string
orig_mod_len:
type: string
original_filename:
type: string
os_type:
type: string
product_name:
type: string
product_version:
type: string
server_added_timestamp:
type: string
signed:
type: string
timestamp:
type: string
watchlists:
type: string
type: object
tmp1:
properties:
message:
type: string
type: object
tmp2:
properties:
childproc_count:
type: string
cmdline:
type: string
comms_ip:
type: string
crossproc_count:
type: string
emet_config:
type: string
emet_count:
type: string
filemod_count:
type: string
filtering_known_dlls:
type: string
group:
type: string
host_type:
type: string
hostname:
type: string
id:
type: string
interface_ip:
type: string
last_server_update:
type: string
last_update:
type: string
modload_count:
type: string
netconn_count:
type: string
os_type:
type: string
parent_id:
type: string
parent_name:
type: string
parent_pid:
type: string
parent_unique_id:
type: string
path:
type: string
process_md5:
type: string
process_name:
type: string
process_pid:
type: string
processblock_count:
type: string
regmod_count:
type: string
segment_id:
type: string
sensor_id:
type: string
start:
type: string
terminated:
type: string
unique_id:
type: string
username:
type: string
type: object
tmp3:
properties:
message:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Carbon Black Response
title: Carbon Black Response
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: Carbon Black Response Binary Search
parameters:
- description: Query
in: query
name: Query
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp0'
description: Successful request
summary: Carbon Black Response Binary Search
tmp1:
post:
description: Carbon Black Response Isolate Sensor
parameters:
- description: Hostname of a sensor to isolate.
in: query
name: Hostname
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp1'
description: Successful request
summary: Carbon Black Response Isolate Sensor
tmp2:
post:
description: Carbon Black Response Process Search
parameters:
- description: Query
in: query
name: Query
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp2'
description: Successful request
summary: Carbon Black Response Process Search
tmp3:
post:
description: Carbon Black Response Unisolate Sensor
parameters:
- description: Hostname of a sensor to unisolate.
in: query
name: Hostname
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp3'
description: Successful request
summary: Carbon Black Response Unisolate Sensor
@@ -1,559 +0,0 @@
components:
schemas:
tmp0:
properties:
outcome:
type: string
type: object
tmp1:
properties:
allRansomwareProcessesSuspended:
type: string
closeTime:
type: string
closerName:
type: string
creationTime:
type: string
customClassification:
type: string
decisionFeature:
type: string
detectionType:
type: string
elementDisplayName:
type: string
hasRansomwareSuspendedProcesses:
type: string
isBlocked:
type: string
malopActivityTypes:
type: string
malopLastUpdateTime:
type: string
malopStartTime:
type: string
managementStatus:
type: string
rootCauseElementNames:
type: string
rootCauseElementTypes:
type: string
type: object
tmp2:
properties:
blocking:
type: string
direction:
type: string
domain:
type: string
ipAddress:
type: string
ipAddressString:
type: string
lastUpdated:
type: string
port:
type: string
ruleId:
type: string
type: object
tmp3:
properties:
blacklistClassification:
type: string
classificationLink:
type: string
containsTorDomain:
type: string
domainClassificationSuspicion:
type: string
domainNameUniqueId:
type: string
elementDisplayName:
type: string
everResolvedDomain:
type: string
everResolvedSecondLevelDomain:
type: string
extendedDisplayId:
type: string
federationSegments:
type: string
getIpDiscoveryDomainList:
type: string
hasMalops:
type: string
hasResolvedClassificationEvidence:
type: string
hasSuspicions:
type: string
indifferentClassification:
type: string
isDomainMalicious:
type: string
isInIpDiscoveryDomainList:
type: string
isInternalDomain:
type: string
isInternalDomainByName:
type: string
isReverseLookup:
type: string
isTorrentDomain:
type: string
lookupDomainClassification:
type: string
maliciousClassification:
type: string
maliciousClassificationString:
type: string
maliciousClassificationType:
type: string
malwareClassification:
type: string
name:
type: string
relatedToMalop:
type: string
rootCauseKey:
type: string
secondLevelDomain:
type: string
sinkholedClassification:
type: string
sinkholedClassificationEvidence:
type: string
topLevelDomain:
type: string
unknownClassification:
type: string
unresolvedClassification:
type: string
unwantedClassification:
type: string
whitelistClassification:
type: string
type: object
tmp4:
properties:
attemptExecutionProcessSuspicion:
type: string
attributes:
type: string
blackListClassification:
type: string
canonizedPath:
type: string
classificationLink:
type: string
correctedPath:
type: string
createdTime:
type: string
detectionEventData:
type: string
dualExtensionName:
type: string
elementDisplayName:
type: string
extendedDisplayId:
type: string
extension:
type: string
extensionRecord:
type: string
extensionType:
type: string
externalProductClassification:
type: string
externalProductClassificationIsSigned:
type: string
externalProductClassificationSignatureVerificationStatus:
type: string
externalProductClassificationSignatureVerified:
type: string
externalProductClassificationSignatureVerifiedByVerificationStatus:
type: string
externalProductClassificationType:
type: string
federationSegments:
type: string
fileHasSystem32SubDirectories:
type: string
fileHasSystemSubDirectories:
type: string
fileHashUniqueId:
type: string
fileInSystemPath:
type: string
fileReputationSuspicion:
type: string
fileReputationSuspicionDecision:
type: string
fileVersionSuspicionDecision:
type: string
getNetworkScannersNames:
type: string
getToolsForUnusualNetworkEvidence:
type: string
hackingToolClassification:
type: string
hasAutorun:
type: string
hasClassification:
type: string
hasInternalName:
type: string
hasLegitClassification:
type: string
hasMalops:
type: string
hasNonLegitClassification:
type: string
hasNonLegitClassificationEvidence:
type: string
hasProductClassification:
type: string
hasRansomwareClassificationSubType:
type: string
hasSuspicions:
type: string
identifiedProduct:
type: string
indifferentClassification:
type: string
isDocument:
type: string
isExecutable:
type: string
isFromRemovableDevice:
type: string
isFromTemp:
type: string
isNoTypeFoundClassification:
type: string
isPEFile:
type: string
isProcessImageFile:
type: string
isScreenSaver:
type: string
isSigned:
type: string
isSuspicious:
type: string
lastDetectionEventData:
type: string
maliciousClassification:
type: string
maliciousClassificationString:
type: string
maliciousClassificationType:
type: string
maliciousToolClassification:
type: string
malwareClassification:
type: string
malwareClassificationEvidence:
type: string
malwareType:
type: string
md5:
type: string
md5String:
type: string
missingInterperterSectionValue:
type: string
modifiedTime:
type: string
name:
type: string
nameWithoutExtension:
type: string
path:
type: string
peSignedAndVerified:
type: string
productClassificationType:
type: string
productType:
type: string
profileId:
type: string
ransomwareClassification:
type: string
reasonSignatureVerificationStatus:
type: string
relatedToMalop:
type: string
reportedByAntiMalwareEvidence:
type: string
reportedByAntiMalwareSuspicion:
type: string
rootCauseKey:
type: string
sha1:
type: string
sha1String:
type: string
signatureVerified:
type: string
signatureVerifiedByVerificationStatus:
type: string
signatureVerifiedInternalOrExternal:
type: string
signedByApple:
type: string
signedByLinux:
type: string
signedByMicrosoft:
type: string
signedByOperatingSystem:
type: string
signedInternalOrExternal:
type: string
size:
type: string
suspiciousClassification:
type: string
suspiciousScreenSaverCondition:
type: string
unknownClassification:
type: string
unsignedPeFileEvidence:
type: string
unwantedClassification:
type: string
whitelistClassification:
type: string
type: object
tmp5:
properties:
address:
type: string
addressInternalExternalLocal:
type: string
addressString:
type: string
blackListClassification:
type: string
countryCode:
type: string
countryName:
type: string
countryNameOrNotExternalType:
type: string
elementDisplayName:
type: string
extendedDisplayId:
type: string
federationSegments:
type: string
geolocationLookup:
type: string
hasMalops:
type: string
hasSuspicions:
type: string
isDynamicConfiguration:
type: string
isExternalAddress:
type: string
isInternalAddress:
type: string
isLocalAddress:
type: string
latitude:
type: string
longitude:
type: string
lookupIpClassification:
type: string
maliciousClassification:
type: string
maliciousClassificationType:
type: string
rootCauseKey:
type: string
uniqueIpAddressHash:
type: string
version:
type: string
whiteListClassification:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Cyberreason
title: Cyberreason
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: CyberReason Block or Unblock an Item
parameters:
- description: Either 'blacklist' or 'whitelist'
in: query
name: Action
required: true
schema:
type: string
- description: IP, Domain, or Hash CSV values to blacklist or whitelist
in: query
name: Values
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp0'
description: Successful request
summary: CyberReason Block Item
tmp1:
post:
description: CyberReason get alerts from MalOps
parameters: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp1'
description: Successful request
summary: CyberReason Get MalOps Alerts
tmp2:
post:
description: CyberReason isolate a host by port
parameters:
- description: IP to block
in: query
name: IP
required: true
schema:
type: string
- description: Direction to block traffic, one of 'ALL', 'INCOMING', 'OUTGOING'
in: query
name: Direction to block
required: true
schema:
type: string
- description: Port to block
in: query
name: Port
required: true
schema:
type: string
- description: If true will isolate, false will remove from isolation
in: query
name: Block
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp2'
description: Successful request
summary: CyberReason Isolate Host
tmp3:
post:
description: Check CyberReason for a domain
parameters:
- description: Limit of results to return
in: body
name: Limit
required: false
schema:
type: string
- description: Timeout
in: body
name: Timeout (ms)
required: false
schema:
type: string
- description: Domain to search for
in: query
name: Domain
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp3'
description: Successful request
summary: CyberReason Query for Domain
tmp4:
post:
description: Check CyberReason for a hash
parameters:
- description: Limit of results to return
in: body
name: Limit
required: false
schema:
type: string
- description: Timeout
in: body
name: Timeout (ms)
required: false
schema:
type: string
- description: Hash to search for
in: query
name: Hash
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp4'
description: Successful request
summary: CyberReason Query for Hash
tmp5:
post:
description: Check CyberReason for a IP
parameters:
- description: Limit of results to return
in: body
name: Limit
required: false
schema:
type: string
- description: Timeout
in: body
name: Timeout (ms)
required: false
schema:
type: string
- description: IP to search for
in: query
name: IP
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp5'
description: Successful request
summary: CyberReason Query for IP
File diff suppressed because it is too large Load Diff
@@ -1,166 +0,0 @@
components:
schemas:
tmp0:
properties:
bid:
type: string
cve:
type: string
description:
type: string
msb:
type: string
osvdb:
type: string
source:
type: string
type: object
tmp1:
properties:
data:
type: string
domains:
type: string
hostnames:
type: string
ip:
type: string
location.area_code:
type: string
location.city:
type: string
location.country_code:
type: string
location.country_name:
type: string
location.dma_code:
type: string
location.latitude:
type: string
location.longitude:
type: string
location.postal_code:
type: string
location.region_code:
type: string
org:
type: string
os:
type: string
port:
type: string
transport:
type: string
type: object
tmp2:
properties:
devicetype:
type: string
domains:
type: string
hostnames:
type: string
ip_str:
type: string
isp:
type: string
location.area_code:
type: string
location.city:
type: string
location.country_code:
type: string
location.country_name:
type: string
location.dma_code:
type: string
location.latitude:
type: string
location.longitude:
type: string
location.postal_code:
type: string
org:
type: string
os:
type: string
port:
type: string
product:
type: string
timestamp:
type: string
title:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Shodan
title: Shodan
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: Search across a variety of data sources for exploits
parameters:
- description: Search query used to search the database of known exploits
in: query
name: Query
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp0'
description: Successful request
summary: Shodan Exploit Search
tmp1:
post:
description: Search all services that have been found on the given host IP
parameters:
- description: Host IP address
in: query
name: IP
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp1'
description: Successful request
summary: Shodan Host
tmp2:
post:
description: Search the SHODAN database
parameters:
- description: Keyword to search Shodan for
in: query
name: Query
required: true
schema:
type: string
- description: 'Max number of results to return. Default: ''Infinite'''
in: body
name: Limit
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp2'
description: Successful request
summary: Shodan Search
@@ -1,149 +0,0 @@
components:
schemas:
tmp0:
properties:
bios_uuid:
type: string
fqdn:
type: string
hostname:
type: string
id:
type: string
ipv4:
type: string
ipv6:
type: string
mac_address:
type: string
netbios_name:
type: string
operating_system:
type: string
ssh_fingerprint:
type: string
system_type:
type: string
type: object
tmp1:
properties:
agent_name:
type: string
fqdn:
type: string
id:
type: string
ipv4:
type: string
ipv6:
type: string
last_seen:
type: string
mac_address:
type: string
operating_system:
type: string
type: object
tmp2:
properties:
hostcount:
type: string
name:
type: string
owner:
type: string
policy:
type: string
scan_end:
type: string
scan_start:
type: string
status:
type: string
targets:
type: string
uuid:
type: string
type: object
tmp4:
properties:
count:
type: string
plugin_family:
type: string
plugin_name:
type: string
severity:
type: string
vulnerability_state:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Tenable Tenable.io
title: Tenable Tenable.io
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: Returns information about the specified asset.
parameters:
- description: The UUID of the asset.
in: query
name: Asset UUID
required: true
schema:
type: string
responses:
'200':
description: Successful request
summary: Tenable.io Asset Info
tmp1:
post:
description: Returns a list of up to 5000 assets.
parameters: []
responses:
'200':
description: Successful request
summary: Tenable.io List Assets
tmp2:
post:
description: Returns details for the given scan.
parameters:
- description: The ID of the scan.
in: query
name: Scan ID
required: true
schema:
type: string
responses:
'200':
description: Successful request
summary: Tenable.io Scan Details
tmp3:
post:
description: Launches a scan.
parameters:
- description: The ID of the scan.
in: body
name: Scan ID
required: true
schema:
type: string
responses:
'200':
description: Successful request
summary: Tenable.io Scan Launch
tmp4:
post:
description: Retrieves a list of recorded vulnerabilities.
parameters: []
responses:
'200':
description: Successful request
summary: Tenable.io Vulnerabilities
+228 -48
View File
@@ -1,5 +1,25 @@
import requests
import yaml
import json
import os
import io
import base64
from PIL import Image
#import tkinter
#import _tkinter
#tkinter._test()
#sudo apt-get install python-imaging-tk
#sudo apt-get install python3-tk
# USAGE:
# 1. Find the item here:
# https://apphub.swimlane.com/swimbundles/swimlane/sw_alienvault_threatcrowd
# 2.
# https://jsonlint.com/
# META: data["meta"]. Stuff like count. May be useful :)
def parse_data(data):
openapi = {
@@ -21,14 +41,61 @@ def parse_data(data):
}
}
category = data["category"]
filename = "%s.yaml" % data["title"].replace(" ", "_").lower()
openapi["info"]["title"] = data["title"]
openapi["info"]["description"] = "Automated generation of %s" % data["title"]
data = data["swimbundle"]
filename = "%s.yaml" % data["product"].replace(" ", "_").lower()
openapi["info"]["title"] = "%s %s" % (data["vendor"], data["product"])
openapi["info"]["description"] = "Automated generation of %s" % (openapi["info"]["title"])
# data["description"]
# https://swagger.io/docs/specification/authentication/
try:
asset = data["asset"]
inputparams = asset["inputParameters"]
try:
openapi["servers"] = [inputparams["api_url"]["example"]]
except KeyError as e:
#print(inputparams)
#print("Field error: %s" % e)
pass
authset = False
try:
tmpauth = inputparams["api_user"]
tmpauth = inputparams["api_key"]
openapi["components"]["securitySchemes"] = {
"BasicAuth": {
"type": "http",
"scheme": "basic"
}
}
authset = True
except KeyError as e:
pass
try:
tmpauth = inputparams["username"]
tmpauth = inputparams["password"]
openapi["components"]["securitySchemes"] = {
"BasicAuth": {
"type": "http",
"scheme": "basic"
}
}
authset = True
except KeyError as e:
pass
#if not authset:
# print("AUTH NOT SET: %s" % inputparams)
except KeyError as e:
print("KeyError asset: %s" % e)
cnt = 0
paramnames = []
for task in data["tasks"]:
method = "post"
@@ -45,51 +112,58 @@ def parse_data(data):
},
}
#taskname = task["name"]
#taskdescription = task["description"]
taskcategory = task["family"]
taskname = task["name"]
paramnames.append(taskname)
# This doesn't really do much except build the return value structures
for parameter in task["input_parameters"]:
example = parameter["example"]
for key, value in task["inputParameters"].items():
schema = "string"
inVar = "query"
if parameter["type"] == 6:
if value["type"] == 6:
inVar = "body"
schema = "string"
schemaset = False
if parameter["type"] != 1:
if (parameter["type"] == 7):
if value["type"] != 1:
if (value["type"] == 7):
schema = "boolean"
schemaset = True
if schema == "string" and schemaset:
print("Should change type: %d" % parameter["type"])
print("Should change type: %d" % value["type"])
print(task["name"])
print(parameter["name"])
print()
print(value["name"])
example = ""
try:
example = value["example"]
except KeyError:
pass
if len(example) == 1:
print("Change to number?")
if example.startswith("{"):
print("Change to json object?")
if example.startswith("["):
print("Change to array object?")
description = ""
try:
description = value["description"]
except KeyError:
pass
# Not sure how to tackle this
required = False
try:
required = value["required"]
except KeyError:
pass
openapi["paths"]["tmp%d" % cnt][method]["parameters"].append({
"in": inVar,
"name": parameter["name"],
"required": parameter["required"],
"description": parameter["description"],
"schema": {"type": schema}
"name": value["name"],
"required": required,
"example": example,
"description": description,
"schema": {"type": schema},
"in": inVar
})
if len(task["available_output_variables"]) > 0:
openapi["paths"]["tmp%d" % cnt][method]["responses"]["200"]["content"]: {
if len(task["availableOutputVariables"]) > 0:
openapi["paths"]["tmp%d" % cnt][method]["responses"]["200"]["content"] = {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tmp%d" % cnt
@@ -97,35 +171,141 @@ def parse_data(data):
}
}
#responses:
# '200':
# content:
# application/json:
# schema:
# $ref: '#/components/schemas/tmp1'
#description: Successful request
openapi["components"]["schemas"]["tmp%d" % cnt] = {
"type": "object",
"properties": {},
}
for output in task["available_output_variables"]:
openapi["components"]["schemas"]["tmp%d" % cnt]["properties"][output["key"]] = {"type": "string"}
for key, value in task["availableOutputVariables"].items():
if key == "response_code":
continue
openapi["components"]["schemas"]["tmp%d" % cnt]["properties"][key] = {
"type": "string"
}
cnt += 1
#print(openapi)
#print(filename)
print("%s: %d" % (openapi["info"]["title"], len(paramnames)))
return filename, openapi
def dump_data(filename, openapi):
generatedfile = "generated/%s" % filename
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
def dump_data(filename, openapi, category):
generatedfile = "generated/%s/%s" % (category, filename)
try:
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
except FileNotFoundError:
try:
os.mkdir("generated/%s" % category)
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
print("Generated %s" % generatedfile)
except FileExistsError:
pass
if __name__ == "__main__":
url = "https://apphub.swimlane.com/api/v1/bundles/cjuspytpz00rh0hpjo5chqg10"
url = "https://apphub.swimlane.com/api/v1/bundles/cjyoy62ch04920lr26id5sr0e"
url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdc2yr02rs0fli6jrosiqb"
url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdat0u01ux0flipb68a0a0"
url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdhbwp07nf0fli23lyb52h"
data = requests.get(url).json()
filename, openapi = parse_data(data)
dump_data(filename, openapi)
#https://apphub.swimlane.com/
categories = [
"Investigation",
"Endpoint Security & Management",
"Network Security & Management",
"Communication",
"SIEM & Log Management",
"Governance & Risk Management",
"Vulnerability & Patch Management",
"Ticket Management",
"DevOps & Application Security",
"Identity & Access Management",
"Infrastructure",
"Miscellaneous",
]
search_category = categories[2]
total = 0
for search_category in categories:
number = 1
innertotal = 0
while(True):
url = "https://apphub.swimlane.io/api/search/swimbundles?page=%d" % number
json = {"fields": {"family": search_category}}
ret = requests.post(
url,
json=json,
)
if ret.status_code != 201:
print("RET NOT 201: %d" % ret.status_code)
break
parsed = ret.json()
try:
category = parsed["data"][0]["swimbundleMeta"]["family"][0]
except KeyError:
category = ""
except IndexError:
category = ""
if category == "":
break
for data in parsed["data"]:
try:
filename, openapi = parse_data(data)
except:
try:
print("Skipping %s %s because of an error" % (data["vendor"], data["product"]))
except KeyError:
pass
continue
openapi["tags"] = [
{
"name": category,
}
]
appid = data["swimbundleMeta"]["logo"]["id"]
logoUrl = "https://apphub.swimlane.io/api/logos/%s" % appid
logodata = requests.get(logoUrl)
if logodata.status_code == 200:
logojson = logodata.json()
try:
logobase64 = logojson["data"]["base64"]
#.split(",")[1]
openapi["info"]["x-logo"] = logobase64
#print(logobase64)
#msg = base64.b64decode(logobase64)
#with io.BytesIO(msg) as buf:
# with Image.open(buf) as tempImg:
# newWidth = 174 / tempImg.width # change this to what ever width you need.
# newHeight = 174 / tempImg.height # change this to what ever height you need.
# newSize = (int(newWidth * tempImg.width), int(newHeight * tempImg.height))
# newImg1 = tempImg.resize(newSize)
# lbl1.IMG = ImageTk.PhotoImage(image=newImg1)
# lbl1.configure(image=lbl1.IMG)
except KeyError:
print("Failed logo parsing for %s" % appid)
pass
dump_data(filename, openapi, category)
innertotal += 1
total += 1
number += 1
print("Created %d openapi specs from Swimlane with category %s" % (innertotal, search_category))
print("\nCreated %d TOTAL openapi specs from Swimlane" % (total))
+65 -21
View File
@@ -308,7 +308,18 @@ class AppBase:
print("DATA: %s\n" % data)
return parse_wrapper(data)
# Looks for parantheses to grab special cases within a string, e.g:
# int(1) lower(HELLO) or length(what's the length)
# FIXME:
# There is an issue in here where it returns data wrong. Example:
# Authorization=Bearer authkey
# =
# Authorization=Bearer authkey
# ^ Double space.
def parse_wrapper_start(data):
if "(" not in data or ")" not in data:
return data
newdata = []
newstring = ""
record = True
@@ -337,7 +348,7 @@ class AppBase:
if len(newstring) > 0:
newdata.append(newstring)
#print(newdata)
print("Newdata: ", newdata)
parsedlist = []
non_string = False
for item in newdata:
@@ -348,17 +359,20 @@ class AppBase:
parsedlist.append(ret)
if len(parsedlist) > 0 and not non_string:
print("Returning parsed list: ", parsedlist)
return " ".join(parsedlist)
elif len(parsedlist) == 1 and non_string:
return parsedlist[0]
else:
#print("Casting back to string because multi: ", parsedlist)
print("Casting back to string because multi: ", parsedlist)
newlist = []
for item in parsedlist:
try:
newlist.append(str(item))
except ValueError:
newlist.append("parsing_error")
# Does this create the issue?
return " ".join(newlist)
# Parses JSON loops and such down to the item you're looking for
@@ -400,7 +414,7 @@ class AppBase:
# Means it's a single item -> continue
if seconditem == "":
print("In first - handling %s", seconditem)
print("In first - handling %s" % seconditem)
tmpitem = basejson[int(firstitem)]
try:
newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:])
@@ -460,23 +474,44 @@ class AppBase:
# Returns a string if the result is single, or a list if it's a list
def get_json_value(execution_data, input_data):
parsersplit = input_data.split(".")
actionname = parsersplit[0][1:].replace(" ", "_", -1)
actionname_lower = parsersplit[0][1:].lower()
#Actionname: Start_node
print(f"Actionname: {actionname}")
# 1. Find the action
baseresult = ""
actionname_lower = actionname.lower()
appendresult = ""
print("Parsersplit length: %d" % len(parsersplit))
if (actionname_lower.startswith("exec ") or actionname_lower.startswith("webhook ") or actionname_lower.startswith("schedule ") or actionname_lower.startswith("userinput ") or actionname_lower.startswith("email_trigger ") or actionname_lower.startswith("trigger ")) and len(parsersplit) == 1:
record = False
for char in actionname_lower:
if char == " ":
record = True
if record:
appendresult += char
actionname_lower = "exec"
actionname_lower = actionname_lower.replace(" ", "_", -1)
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":
baseresult = execution_data["execution_argument"]
else:
for result in execution_data["results"]:
resultlabel = result["action"]["label"].replace(" ", "_", -1).lower()
if resultlabel.lower() == actionname_lower:
baseresult = result["result"]
break
#print("Within execution data check. Execution data: %s", execution_data["results"])
if execution_data["results"] != None:
for result in execution_data["results"]:
resultlabel = result["action"]["label"].replace(" ", "_", -1).lower()
if resultlabel.lower() == actionname_lower:
baseresult = result["result"]
break
else:
print("No results to get values from.")
baseresult = "$" + parsersplit[0][1:]
print("BEFORE VARIABLES!")
if len(baseresult) == 0:
@@ -518,17 +553,17 @@ class AppBase:
# 2. Find the JSON data
if len(baseresult) == 0:
return "", False
return ""+appendresult, False
if len(parsersplit) == 1:
return baseresult, False
return baseresult+appendresult, False
baseresult = baseresult.replace("\'", "\"")
basejson = {}
try:
basejson = json.loads(baseresult)
except json.decoder.JSONDecodeError as e:
return baseresult, False
return baseresult+appendresult, False
data, is_loop = recurse_json(basejson, parsersplit[1:])
parseditem = data
@@ -542,7 +577,7 @@ class AppBase:
print("SET DATA WRAPPER TO %s!" % parsersplit[-1])
parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data))
return parseditem, is_loop
return parseditem+appendresult, is_loop
# Parses parameters sent to it and returns whether it did it successfully with the values found
def parse_params(action, fullexecution, parameter):
@@ -863,7 +898,7 @@ class AppBase:
# Custom format for ${name[0,1,2,...]}$
#submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})"
submatch = "([${]{2}#?([0-9a-zA-Z_-]+)#?(\[.*\])[}$]{2})"
actualitem = re.findall(submatch, value, re.MULTILINE)
try:
if action["skip_multicheck"]:
@@ -875,12 +910,14 @@ class AppBase:
print("Return value: %s" % value)
actionname = action["name"]
#print("Multicheck ", actualitem)
print("Actual item: %s" % actualitem)
print("ITEM LENGTH: %d, Actual item: %s" % (len(actualitem), actualitem))
if len(actualitem) > 0:
multiexecution = True
# Loop WITHOUT JSON variables go here.
# Loop WITH variables go in else.
print("Before first part in multiexec!")
handled = False
if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER":
print("Pre replacement: %s" % actualitem[0][2])
tmpitem = value
@@ -910,13 +947,13 @@ class AppBase:
#print("RESULTARRAY: %s" % resultarray)
print("MULTI finished: %s" % replacement)
else:
# This is here to handle for loops within variables.. kindof
# 1. Find the length of the longest array
# 2. Build an array with the base values based on parameter["value"]
# 3. Get the n'th value of the generated list from values
# 4. Execute all n answers
replacements = {}
curminlength = 0
for replace in actualitem:
try:
to_be_replaced = replace[0]
@@ -928,6 +965,9 @@ class AppBase:
itemlist = json.loads(actualitem)
if len(itemlist) > minlength:
minlength = len(itemlist)
if len(itemlist) > curminlength:
curminlength = len(itemlist)
except json.decoder.JSONDecodeError as e:
print("JSON Error: %s in %s" % (e, actualitem))
@@ -935,8 +975,9 @@ class AppBase:
# This is a result array for JUST this value..
# What if there are more?
print("LENGTH: %d. In second part of else: %s" % (len(itemlist), replacements))
resultarray = []
for i in range(0, minlength):
for i in range(0, curminlength):
tmpitem = json.loads(json.dumps(parameter["value"]))
for key, value in replacements.items():
replacement = json.dumps(json.loads(value)[i])
@@ -959,9 +1000,10 @@ class AppBase:
multi_parameters[parameter["name"]] = resultarray
else:
# Parses things like int(value)
self.logger.info("Parsing wrapper data for %s" % value)
print("Normal parsing (not looping) with data %s" % value)
value = parse_wrapper_start(value)
print("POST data value: %s" % value)
params[parameter["name"]] = value
multi_parameters[parameter["name"]] = value
@@ -992,8 +1034,9 @@ class AppBase:
#for i in range(calltimes):
if not multiexecution:
print("APP_SDK DONE: Starting NORMAL execution of function")
print("Running with params %s" % params)
newres = await func(**params)
#print("NEWRES: ", newres)
print("Return from execution: %s" % newres)
if isinstance(newres, str):
result += newres
else:
@@ -1074,6 +1117,7 @@ class AppBase:
print("Running with params %s" % baseparams)
ret = await func(**baseparams)
print("Return from execution: %s" % ret)
if isinstance(ret, dict) or isinstance(ret, list):
results.append(ret)
json_object = True
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
NAME=app_sdk
VERSION=0.7.3
VERSION=0.7.6
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
+14 -7
View File
@@ -396,10 +396,11 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet
bodyAddin,
verifyAddin,
)
//if strings.Contains(functionname, "get_returns_the_vuln") {
// log.Println(data)
// log.Printf("Queries: %s", queryString)
//}
if strings.Contains(functionname, "api_dumps_delete") {
log.Println(data)
log.Printf("Queries: %s", queryString)
}
//log.Printf(data)
return functionname, data
@@ -414,7 +415,13 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger,
}
if len(swagger.Servers) == 0 {
return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
//return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
//return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
swagger.Servers = openapi3.Servers{
&openapi3.Server{
URL: "https://hostname.com",
},
}
}
api.Name = swagger.Info.Title
@@ -1734,7 +1741,7 @@ func handlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []W
action.Parameters = append(action.Parameters, optionalParam)
}
functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "delete", parameters, optionalQueries, headersFound)
functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "patch", parameters, optionalQueries, headersFound)
if len(functionname) > 0 {
action.Name = functionname
@@ -1868,7 +1875,7 @@ func handlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []Wor
action.Parameters = append(action.Parameters, optionalParam)
}
functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "delete", parameters, optionalQueries, headersFound)
functionname, curCode := makePythoncode(swagger, functionName, baseUrl, "put", parameters, optionalQueries, headersFound)
if len(functionname) > 0 {
action.Name = functionname
+836 -205
View File
File diff suppressed because it is too large Load Diff
+269 -156
View File
@@ -28,7 +28,6 @@ import (
"github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/storage/memory"
http2 "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
@@ -69,16 +68,21 @@ type ExecutionRequest struct {
type SyncFeatures struct {
Apps SyncData `json:"apps" datastore:"apps"`
Workflows SyncData `json:"apps" datastore:"apps"`
Schedules SyncData `json:"apps" datastore:"apps"`
Autocomplete SyncData `json:"apps" datastore:"apps"`
Authentication SyncData `json:"apps" datastore:"apps"`
Workflows SyncData `json:"workflows" datastore:"workflows"`
Schedules SyncData `json:"schedules" datastore:"schedules"`
Autocomplete SyncData `json:"autocomplete" datastore:"autocomplete"`
Authentication SyncData `json:"authentication" datastore:"authentication"`
}
type SyncData struct {
Active bool `json:"active" datastore:"active"`
}
type SyncConfig struct {
Interval int64 `json:"interval" datastore:"interval"`
Apikey string `json:"api_key" datastore:"api_key"`
}
// Role is just used for feedback for a user
type Org struct {
Name string `json:"name" datastore:"name"`
@@ -88,6 +92,7 @@ type Org struct {
Role string `json:"role" datastore:"role"`
Roles []string `json:"roles" datastore:"roles"`
CloudSync bool `json:"cloud_sync" datastore:"CloudSync"`
SyncConfig SyncConfig `json:"sync_config" datastore:"sync_config"`
SyncFeatures SyncFeatures `json:"sync_features" datastore:"sync_features"`
}
@@ -100,6 +105,7 @@ type AppAuthenticationStorage struct {
Usage []AuthenticationUsage `json:"usage" datastore:"usage"`
WorkflowCount int64 `json:"workflow_count" datastore:"workflow_count"`
NodeCount int64 `json:"node_count" datastore:"node_count"`
OrgId string `json:"org_id" datastore:"org_id"`
}
type AuthenticationUsage struct {
@@ -192,6 +198,7 @@ type WorkflowAppAction struct {
}
// FIXME: Generate a callback authentication ID?
// FIXME: Add org check ..
type WorkflowExecution struct {
Type string `json:"type" datastore:"type"`
Status string `json:"status" datastore:"status"`
@@ -199,6 +206,7 @@ type WorkflowExecution struct {
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"`
ExecutionId string `json:"execution_id" datastore:"execution_id"`
ExecutionSource string `json:"execution_source" datastore:"execution_source"`
ExecutionOrg string `json:"execution_org" datastore:"execution_org"`
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
LastNode string `json:"last_node" datastore:"last_node"`
Authorization string `json:"authorization" datastore:"authorization"`
@@ -215,6 +223,7 @@ type WorkflowExecution struct {
Name string `json:"name" datastore:"name"`
Value string `json:"value" datastore:"value,noindex"`
} `json:"execution_variables,omitempty" datastore:"execution_variables,omitempty"`
OrgId string `json:"org_id" datastore:"org_id"`
}
// This is for the nodes in a workflow, NOT the app action itself.
@@ -297,6 +306,7 @@ type Schedule struct {
Frequency string `json:"frequency" datastore:"frequency"`
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"`
Id string `json:"id" datastore:"id"`
OrgId string `json:"org_id" datastore:"org_id"`
}
type Workflow struct {
@@ -476,7 +486,7 @@ func getWorkflowQueue(ctx context.Context, id string) (ExecutionRequestWrapper,
//}
// Frequency = cronjob OR minutes between execution
func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency string, body []byte) error {
func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency, orgId string, body []byte) error {
var err error
testSplit := strings.Split(frequency, "*")
cronJob := ""
@@ -520,7 +530,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)),
}
_, _, err := handleExecution(workflowId, Workflow{}, request)
_, _, err := handleExecution(workflowId, Workflow{ExecutingOrg: Org{Id: orgId}}, request)
if err != nil {
log.Printf("Failed to execute %s: %s", workflowId, err)
}
@@ -548,6 +558,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
CreationTime: timeNow,
LastModificationtime: timeNow,
LastRuntime: timeNow,
Org: orgId,
}
err = setSchedule(ctx, schedule)
@@ -1041,6 +1052,7 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
extraInputs := 0
for _, result := range workflowExecution.Results {
if result.Action.Name == "User Input" && result.Action.AppName == "User Input" {
log.Printf("Found User Input node - prepare cloud?")
extraInputs += 1
}
}
@@ -1350,6 +1362,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
workflow.ID = uuid.NewV4().String()
workflow.Owner = user.Id
workflow.Sharing = "private"
workflow.ExecutingOrg = user.ActiveOrg
ctx := context.Background()
log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name)
@@ -1390,7 +1403,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
if err == nil {
// FIXME: Add real env
envName := "Shuffle"
environments, err := getEnvironments(ctx)
environments, err := getEnvironments(ctx, user.ActiveOrg.Id)
if err == nil {
for _, env := range environments {
if env.Default {
@@ -1552,12 +1565,6 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) {
log.Printf("Failed to increase total workflows: %s", err)
}
// recalculate authenticators stats
err = recalculateAppAuthentications()
if err != nil {
log.Printf("Authentications recalculation failed: %s", err)
}
//memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId)
//memcache.Delete(ctx, memcacheName)
//memcacheName = fmt.Sprintf("%s_workflows", user.Username)
@@ -1680,8 +1687,6 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
return
}
//Actions []Action `json:"actions" datastore:"actions,noindex"`
body, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Printf("Failed hook unmarshaling: %s", err)
@@ -1713,6 +1718,11 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
workflow.Owner = user.Id
}
if len(workflow.ExecutingOrg.Id) == 0 {
log.Printf("setting executing org")
workflow.ExecutingOrg = user.ActiveOrg
}
// FIXME - this shouldn't be necessary with proper API checks
newActions := []Action{}
allNodes := []string{}
@@ -1721,6 +1731,11 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
for _, action := range workflow.Actions {
allNodes = append(allNodes, action.ID)
if len(action.Errors) > 0 {
action.IsValid = true
action.Errors = []string{}
}
if action.Environment == "" {
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "An environment for %s is required"}`, action.Label)))
@@ -1895,7 +1910,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
}
}
allAuths, err := getAllWorkflowAppAuth(ctx)
allAuths, err := getAllWorkflowAppAuth(ctx, user.ActiveOrg.Id)
if userErr != nil {
log.Printf("Api authentication failed in get all apps: %s", userErr)
resp.WriteHeader(401)
@@ -2125,17 +2140,7 @@ func abortExecution(resp http.ResponseWriter, request *http.Request) {
return
}
// FIXME: Check the execution if this fails.
user, err := handleApiAuthentication(resp, request)
if err != nil {
log.Printf("Api authentication failed in abort workflow: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
location := strings.Split(request.URL.String(), "/")
var fileId string
if location[1] == "api" {
if len(location) <= 4 {
@@ -2169,12 +2174,34 @@ func abortExecution(resp http.ResponseWriter, request *http.Request) {
return
}
// FIXME - have a check for org etc too..
if user.Id != workflowExecution.Workflow.Owner && user.Role != "admin" {
log.Printf("Wrong user (%s) for workflowexecution workflow %s", user.Username, workflowExecution.Workflow.ID)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
apikey := request.Header.Get("Authorization")
parsedKey := ""
if strings.HasPrefix(apikey, "Bearer ") {
apikeyCheck := strings.Split(apikey, " ")
if len(apikeyCheck) == 2 {
parsedKey = apikeyCheck[1]
}
}
if workflowExecution.Authorization != parsedKey {
// FIXME: Check the execution if this fails.
user, err := handleApiAuthentication(resp, request)
if err != nil {
log.Printf("Api authentication failed in abort workflow: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// FIXME - have a check for org etc too..
if user.Id != workflowExecution.Workflow.Owner && user.Role != "admin" {
log.Printf("Wrong user (%s) for workflowexecution workflow %s", user.Username, workflowExecution.Workflow.ID)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
} else {
log.Printf("API key %s is correct to abort %s", parsedKey, executionId)
}
if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" || workflowExecution.Status == "FINISHED" {
@@ -2289,6 +2316,11 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
workflow = *tmpworkflow
}
if len(workflow.ExecutingOrg.Id) == 0 {
log.Printf("Stopped execution because there is no executing org for workflow %s", workflow.ID)
return WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined")
}
if len(workflow.Actions) == 0 {
workflow.Actions = []Action{}
}
@@ -2381,6 +2413,8 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
}
} else {
// Check for parameters of start and ExecutionId
// This is mostly used for user input trigger
start, startok := request.URL.Query()["start"]
answer, answerok := request.URL.Query()["answer"]
referenceId, referenceok := request.URL.Query()["reference_execution"]
@@ -2547,7 +2581,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
// FIXME: Authentication parameters
if len(action.AuthenticationId) > 0 {
if len(allAuths) == 0 {
allAuths, err = getAllWorkflowAppAuth(ctx)
allAuths, err = getAllWorkflowAppAuth(ctx, workflow.ExecutingOrg.Id)
if err != nil {
log.Printf("Api authentication failed in get all app auth: %s", err)
return WorkflowExecution{}, fmt.Sprintf("Api authentication failed in get all app auth: %s", err), err
@@ -2626,31 +2660,83 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
// Verification for execution environments
workflowExecution.Results = defaultResults
workflowExecution.Workflow.Actions = newActions
onpremExecution := false
onpremExecution := true
environments := []string{}
if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 {
workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id
}
var allEnvs []Environment
if len(workflowExecution.ExecutionOrg) > 0 {
log.Printf("Executing ORG: %s", workflowExecution.ExecutionOrg)
allEnvironments, err := getEnvironments(ctx, workflowExecution.ExecutionOrg)
if err != nil {
log.Printf("Failed finding environments: %s", err)
return WorkflowExecution{}, fmt.Sprintf("Workflow environments not found for this org"), errors.New(fmt.Sprintf("Workflow environments not found for this org"))
}
for _, curenv := range allEnvironments {
if curenv.Archived {
continue
}
allEnvs = append(allEnvs, curenv)
}
} else {
log.Printf("[ERROR] No org identified for execution of %s. Returning", workflowExecution.Workflow.ID)
return WorkflowExecution{}, "No org identified for execution", errors.New("No org identified for execution")
}
if len(allEnvs) == 0 {
log.Printf("[ERROR] No active environments found for org", workflowExecution.ExecutionOrg)
return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No active env found for org %s", workflowExecution.ExecutionOrg))
}
// Check if the actions are children of the startnode?
imageNames := []string{}
cloudExec := false
for _, action := range workflowExecution.Workflow.Actions {
if action.Environment != cloudname {
found := false
for _, env := range environments {
if env == action.Environment {
found = true
break
// Verify if the action environment exists and append
found := false
for _, env := range allEnvs {
if env.Name == action.Environment {
found = true
if env.Type == "cloud" {
cloudExec = true
} else if env.Type == "onprem" {
onpremExecution = true
} else {
log.Printf("[ERROR] No handler for environment type %s", env.Type)
return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No handler for environment type %s", env.Type))
}
break
}
}
// Check if the app exists?
newName := action.AppName
newName = strings.ReplaceAll(newName, " ", "-")
imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion))
if !found {
log.Printf("[ERROR] Couldn't find environment %s. Maybe it's inactive?", action.Environment)
return WorkflowExecution{}, "Couldn't find the environment", errors.New(fmt.Sprintf("Couldn't find env %s in org %s", action.Environment, workflowExecution.ExecutionOrg))
}
if !found {
environments = append(environments, action.Environment)
found = false
for _, env := range environments {
if env == action.Environment {
found = true
break
}
}
onpremExecution = true
// Check if the app exists?
newName := action.AppName
newName = strings.ReplaceAll(newName, " ", "-")
imageNames = append(imageNames, fmt.Sprintf("%s:%s_%s", baseDockerName, newName, action.AppVersion))
if !found {
environments = append(environments, action.Environment)
}
}
@@ -2695,8 +2781,30 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
log.Printf("Failed adding to db: %s", err)
}
}
} else {
log.Printf("[ERROR] Cloud not implemented yet")
}
// Verifies and runs cloud executions
if cloudExec {
featuresList, err := handleVerifyCloudsync(workflowExecution.ExecutionOrg)
if !featuresList.Workflows.Active || err != nil {
log.Printf("Error: %s", err)
log.Printf("[ERROR] Cloud not implemented yet. May need to work on app checking and such")
return WorkflowExecution{}, "Cloud not implemented yet", errors.New("Cloud not implemented yet")
}
// What it needs to know:
// 1. Parameters
if len(workflowExecution.Workflow.Actions) == 1 {
log.Printf("Should execute directly with cloud instead of worker because only one action")
//cloudExecuteAction(workflowExecution.ExecutionId, workflowExecution.Workflow.Actions[0], workflowExecution.ExecutionOrg, workflowExecution.Workflow.ID)
cloudExecuteAction(workflowExecution)
return WorkflowExecution{}, "Cloud not implemented yet (1)", errors.New("Cloud not implemented yet")
} else {
// If it's here, it should be controlled by Worker.
// If worker, should this backend be a proxy? I think so.
return WorkflowExecution{}, "Cloud not implemented yet (2)", errors.New("Cloud not implemented yet")
}
}
err = increaseStatisticsField(ctx, "workflow_executions", workflow.ID, 1)
@@ -2707,6 +2815,77 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
return workflowExecution, "", nil
}
// This updates stuff locally from remote executions
func cloudExecuteAction(execution WorkflowExecution) error {
ctx := context.Background()
org, err := getOrg(ctx, execution.ExecutionOrg)
if err != nil {
return err
}
type ExecutionStruct struct {
ExecutionId string `json:"execution_id" datastore:"execution_id"`
Action Action `json:"action" datastore:"action"`
Authorization string `json:"authorization" datastore:"authorization"`
Results []ActionResult `json:"results" datastore:"results,noindex"`
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"`
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
ExecutionSource string `json:"execution_source" datastore:"execution_source"`
}
data := ExecutionStruct{
ExecutionId: execution.ExecutionId,
WorkflowId: execution.Workflow.ID,
Action: execution.Workflow.Actions[0],
Authorization: execution.Authorization,
}
log.Printf("Executing action: %#v in execution ID %s", data.Action, data.ExecutionId)
b, err := json.Marshal(data)
if err != nil {
log.Printf("Failed marshaling api key data: %s", err)
return err
}
syncURL := fmt.Sprintf("%s/api/v1/cloud/sync/execute_node", syncUrl)
client := &http.Client{}
req, err := http.NewRequest(
"POST",
syncURL,
bytes.NewBuffer(b),
)
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey))
newresp, err := client.Do(req)
if err != nil {
return err
}
respBody, err := ioutil.ReadAll(newresp.Body)
if err != nil {
return err
}
log.Printf("Finished request. Data: %s", string(respBody))
log.Printf("Status code: %d", newresp.StatusCode)
responseData := retStruct{}
err = json.Unmarshal(respBody, &responseData)
if err != nil {
return err
}
if newresp.StatusCode != 200 {
return errors.New(fmt.Sprintf("Got status code %d when executing remotely. Expected 200. Contact support.", newresp.StatusCode))
}
if !responseData.Success {
return errors.New(responseData.Reason)
}
return nil
}
func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
@@ -2760,6 +2939,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
}
log.Printf("[INFO] Starting execution of %s!", fileId)
workflow.ExecutingOrg = user.ActiveOrg
workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request)
if err == nil {
@@ -3121,6 +3301,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
schedule.Name,
startNode,
schedule.Frequency,
user.ActiveOrg.Id,
[]byte(parsedBody),
)
@@ -3320,9 +3501,9 @@ func getWorkflow(ctx context.Context, id string) (*Workflow, error) {
return workflow, nil
}
func getEnvironments(ctx context.Context) ([]Environment, error) {
func getEnvironments(ctx context.Context, OrgId string) ([]Environment, error) {
var environments []Environment
q := datastore.NewQuery("Environments")
q := datastore.NewQuery("Environments").Filter("org_id =", OrgId)
_, err := dbclient.GetAll(ctx, q, &environments)
if err != nil {
@@ -3740,7 +3921,7 @@ func getAppAuthentication(resp http.ResponseWriter, request *http.Request) {
return
}
_, userErr := handleApiAuthentication(resp, request)
user, userErr := handleApiAuthentication(resp, request)
if userErr != nil {
log.Printf("Api authentication failed in get all apps: %s", userErr)
resp.WriteHeader(401)
@@ -3756,7 +3937,7 @@ func getAppAuthentication(resp http.ResponseWriter, request *http.Request) {
// return
//}
ctx := context.Background()
allAuths, err := getAllWorkflowAppAuth(ctx)
allAuths, err := getAllWorkflowAppAuth(ctx, user.ActiveOrg.Id)
if err != nil {
log.Printf("Api authentication failed in get all app auth: %s", err)
resp.WriteHeader(401)
@@ -3981,7 +4162,7 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) {
workflowapps, err := getAllWorkflowApps(ctx)
if err != nil {
log.Printf("Failed getting apps: %s", err)
log.Printf("Failed getting apps (getworkflowapps): %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
@@ -4391,7 +4572,7 @@ func deployWebhookFunction(ctx context.Context, name, localization, applocation
func loadGithubWorkflows(url, username, password, userId, branch string) error {
fs := memfs.New()
// FIXME: add more git options lol
log.Printf("Starting load of %s with branch %s", url, branch)
if strings.Contains(url, "github") || strings.Contains(url, "gitlab") || strings.Contains(url, "bitbucket") {
cloneOptions := &git.CloneOptions{
URL: url,
@@ -4400,43 +4581,27 @@ func loadGithubWorkflows(url, username, password, userId, branch string) error {
// FIXME: Better auth.
if len(username) > 0 && len(password) > 0 {
cloneOptions.Auth = &http2.BasicAuth{
Username: username,
Password: password,
}
}
// main is the new master
if len(branch) > 0 && branch != "main" && branch != "master" {
cloneOptions.ReferenceName = plumbing.ReferenceName(branch)
}
storer := memory.NewStorage()
r, err := git.Clone(storer, fs, cloneOptions)
if err != nil {
log.Printf("Failed loading repo into memory: %s", err)
log.Printf("Failed loading repo %s into memory (github workflows): %s", url, err)
return err
}
if len(branch) > 0 {
log.Printf("Checkout to branch: %s", branch)
w, _ := r.Worktree()
err := r.Fetch(&git.FetchOptions{
RefSpecs: []config.RefSpec{"refs/*:refs/*", "HEAD:refs/heads/HEAD"},
})
if err != nil {
log.Printf("Failed fetch for git repo: %s", err)
}
err = w.Checkout(&git.CheckoutOptions{
Branch: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)),
Force: true,
})
if err != nil {
log.Printf("Failed checkout for git repo: %s", err)
return errors.New(fmt.Sprintf("Failed checking out to branch %s - does it exist?", branch))
}
}
dir, err := fs.ReadDir("/")
if err != nil {
log.Printf("Failed reading folder: %s", err)
log.Printf("FAiled reading folder: %s", err)
}
_ = r
@@ -4511,7 +4676,7 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) {
if err != nil {
log.Printf("Error with unmarshal tmpBody: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "json decode error"}`))
resp.Write([]byte(`{"success": false}`))
return
}
@@ -4520,82 +4685,14 @@ func loadSpecificWorkflows(resp http.ResponseWriter, request *http.Request) {
if err != nil {
log.Printf("Failed to update workflows: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
resp.Write([]byte(`{"success": false}`))
return
}
// recalculate authenticators stats
err = recalculateAppAuthentications()
if err != nil {
log.Printf("Authentications recalculation failed: %s", err)
}
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
}
func recalculateAppAuthentications() error {
// create context
ctx := context.Background()
// form workflows list
workflows, err := getAllWorkflows(ctx)
if err != nil {
log.Printf("Error: Failed getting workflows: %s", err)
return err
}
// form authenticators list
auths, err := getAllWorkflowAppAuth(ctx)
if err != nil {
log.Printf("Error: Failed getting auths: %s", err)
return err
}
// iterate through auths
for _, auth := range auths {
// reset calculated values
auth.WorkflowCount = 0
auth.NodeCount = 0
auth.Usage = []AuthenticationUsage{}
// iterate through workflows to find which uses this auth
for _, workflow := range workflows {
hasCurrentAuth := false
usageItem := AuthenticationUsage{
WorkflowId: workflow.ID,
Nodes: []string{},
}
// iterate through actions
for _, action := range workflow.Actions {
if action.AuthenticationId == auth.Id {
// this workflow should be added to "usage" field
hasCurrentAuth = true
// add this action to list
usageItem.Nodes = append(usageItem.Nodes, action.ID)
}
}
// update current auth with found workflow
if hasCurrentAuth {
auth.WorkflowCount += 1
auth.NodeCount += int64(len(usageItem.Nodes))
auth.Usage = append(auth.Usage, usageItem)
}
}
// update record in database
err := setWorkflowAppAuthDatastore(ctx, auth, auth.Id)
if err != nil {
log.Printf("Failed setting up app auth %s: %s", auth.Id, err)
}
}
return nil
}
func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
@@ -4630,8 +4727,9 @@ func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) {
log.Printf("Hotloading from %s", location)
err = handleAppHotload(location, true)
if err != nil {
log.Printf("Failed app hotload: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed loading apps: %s"}`)))
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed loading apps: %s"}`, err)))
return
}
@@ -4699,7 +4797,7 @@ func loadSpecificApps(resp http.ResponseWriter, request *http.Request) {
storer := memory.NewStorage()
r, err := git.Clone(storer, fs, cloneOptions)
if err != nil {
log.Printf("Failed loading repo into memory: %s", err)
log.Printf("Failed loading repo %s into memory (github workflows 2): %s", tmpBody.URL, err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return
@@ -4778,10 +4876,25 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string,
case mode.IsRegular():
// Check the file
filename := file.Name()
filteredNames := []string{"FUNDING.yml"}
if strings.Contains(filename, "yaml") || strings.Contains(filename, "yml") {
contOuter := false
for _, name := range filteredNames {
if filename == name {
contOuter = true
break
}
}
if contOuter {
log.Printf("Skipping %s", filename)
continue
}
//log.Printf("File: %s", filename)
//log.Printf("Found file: %s", filename)
log.Printf("OpenAPI app: %s", filename)
//log.Printf("OpenAPI app: %s", filename)
tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name())
fileReader, err := fs.Open(tmpExtra)
@@ -5368,7 +5481,7 @@ func getAllSchedules(ctx context.Context) ([]ScheduleOld, error) {
func getAllWorkflowApps(ctx context.Context) ([]WorkflowApp, error) {
var allworkflowapps []WorkflowApp
q := datastore.NewQuery("workflowapp")
q := datastore.NewQuery("workflowapp").Limit(50)
_, err := dbclient.GetAll(ctx, q, &allworkflowapps)
if err != nil {
@@ -5378,9 +5491,9 @@ func getAllWorkflowApps(ctx context.Context) ([]WorkflowApp, error) {
return allworkflowapps, nil
}
func getAllWorkflowAppAuth(ctx context.Context) ([]AppAuthenticationStorage, error) {
func getAllWorkflowAppAuth(ctx context.Context, OrgId string) ([]AppAuthenticationStorage, error) {
var allworkflowapps []AppAuthenticationStorage
q := datastore.NewQuery("workflowappauth")
q := datastore.NewQuery("workflowappauth").Filter("org_id = ", OrgId)
_, err := dbclient.GetAll(ctx, q, &allworkflowapps)
if err != nil {
+3
View File
@@ -0,0 +1,3 @@
# ./b199646b-16d2-456d-9fd6-b9972e929466/2e9d6474-402c-4dcc-bb53-45f638ca18d3/0d676d72-5d53-4803-a6b0-4afb464df828
# org_id / workflow_id / file_id
curl http://192.168.3.6:5001/api/v1/files/0d676d72-5d53-4803-a6b0-4afb464df828/content -H "Authorization: Bearer 093b576f-19ea-4353-b685-362ab50f39f4"
+1 -1
View File
@@ -16,7 +16,7 @@ services:
depends_on:
- backend
backend:
#build: ./backend
build: ./backend
image: frikky/shuffle:backend
container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+19 -18
View File
@@ -3,7 +3,7 @@ import React, { useState, useEffect } from 'react';
import { Route } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
import { CookiesProvider } from 'react-cookie';
import { useCookies } from 'react-cookie';
import { removeCookies, useCookies } from 'react-cookie';
import EditSchedule from "./views/EditSchedule";
import Schedules from "./views/Schedules";
@@ -93,23 +93,24 @@ const App = (message, props) => {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(responseJson => {
if (responseJson.success === true) {
//console.log(responseJson.success)
setUserData(responseJson)
setIsLoggedIn(true)
.then(response => response.json())
.then(responseJson => {
if (responseJson.success === true) {
//console.log(responseJson.success)
setUserData(responseJson)
setIsLoggedIn(true)
console.log("Cookies: ", cookies)
// Updating cookie every request
for (var key in responseJson["cookies"]) {
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
}
// Updating cookie every request
for (var key in responseJson["cookies"]) {
setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" })
}
setIsLoaded(true)
})
.catch(error => {
setIsLoaded(true)
});
}
setIsLoaded(true)
})
.catch(error => {
setIsLoaded(true)
});
}
// Dumb for content load (per now), but good for making the site not suddenly reload parts (ajax thingies)
@@ -124,7 +125,7 @@ const App = (message, props) => {
<Route exact path="/home" render={props => <LandingPageNew isLoaded={isLoaded} {...props} />} />
</div> :
<div style={{ backgroundColor: "#1F2023", color: "rgba(255, 255, 255, 0.65)", minHeight: "100vh" }}>
<Header removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} userdata={userdata} {...props} />
<Header cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} globalUrl={globalUrl} setIsLoggedIn={setIsLoggedIn} isLoggedIn={isLoggedIn} userdata={userdata} {...props} />
<Route exact path="/oauth2" render={props => <Oauth2 isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
<Route exact path="/contact" render={props => <Contact isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
<Route exact path="/login" render={props => <LoginPage isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} register={true} isLoaded={isLoaded} globalUrl={globalUrl} setCookie={setCookie} cookies={cookies} {...props} />} />
@@ -140,7 +141,7 @@ const App = (message, props) => {
<Route exact path="/apps/new" 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="/workflows" render={props => <Workflows isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} {...props} />} />
<Route exact path="/workflows" render={props => <Workflows cookies={cookies} removeCookie={removeCookie} isLoaded={isLoaded} isLoggedIn={isLoggedIn} globalUrl={globalUrl} cookies={cookies} {...props} />} />
<Route exact path="/workflows/:key" render={props => <AngularWorkflow userdata={userdata} globalUrl={globalUrl} isLoaded={isLoaded} isLoggedIn={isLoggedIn} {...props} />} />
<Route exact path="/docs/:key" render={props => <Docs isLoaded={isLoaded} globalUrl={globalUrl} {...props} />} />
<Route exact path="/docs" render={props => { window.location.pathname = "/docs/about" }} />
+84
View File
@@ -0,0 +1,84 @@
import React, { useRef, useState } from 'react';
import { useEffect } from 'react';
import BackupIcon from '@material-ui/icons/Backup';
const dragOverStyle = {
backgroundColor: 'rgba(0,0,0,0.8)',
border: '5px dashed white',
borderRadius: '8px',
width: '100%',
height: '100%',
position: 'absolute',
overflow: 'hidden',
zIndex: 100,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
};
const Dropzone = ({ children, style, onDrop }) => {
const dropzoneRef = useRef(null);
const [dragging, setDragging] = useState(false);
let dragCounter = 0;
const handleDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
};
const handleDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter++;
if (e.dataTransfer.items && e.dataTransfer.items.length > 0)
setDragging(true);
};
const handleDragLeave = (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter--;
if (dragCounter === 0) setDragging(false);
};
const handleDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragging(false);
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
onDrop(e);
e.dataTransfer.clearData();
dragCounter = 0;
}
};
useEffect(() => {
if (!dropzoneRef.current) return;
dropzoneRef.current.addEventListener('dragover', handleDragOver);
dropzoneRef.current.addEventListener('dragenter', handleDragEnter);
dropzoneRef.current.addEventListener('dragleave', handleDragLeave);
dropzoneRef.current.addEventListener('drop', handleDrop);
return () => {
dropzoneRef.current.removeEventListener('dragover', handleDragOver);
dropzoneRef.current.removeEventListener('dragenter', handleDragEnter);
dropzoneRef.current.removeEventListener('dragleave', handleDragLeave);
dropzoneRef.current.removeEventListener('drop', handleDrop);
};
}, [dropzoneRef]);
return (
<div ref={dropzoneRef} style={{ position: 'relative', ...style }}>
{dragging && (
<div style={dragOverStyle}>
<BackupIcon fontSize="large" />
</div>
)}
{children}
</div>
);
};
export default Dropzone;
+46 -23
View File
@@ -19,7 +19,7 @@ const hoverColor = "#f85a3e"
const hoverOutColor = "#e8eaf6"
const Header = props => {
const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded, userdata } = props;
const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded, userdata, cookies } = props;
const theme = useTheme();
const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor);
@@ -35,27 +35,31 @@ const Header = props => {
// DEBUG HERE
const handleClickLogout = () => {
console.log("SHOULD LOG OUT")
console.log(isLoggedIn)
console.log("COOKIES: ", cookies, "Remover: ", removeCookie)
// Don't really care about the logout
fetch(globalUrl+"/api/v1/logout", {
fetch(globalUrl+"/api/v1/logout", {
credentials: "include",
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
.then(() => {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
.then(() => {
// Log out anyway
console.log("Hey")
//cookies.remove("session_token")
//window.location.pathname = "/"
console.log("Should've logged out")
removeCookie("session_token", {path: "/"})
window.location.pathname = "/"
})
removeCookie("session_token", {path: "/workflows"})
window.location.reload()
})
.catch(error => {
console.log(error)
});
}
console.log("Error in logout: ", error)
removeCookie("session_token", {path: "/"})
window.location.reload()
//removeCookie("session_token", {path: "/"})
})
}
// Rofl this is weird
const handleDocsHover = () => {
@@ -158,6 +162,16 @@ const Header = props => {
</div>
</Link>
</ListItem>
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/pricing" style={hrefStyle}>
<div onMouseOver={handleDocsHover} onMouseOut={handleDocsHoverOut} style={{color: DocsHoverColor, cursor: "pointer", display: "flex"}}>
<DescriptionIcon style={{marginRight: "5px"}} />
<span style={{marginTop: 2}}>Pricing</span>
</div>
</Link>
</ListItem>
*/}
{/*
<ListItem style={{textAlign: "center"}}>
<Link to="/configurations" style={hrefStyle}>
@@ -183,6 +197,19 @@ const Header = props => {
color="primary"> Settings</Button>
</Link>
</ListItem>
{/*
<ListItem>
<Link to="/contact" style={hrefStyle}>
<Button
style={{}}
variant="contained"
color="primary"
>
Contact
</Button>
</Link>
</ListItem>
*/}
{userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null :
<ListItem>
<Link to="/admin" style={hrefStyle}>
@@ -299,8 +326,8 @@ const Header = props => {
</div>
// <Divider style={{height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
const loadedCheck = isLoaded ?
<div>
const loadedCheck =
<div style={{minHeight: 68}}>
<BrowserView>
{loginTextBrowser}
</BrowserView>
@@ -308,10 +335,6 @@ const Header = props => {
{loginTextMobile}
</MobileView>
</div>
:
<div>
</div>
// <div style={{backgroundImage: "linear-gradient(-90deg,#342f78 0,#29255e 50%,#1b1947 100%"}}>
return (
<div>
+136 -27
View File
@@ -3,24 +3,36 @@ import React, { useEffect} from 'react';
import {Link} from 'react-router-dom';
import Paper from '@material-ui/core/Paper';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Typography from '@material-ui/core/Typography';
import Switch from '@material-ui/core/Switch';
import Select from '@material-ui/core/Select';
import MenuItem from '@material-ui/core/MenuItem';
import List from '@material-ui/core/List';
import Divider from '@material-ui/core/Divider';
import TextField from '@material-ui/core/TextField';
import ListItem from '@material-ui/core/ListItem';
import Button from '@material-ui/core/Button';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import Grid from '@material-ui/core/Grid';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
import IconButton from '@material-ui/core/IconButton';
import Avatar from '@material-ui/core/Avatar';
import { useAlert } from "react-alert";
import { Dialog, DialogTitle, DialogActions, DialogContent } from '@material-ui/core';
import { useTheme } from '@material-ui/core/styles';
import PolymerIcon from '@material-ui/icons/Polymer';
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import CloseIcon from '@material-ui/icons/Close';
import AppsIcon from '@material-ui/icons/Apps';
import ImageIcon from '@material-ui/icons/Image';
import DeleteIcon from '@material-ui/icons/Delete';
import CachedIcon from '@material-ui/icons/Cached';
import AccessibilityNewIcon from '@material-ui/icons/AccessibilityNew';
import LockIcon from '@material-ui/icons/Lock';
@@ -46,6 +58,8 @@ const Admin = (props) => {
const [curTab, setCurTab] = React.useState(0);
const [users, setUsers] = React.useState([]);
const [organizations, setOrganizations] = React.useState([]);
const [orgSyncResponse, setOrgSyncResponse] = React.useState("");
const [environments, setEnvironments] = React.useState([]);
const [authentication, setAuthentication] = React.useState([]);
const [schedules, setSchedules] = React.useState([])
@@ -182,10 +196,13 @@ const Admin = (props) => {
});
}
const enableCloudSync = (apikey, organization) => {
const enableCloudSync = (apikey, organization, disableSync) => {
setOrgSyncResponse("")
const data = {
apikey: apikey,
organization: organization,
disable: disableSync,
}
const url = globalUrl + '/api/v1/cloud/setup';
@@ -200,21 +217,39 @@ const Admin = (props) => {
'Content-Type': 'application/json; charset=utf-8',
},
})
.then(response =>
response.json().then(responseJson => {
setLoading(false)
console.log(responseJson)
if (responseJson["success"] === false) {
alert.error("Failed setting up cloud sync")
} else {
alert.success("Set up cloud sync!")
}
}),
)
.catch(error => {
setLoading(false)
alert.error("Err: " + error.toString())
});
.then(response => {
setLoading(false)
if (response.status === 200) {
console.log("Cloud sync success?")
} else {
console.log("Cloud sync fail?")
}
return response.json()
})
.then((responseJson) => {
if (!responseJson.success && responseJson.reason !== undefined) {
setOrgSyncResponse(responseJson.reason)
alert.error("Failed to handle sync: "+responseJson.reason)
} else if (!responseJson.success) {
alert.error("Failed to handle sync.")
} else {
getOrgs()
if (disableSync) {
alert.success("Successfully disabled sync!")
} else {
alert.success("Sync successfully set up!")
}
selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync
setSelectedOrganization(selectedOrganization)
setCloudSyncApikey("")
}
})
.catch(error => {
setLoading(false)
alert.error("Err: " + error.toString())
})
}
const onPasswordChange = () => {
@@ -362,9 +397,15 @@ const Admin = (props) => {
for (var key in environments) {
if (environments[key].Name == name) {
if (environments[key].default) {
alert.info("Can't delete the default environment")
alert.error("Can't delete the default environment")
return
}
if (environments[key].type === "cloud") {
alert.error("Can't delete the cloud environments")
return
}
environments[key].archived = true
}
@@ -514,7 +555,7 @@ const Admin = (props) => {
}
const getOrgs = () => {
fetch(globalUrl + "/api/v1/getorgs", {
fetch(globalUrl + "/api/v1/orgs", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -780,6 +821,49 @@ const Admin = (props) => {
</DialogContent>
</Dialog>
const GridItem = (props) => {
const primary = props.data.primary
const secondary = props.data.secondary
const primaryIcon = props.data.icon
const secondaryIcon = props.data.active ?
<CheckCircleIcon style={{color: "green"}} />
:
<CloseIcon style={{color: "red"}} />
return (
<Grid item xs={6}>
<ListItem>
<ListItemAvatar>
<Avatar>
{primaryIcon}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={primary}
secondary={secondary}
/>
{secondaryIcon}
</ListItem>
</Grid>
)
}
const itemColor = "black"
var syncList = [
{
"primary": "Workflows",
"secondary": "",
"active": false,
"icon": <PolymerIcon style={{color: itemColor}}/>,
},
{
"primary": "Apps",
"secondary": "",
"active": false,
"icon": <AppsIcon style={{color: itemColor}}/>,
},
]
const cloudSyncModal =
<Dialog
open={cloudSyncModalOpen}
@@ -798,8 +882,6 @@ const Admin = (props) => {
</span></DialogTitle>
<DialogContent>
What does <a href="https://shuffler.io/docs/hybrid#cloud_sync" target="_blank" style={{textDecoration: "none", color: "#f85a3e"}}>cloud sync</a> do?
<div style={{marginTop: 5}}/>
Cloud Apikey
<div style={{display: "flex", marginBottom: 20, }}>
<TextField
color="primary"
@@ -813,6 +895,7 @@ const Admin = (props) => {
}}
required
fullWidth={true}
disabled={selectedOrganization.cloud_sync}
autoComplete="cloud apikey"
id="apikey_field"
margin="normal"
@@ -822,17 +905,35 @@ const Admin = (props) => {
setCloudSyncApikey(event.target.value)
}}
/>
<Button disabled={cloudSyncApikey.length === 0 || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
setLoading(true)
enableCloudSync(
cloudSyncApikey,
selectedOrganization,
selectedOrganization.cloud_sync,
)
}} color="primary">
Test sync
{selectedOrganization.cloud_sync ?
"Stop sync"
:
"Start sync"
}
</Button>
</div>
{orgSyncResponse.length > 0 ?
<Typography style={{marginTop: 5, marginBottom: 10}}>
Error: {orgSyncResponse}
</Typography>
: null
}
<Grid container style={{width: "100%", marginBottom: 15, }}>
{syncList.map((data, index) => {
return (
<GridItem key={index} data={data} />
)
})}
</Grid>
* New triggers (userinput, hotmail realtime)<div/>
* Execute in the cloud rather than onprem<div/>
* Apps can be built in the cloud<div/>
@@ -1035,9 +1136,9 @@ const Admin = (props) => {
}}
>
Edit user
</Button>
</Button>
</ListItemText>
</ListItem>
</ListItem>
)
})}
</List>
@@ -1288,6 +1389,10 @@ const Admin = (props) => {
primary="Orborus running (TBD)"
style={{minWidth: 200, maxWidth: 200}}
/>
<ListItemText
primary="Type"
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary="Default"
style={{minWidth: 150, maxWidth: 150}}
@@ -1301,7 +1406,7 @@ const Admin = (props) => {
style={{minWidth: 150, maxWidth: 150}}
/>
</ListItem>
{environments === undefined ? null : environments.map((environment, index)=> {
{environments === undefined || environments === null ? null : environments.map((environment, index)=> {
if (!showArchived && environment.archived) {
return null
}
@@ -1321,6 +1426,10 @@ const Admin = (props) => {
primary={"TBD"}
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
/>
<ListItemText
primary={environment.Type}
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
primary={environment.default ? "true" : null}
File diff suppressed because one or more lines are too long
+41 -7
View File
@@ -618,8 +618,7 @@ const AppCreator = (props) => {
"id": props.match.params.appid,
}
if (basedata.info.contact !== undefined) {
if (basedata.info !== undefined && basedata.info.contact !== undefined) {
data.info["contact"] = basedata.info.contact
} else if (contact === "") {
data.info["contact"] = {
@@ -701,7 +700,7 @@ const AppCreator = (props) => {
data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
//console.log(queryitem)
}
}
}
if (item.paths.length > 0) {
for (querykey in item.paths) {
@@ -720,6 +719,30 @@ const AppCreator = (props) => {
newitem.description = queryitem.description
}
data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
//console.log(queryitem)
}
} else {
// Always goes here if they didn't click anything :/
const values = getCurrentPaths(item.url)
const paths = values[0]
for (querykey in paths) {
const queryitem = paths[querykey]
newitem = {
"in": "path",
"name": queryitem,
"description": "Generated by shuffler.io OpenAPI",
"required": true,
"schema": {
"type": "string",
},
}
if (queryitem.description !== undefined) {
newitem.description = queryitem.description
}
data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem)
//console.log(queryitem)
}
@@ -1220,7 +1243,7 @@ const AppCreator = (props) => {
return errormessage
}
const UrlPathParameters = () => {
const getCurrentPaths = (urlPath) => {
var paths = []
var queries = []
@@ -1296,7 +1319,16 @@ const AppCreator = (props) => {
}
}
return [paths, queries]
}
const UrlPathParameters = () => {
const values = getCurrentPaths(urlPath)
const paths = values[0]
const queries = values[1]
if (currentAction.paths !== paths && urlPath.length > 0) {
console.log("IN PATHS SETTER: !", paths)
setActionField("paths", paths)
}
@@ -1563,6 +1595,8 @@ const AppCreator = (props) => {
</Button>
<Button color="primary" variant="outlined" style={{borderRadius: "0px"}} onClick={() => {
console.log(urlPathQueries)
console.log(urlPath)
// value={urlPath}
const errors = getActionErrors()
addActionToView(errors)
setActionsModalOpen(false)
@@ -1653,7 +1687,7 @@ const AppCreator = (props) => {
<div style={{color: "white"}}>
<h2>Test</h2>
Test an action to see whether it performs in an expected way.
<Link target="_blank" to="https://shuffler.io/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}>&nbsp;TBD: Click here to learn more about testing</Link>.
<a target="_blank" href="https://shuffler.io/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}>&nbsp;TBD: Click here to learn more about testing</a>.
<div>
Test :)
</div>
@@ -1704,7 +1738,7 @@ const AppCreator = (props) => {
// <img src={file} id="logo" style={{width: "100%", height: "100%"}} />
const imageData = file.length > 0 ? file : fileBase64
const imageInfo = <img src={imageData} alt="Click to upload an image (174x174)" id="logo" style={{maxWidth: 174, maxHeight: 174,}} />
const imageInfo = <img src={imageData} alt="Click to upload an image (174x174)" id="logo" style={{maxWidth: 174, maxHeight: 174, minWidth: 174, minHeight: 174, objectFit: "contain",}} />
// Random names for type & autoComplete. Didn't research :^)
const landingpageDataBrowser =
@@ -1722,7 +1756,7 @@ const AppCreator = (props) => {
</Breadcrumbs>
<Paper style={boxStyle}>
<h2 style={{marginBottom: "10px", color: "white"}}>General information</h2>
<Link target="_blank" to="https://shuffler.io/docs/apps#create_openapi_app" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</Link>
<a target="_blank" href="https://shuffler.io/docs/apps#create_openapi_app" style={{textDecoration: "none", color: "#f85a3e"}}>Click here to learn more about app creation</a>
<div style={{color: "white", flex: "1", display: "flex", flexDirection: "row"}}>
<Tooltip title="Click to edit the app's image" placement="bottom">
<div style={{flex: "1", margin: 10, border: "1px solid #f85a3e", cursor: "pointer", backgroundColor: inputColor, maxWidth: 174, maxHeight: 174}} onClick={() => {upload.click()}}>
+56 -29
View File
@@ -1,4 +1,4 @@
import React, { useEffect} from 'react';
import React, { useEffect } from 'react';
import { useInterval } from 'react-powerhooks';
@@ -37,6 +37,7 @@ import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import CircularProgress from '@material-ui/core/CircularProgress';
import Dropzone from '../components/Dropzone';
const surfaceColor = "#27292D"
const inputColor = "#383B40"
@@ -134,6 +135,9 @@ const Apps = (props) => {
const [cursearch, setCursearch] = React.useState("")
const [sharingConfiguration, setSharingConfiguration] = React.useState("you")
const [isDropzone, setIsDropzone] = React.useState(false);
const upload = React.useRef(null);
const { start, stop } = useInterval({
duration: 5000,
startImmediate: false,
@@ -177,7 +181,6 @@ const Apps = (props) => {
color: "#ffffff",
width: "100%",
display: "flex",
margin: 20,
}
const paperAppStyle = {
@@ -733,7 +736,7 @@ const Apps = (props) => {
&nbsp;- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
&nbsp;- <a href="https://editor.swagger.io/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI Validator</a>
<div/>
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. The links above are references to OpenAPI tools and other app repositories. There's ten thousands of them.
Apps interact with eachother in workflows. They are created with the app creator, using OpenAPI specification or manually in python. The links above are references to OpenAPI tools and other app repositories. There's thousands of them.
<div/>
<Divider style={{height: 1, backgroundColor: dividerColor, marginTop: 20, marginBottom: 20}} />
<div style={{}}>
@@ -790,8 +793,38 @@ const Apps = (props) => {
//}
}
const uploadFile = (e) => {
const isDropzone = e.dataTransfer?.files.length > 0;
const files = isDropzone ? e.dataTransfer.files : e.target.files;
const reader = new FileReader();
reader.addEventListener('load', (e) => {
const content = e.target.result;
setOpenApiData(content);
setIsDropzone(isDropzone);
setOpenApiModal(true)
})
reader.readAsText(files[0]);
};
useEffect(() => {
if (openApiData.length > 0) {
setOpenApiError('');
validateOpenApi(openApiData);
}
}, [openApiData]);
useEffect(() => {
if (appValidation && isDropzone) {
redirectOpenApi();
setIsDropzone(false);
}
}, [appValidation, isDropzone]);
const appView = isLoggedIn ?
<div style={{maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto",}}>
<Dropzone style={{maxWidth: window.innerWidth > 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}>
<div style={appViewStyle}>
<div style={{flex: 1}}>
<Breadcrumbs aria-label="breadcrumb" separator="" style={{color: "white",}}>
@@ -903,7 +936,7 @@ const Apps = (props) => {
</div>
</div>
</div>
</div>
</Dropzone>
:
null
@@ -1174,7 +1207,7 @@ const Apps = (props) => {
})
.then((responseJson) => {
if (responseJson.success) {
setAppValidation(responseJson.id)
setAppValidation(responseJson.id);
} else {
if (responseJson.reason !== undefined) {
setOpenApiError(responseJson.reason)
@@ -1327,7 +1360,7 @@ const Apps = (props) => {
</Dialog>
: null
const errorText = openApiError.length > 0 ? <div>Error: {openApiError}</div> : null
const errorText = openApiError.length > 0 ? <div style={{marginTop: 10}}>Error: {openApiError}</div> : null
const modalView = openApiModal ?
<Dialog
open={openApiModal}
@@ -1376,28 +1409,22 @@ const Apps = (props) => {
<div />
https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json
*/}
Or paste the YAML or JSON specification
<TextField
style={{backgroundColor: inputColor}}
variant="outlined"
multiline
rows={6}
margin="normal"
InputProps={{
style:{
color: "white",
fontSize: "1em",
},
endAdornment: <Button style={{marginLeft: 10, borderRadius: "0px", marginTop: "0px"}} variant="contained" disabled={openApiData.length === 0 || appValidation.length > 0} color="primary" onClick={() => {
setOpenApiError("")
validateOpenApi(openApiData)
}}>Validate OpenAPI</Button>
}}
onChange={e => setOpenApiData(e.target.value)}
helperText={<span style={{color:"white", marginBottom: "2px",}}>Must point to a version 2 or 3 specification.</span>}
placeholder="OpenAPI text"
fullWidth
/>
<p>Or upload a YAML or JSON specification</p>
<input
hidden
type="file"
ref={upload}
accept="application/JSON, text/yaml, text/x-yaml, application/x-yaml, application/vnd.yaml"
multiple={false}
onChange={uploadFile}
/>
<Button
variant="contained"
color="primary"
onClick={() => upload.current.click()}
>
Upload
</Button>
{errorText}
</DialogContent>
<DialogActions>
-4
View File
@@ -160,10 +160,6 @@ 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 formtitle = register ? <div>Login</div> : <div>Register</div>
// <DialogTitle>{formtitle}</DialogTitle>
console.log("THEME: ", theme.palette.surfaceColor)
const basedata =
<div style={bodyDivStyle}>
<Paper style={{
+26 -26
View File
@@ -109,13 +109,13 @@ const Settings = (props) => {
const getSettings = () => {
fetch(globalUrl+"/api/v1/getsettings", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for WORKFLOW EXECUTION :O!")
@@ -137,24 +137,24 @@ const Settings = (props) => {
if (userInfo.username.length > 0) {
setUsername(userInfo.username)
}
if (userInfo.firstname.length > 0) {
setFirstname(userInfo.firstname)
}
if (userInfo.lastname.length > 0) {
setLastname(userInfo.lastname)
}
if (userInfo.title.length > 0) {
setTitle(userInfo.title)
}
if (userInfo.companyname.length > 0) {
setCompanyname(userInfo.companyname)
}
if (userInfo.phone.length > 0) {
setPhone(userInfo.phone)
}
if (userInfo.email.length > 0) {
setEmail(userInfo.email)
}
//if (userInfo.firstname.length > 0) {
// setFirstname(userInfo.firstname)
//}
//if (userInfo.lastname.length > 0) {
// setLastname(userInfo.lastname)
//}
//if (userInfo.title.length > 0) {
// setTitle(userInfo.title)
//}
//if (userInfo.companyname.length > 0) {
// setCompanyname(userInfo.companyname)
//}
//if (userInfo.phone.length > 0) {
// setPhone(userInfo.phone)
//}
//if (userInfo.email.length > 0) {
// setEmail(userInfo.email)
//}
}
}
@@ -175,7 +175,7 @@ const Settings = (props) => {
<div style={{display: "flex", marginTop: "80px"}}>
<Paper style={boxStyle}>
<h2>APIKEY</h2>
<Link to="/docs/API#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is the API key used for?</Link>
<a target="_blank" href="/docs/API#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is the API key used for?</a>
<TextField
style={{backgroundColor: theme.palette.inputColor, flex: "1"}}
InputProps={{
+37 -6
View File
@@ -33,7 +33,6 @@ import {Link} from 'react-router-dom';
import { useAlert } from "react-alert";
import ChipInput from 'material-ui-chip-input'
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions';
@@ -44,7 +43,7 @@ const inputColor = "#383B40"
const surfaceColor = "#27292D"
const Workflows = (props) => {
const { globalUrl, isLoggedIn, isLoaded, } = props;
const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies} = props;
document.title = "Shuffle - Workflows"
const alert = useAlert()
@@ -83,6 +82,31 @@ const Workflows = (props) => {
}
})
// DEBUG HERE
const handleClickLogout = () => {
//console.log("Cookies: ", cookies)
//console.log("SHOULD LOG OUT")
//console.log(isLoggedIn)
// Don't really care about the logout
//fetch(globalUrl+"/api/v1/logout", {
// credentials: "include",
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
//})
//.then(() => {
// // Log out anyway
// removeCookie("session_token", {path: "/"})
// //window.location = "/login"
//})
//.catch(error => {
// console.log(error)
// removeCookie("session_token", {path: "/"})
//});
}
const deleteModal = deleteModalOpen ?
<Dialog
open={deleteModalOpen}
@@ -124,13 +148,13 @@ const Workflows = (props) => {
const getAvailableWorkflows = () => {
fetch(globalUrl+"/api/v1/workflows", {
method: 'GET',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for workflows :O!")
@@ -149,7 +173,7 @@ const Workflows = (props) => {
if (isLoggedIn) {
alert.error("An error occurred while loading workflows")
} else {
window.location = "/login"
handleClickLogout()
}
return
@@ -337,6 +361,9 @@ const Workflows = (props) => {
}
}
data.execution_org = {"id": ""}
console.log(data)
let linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
@@ -583,6 +610,10 @@ const Workflows = (props) => {
return null
}
if (data.workflow.actions === null || data.workflow.actions === undefined) {
return null
}
var actions = data.workflow.actions.length
if (data.results !== null) {
var results = data.results.length
+8 -4
View File
@@ -126,7 +126,7 @@ func deployWorker(image string, identifier string, env []string) {
log.Printf("[INFO] Found container ID %s", containerId)
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
} else {
log.Printf("[INFO] Empty self container id, continue without NetworkMode")
//log.Printf("[INFO] Empty self container id, continue without NetworkMode")
}
config := &container.Config{
@@ -144,7 +144,7 @@ func deployWorker(image string, identifier string, env []string) {
)
if err != nil {
log.Println(err)
log.Printf("[ERROR] Container create error: %s", err)
return
}
@@ -218,9 +218,11 @@ func initializeImages() {
if baseimageregistry == "" {
baseimageregistry = "docker.io"
log.Printf("Setting baseimageregistry")
}
if baseimagename == "" {
baseimagename = "frikky/shuffle"
log.Printf("Setting baseimagename")
}
// check whether they are the same first
@@ -290,13 +292,15 @@ func main() {
// FIXME - during init, BUILD and/or LOAD worker and app_sdk
// Build/load app_sdk so it can be loaded as 127.0.0.1:5000/walkoff_app_sdk
log.Printf("[INFO] Setting up Docker environment. Downloading worker and App SDK!")
go initializeImages()
initializeImages()
//workerName := "worker"
//workerVersion := "0.1.0"
//workerImage := fmt.Sprintf("docker.pkg.github.com/frikky/shuffle/%s:%s", workerName, workerVersion)
//workerImage := fmt.Sprintf("%s/worker:%s", baseimagename, workerVersion)
// workerImage := fmt.Sprintf("docker.io/%s:worker", baseimagename)
// fmt.Sprintf("%s/%s:app_sdk%s", baseimageregistry, baseimagename, baseimagetagsuffix),
workerImage := fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix)
log.Printf("[INFO] Finished configuring docker environment")
@@ -387,7 +391,7 @@ func main() {
}
if hasStarted && len(executionRequests.Data) > 0 {
log.Printf("[INFO] Body: %s", string(body))
//log.Printf("[INFO] Body: %s", string(body))
// Type string `json:"type"`
}
+8 -7
View File
@@ -694,15 +694,16 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
// FIXME: Force killing a worker should result in a notification somewhere
if len(nextActions) == 0 {
log.Printf("No next action. Finished? Result vs Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
//exit := true
//for _, item := range workflowExecution.Results {
// if item == "EXECUTING" {
// exit = false
// break
// }
//}
exit := true
for _, item := range workflowExecution.Results {
if item.Status == "EXECUTING" {
exit = false
break
}
}
if exit && len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) {
log.Printf("Shutting down.")
shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
}