@@ -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
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+269
-156
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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" }} />
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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"}}> TBD: Click here to learn more about testing</Link>.
|
||||
<a target="_blank" href="https://shuffler.io/docs/apps#testing" style={{textDecoration: "none", color: "#f85a3e"}}> 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
@@ -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) => {
|
||||
- <a href="https://apis.guru/browse-apis/" style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">OpenAPI directory</a>
|
||||
- <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>
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user