Fixed no-path issue with requirement of leading /

This commit is contained in:
frikky
2021-03-29 21:58:48 +02:00
parent 7814eb1e08
commit 1b428b301a
16 changed files with 419 additions and 161 deletions
-3
View File
@@ -7,15 +7,12 @@ WORKDIR /app
ADD ./go-app/main.go /app
ADD ./go-app/walkoff.go /app
ADD ./go-app/docker.go /app
ADD ./go-app/codegen.go /app
ADD ./go-app/files.go /app
ADD ./go-app/oauth2.go /app
ADD ./go-app/go.mod /app
# Required files for code generation
ADD ./app_sdk/app_base.py /app_sdk
ADD ./app_sdk/static_baseline.py /app_sdk
ADD ./app_sdk_kali/app_base.py /app_sdk_kali
ADD ./app_sdk_kali/static_baseline.py /app_sdk_kali
ADD ./app_sdk_blackarch/app_base.py /app_sdk_blackarch
+2 -2
View File
@@ -2,7 +2,7 @@ module shuffle
go 1.13
replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
//replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
require (
@@ -18,7 +18,7 @@ require (
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0 // indirect
github.com/frikky/shuffle-shared v0.0.15
github.com/frikky/shuffle-shared v0.0.20
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.0.0
+137 -2
View File
@@ -77,6 +77,8 @@ var registryName = "registry.hub.docker.com"
var runningEnvironment = "onprem"
var syncUrl = "https://shuffler.io"
//var syncUrl = "http://localhost:5002"
var syncSubUrl = "https://shuffler.io"
//var syncUrl = "http://localhost:5002"
@@ -5463,12 +5465,12 @@ func runInit(ctx context.Context) {
}
iterateOpenApiGithub(fs, dir, "", "")
log.Printf("Finished downloading extra API samples")
log.Printf("[INFO] Finished downloading extra API samples")
}
workflowLocation := os.Getenv("SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION")
if len(workflowLocation) > 0 {
log.Printf("Downloading WORKFLOWS from %s if no workflows - EXTRA workflows", workflowLocation)
log.Printf("[INFO] Downloading WORKFLOWS from %s if no workflows - EXTRA workflows", workflowLocation)
q := datastore.NewQuery("workflow").Limit(35)
var workflows []shuffle.Workflow
_, err = dbclient.GetAll(ctx, q, &workflows)
@@ -5918,6 +5920,138 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
resp.Write(respBody)
}
func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) {
cors := shuffle.HandleCors(resp, request)
if cors {
return
}
user, userErr := shuffle.HandleApiAuthentication(resp, request)
if userErr != nil {
log.Printf("[WARNING] Api authentication failed in make workflow public: %s", userErr)
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 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[4]
}
ctx := context.Background()
if strings.Contains(fileId, "?") {
fileId = strings.Split(fileId, "?")[0]
}
if len(fileId) != 36 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Workflow ID when getting workflow is not valid"}`))
return
}
workflow, err := shuffle.GetWorkflow(ctx, fileId)
if err != nil {
log.Printf("[WARNING] Workflow %s doesn't exist in app publish. User: %s", fileId, user.Id)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// CHECK orgs of user, or if user is owner
// FIXME - add org check too, and not just owner
// Check workflow.Sharing == private / public / org too
if user.Id != workflow.Owner || len(user.Id) == 0 {
if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" {
log.Printf("[INFO] User %s is accessing workflow %s as admin", user.Username, workflow.ID)
} else {
log.Printf("[WARNING] Wrong user (%s) for workflow %s (get workflow)", user.Username, workflow.ID)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
}
if !workflow.IsValid || !workflow.PreviouslySaved {
log.Printf("[INFO] Failed uploading workflow because it's invalid or not saved")
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Invalid workflows are not sharable"}`))
return
}
// Starting validation of the POST workflow
body, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Printf("[WARNING] Body data error on mail: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
parsedWorkflow := shuffle.Workflow{}
err = json.Unmarshal(body, &parsedWorkflow)
if err != nil {
log.Printf("[WARNING] Unmarshal error on mail: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// Super basic validation. Doesn't really matter.
if parsedWorkflow.ID != workflow.ID || len(parsedWorkflow.Actions) != len(workflow.Actions) {
log.Printf("[WARNING] Bad ID during publish: %s vs %s", workflow.ID, parsedWorkflow.ID)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if !workflow.IsValid || !workflow.PreviouslySaved {
log.Printf("[INFO] Failed uploading new workflow because it's invalid or not saved")
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Invalid workflows are not sharable"}`))
return
}
workflowData, err := json.Marshal(parsedWorkflow)
if err != nil {
log.Printf("[WARNING] Failed marshalling workflow: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// Sanitization is done in the frontend as well
parsedWorkflow = shuffle.SanitizeWorkflow(parsedWorkflow)
parsedWorkflow.ID = uuid.NewV4().String()
action := shuffle.CloudSyncJob{
Type: "workflow",
Action: "publish",
OrgId: user.ActiveOrg.Id,
PrimaryItemId: workflow.ID,
SecondaryItem: string(workflowData),
FifthItem: user.Id,
}
err = executeCloudAction(action, user.ActiveOrg.SyncConfig.Apikey)
if err != nil {
log.Printf("[WARNING] Failed cloud PUBLISH: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
return
}
log.Printf("[INFO] Successfully published workflow %s (%s) TO CLOUD", workflow.Name, workflow.ID)
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
}
func initHandlers() {
var err error
ctx := context.Background()
@@ -6041,6 +6175,7 @@ func initHandlers() {
r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS")
// NEW for 0.8.0
r.HandleFunc("/api/v1/workflows/{key}/publish", makeWorkflowPublic).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS")
r.HandleFunc("/api/v1/orgs", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS")
r.HandleFunc("/api/v1/orgs/", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS")
+2 -1
View File
@@ -981,7 +981,7 @@ func validateNewWorkerExecution(body []byte) error {
execution.Status = "FINISHED"
}
log.Printf("BASEEXECUTION LENGTH: %d", len(baseExecution.Workflow.Actions)+extra)
log.Printf("[INFO] BASEEXECUTION LENGTH: %d", len(baseExecution.Workflow.Actions)+extra)
}
// FIXME: Add extra here
@@ -4782,6 +4782,7 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin
workflowapp.Sharing = true
workflowapp.Downloaded = true
workflowapp.Hash = md5
workflowapp.Public = true
err = shuffle.SetWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID)
if err != nil {
+6 -6
View File
@@ -1,8 +1,8 @@
version: '3'
services:
frontend:
#build: ./frontend
image: ghcr.io/frikky/shuffle-frontend:0.8.64
build: ./frontend
image: ghcr.io/frikky/shuffle-frontend:0.8.70
container_name: shuffle-frontend
hostname: shuffle-frontend
ports:
@@ -16,8 +16,8 @@ services:
depends_on:
- backend
backend:
#build: ./backend
image: ghcr.io/frikky/shuffle-backend:0.8.64
build: ./backend
image: ghcr.io/frikky/shuffle-backend:0.8.70
container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME}
# Here for debugging:
@@ -47,7 +47,7 @@ services:
- database
orborus:
#build: ./functions/onprem/orborus
image: ghcr.io/frikky/shuffle-orborus:0.8.63
image: ghcr.io/frikky/shuffle-orborus:0.8.70
container_name: shuffle-orborus
hostname: shuffle-orborus
networks:
@@ -56,7 +56,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- SHUFFLE_APP_SDK_VERSION=0.8.60
- SHUFFLE_WORKER_VERSION=0.8.63
- SHUFFLE_WORKER_VERSION=0.8.70
- ORG_ID=${ORG_ID}
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
+1 -1
View File
@@ -21,7 +21,7 @@ COPY ./*.json /usr/src/app/
RUN yarn build
# Production environment
FROM nginx:latest
FROM nginx:1.19
RUN mkdir -p /usr/share/nginx/html/build
RUN mkdir -p /usr/share/nginx/html/css
+20 -1
View File
@@ -39,6 +39,23 @@ const data = [{
'border-color': '#81c784',
'background-width': '100%',
'background-height': '100%',
'border-radius': '5px',
},
},
{
selector: `node[app_name="Shuffle Tools"]`,
css: {
'width': '30px',
'height': '30px',
'font-size': '0px',
},
},
{
selector: `node[app_name="Testing"]`,
css: {
'width': '30px',
'height': '30px',
'font-size': '0px',
},
},
{
@@ -112,7 +129,7 @@ const data = [{
},
},
{
selector: 'node:selected',
selector: ':selected',
css: {
'background-color': '#77b0d0',
'border-color': '#77b0d0',
@@ -187,6 +204,8 @@ const data = [{
'border-width': '12px',
'transition-property': 'border-width',
'transition-duration': '0.25s',
'font-size': '30px',
'label': 'data(label)',
},
},
{
+107 -18
View File
@@ -92,7 +92,6 @@ const AngularWorkflow = (props) => {
const theme = useTheme();
const [bodyWidth, bodyHeight] = useWindowSize();
const appBarSize = 75
var to_be_copied = ""
const [cystyle, ] = useState(cytoscapestyle)
@@ -190,18 +189,22 @@ const AngularWorkflow = (props) => {
const [workflowExecutions, setWorkflowExecutions] = React.useState([]);
const [defaultEnvironmentIndex, setDefaultEnvironmentIndex] = React.useState(0)
// This should all be set once, not on every iteration
// Use states and don't update lol
const cloudSyncEnabled = props.userdata !== undefined && props.userdata.active_org !== null && props.userdata.active_org !== undefined ? props.userdata.active_org.cloud_sync === true : false
//const triggerEnvironments = cloudSyncEnabled ? ["cloud", "onprem"] : environments
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"
const appBarSize = isCloud ? 75 : 60
const triggerEnvironments = isCloud ? ["cloud"] : ["onprem", "cloud"]
const unloadText = 'Are you sure you want to leave without saving (CTRL+S)?'
useBeforeunload(() => {
if (!lastSaved) {
return unloadText
}
})
const [elements, setElements] = useState([])
// No point going as fast, as the nodes aren't realtime anymore, but bulk updated.
// Set it from 2500 to 6000 to reduce overall load
@@ -1121,7 +1124,7 @@ const AngularWorkflow = (props) => {
}
if (responseJson.public) {
alert.info("This workflow is public. You'll have to save it to make it your own!")
alert.info("This workflow is public. You will have to save it to make it your own!")
setLastSaved(false)
}
@@ -1215,9 +1218,36 @@ const AngularWorkflow = (props) => {
setSelectedTrigger({})
}
// Comparing locations between nodes and setting views
const onNodeDrag = (event, newAppAuth) => {
//console.log("DRAGGING: ", event.target)
//console.log("LEN2: ", event.target.edges.length)
/*
event.target.animate({
style: {
"border-width": "12px",
"border-opacity": ".7",
}
}, {
duration: animationDuration,
})
event.target.animate({
style: {
"border-width": "12px",
"border-opacity": ".7",
}
}, {
duration: animationDuration,
})
*/
}
// Nodeselectbatching:
// https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
const onNodeSelect = (event, newAppAuth) => {
const data = event.target.data()
setLastSaved(false)
@@ -1619,6 +1649,17 @@ const AngularWorkflow = (props) => {
});
}
if (!firstrequest && graphSetup && established && props.match.params.key !== workflow.id && workflow.id !== undefined && workflow.id !== null && workflow.id.length > 0) {
//console.log(props.match.params.key, workflow.id)
//getWorkflow()
//setCy()
//getWorkflowExecution(props.match.params.key, "")
//setEstablished(false)
//setGraphSetup(false)
window.location.pathname = "/workflows/"+props.match.params.key
}
useEffect(() => {
if (firstrequest) {
setFirstrequest(false)
@@ -1642,14 +1683,14 @@ const AngularWorkflow = (props) => {
}
// App length necessary cus of cy initialization
if (elements.length === 0 && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true)
setupGraph()
} else if (!established && cy !== undefined && apps !== null && apps !== undefined && apps.length > 0 && Object.getOwnPropertyNames(workflow).length > 0 && authLoaded){
//This part has to load LAST, as it's kind of not async.
//This means we need everything else to happen first.
console.log("AUTH IN HERE: ", appAuthentication)
//
//console.log("IN THIS PART AGAIN")
setEstablished(true)
cy.edgehandles({
@@ -1677,6 +1718,9 @@ const AngularWorkflow = (props) => {
cy.on('mouseover', 'node', (e) => onNodeHover(e))
cy.on('mouseout', 'node', (e) => onNodeHoverOut(e))
// Handles dragging
//cy.on('drag', 'node', (e) => onNodeDrag(e))
//cy.on('mouseover', 'node', () => $(targetElement).addClass('mouseover'));
//cy.on('cxttapstart', 'node', (e) => edgeHandler.start(e.target))
@@ -1711,7 +1755,7 @@ const AngularWorkflow = (props) => {
const onNodeHover = (event) => {
event.target.animate({
style: {
"border-width": "5px",
"border-width": "7px",
"border-opacity": ".7",
}
}, {
@@ -1727,17 +1771,23 @@ const AngularWorkflow = (props) => {
// This is here to have a proper transition for lines
const onEdgeHover = (event) => {
if (event === null || event === undefined) {
return
}
const sourcecolor = cy.getElementById(event.target.data("source")).style("border-color")
const targetcolor = cy.getElementById(event.target.data("target")).style("border-color")
event.target.animate({
style: {
"line-fill": "linear-gradient",
'target-arrow-color': targetcolor,
"line-gradient-stop-colors": [sourcecolor, targetcolor],
"line-gradient-stop-positions": [0, 1],
},
duration: 0,
})
if (sourcecolor !== null && sourcecolor !== undefined && targetcolor !== null && targetcolor !== undefined) {
event.target.animate({
style: {
"line-fill": "linear-gradient",
'target-arrow-color': targetcolor,
"line-gradient-stop-colors": [sourcecolor, targetcolor],
"line-gradient-stop-positions": [0, 1],
},
duration: 0,
})
}
}
@@ -1799,6 +1849,31 @@ const AngularWorkflow = (props) => {
conditions: conditions,
hasErrors: branch.has_errors
};
// This is an attempt at prettier edges. The numbers are weird to work with.
/*
//http://manual.graphspace.org/projects/graphspace-python/en/latest/demos/edge-types.html
const sourcenode = actions.find(node => node.data._id === branch.source_id)
const destinationnode = actions.find(node => node.data._id === branch.destination_id)
if (sourcenode !== undefined && destinationnode !== undefined && branch.source_id !== branch.destination_id) {
//node.data._id = action["id"]
console.log("SOURCE: ", sourcenode.position)
console.log("DESTINATIONNODE: ", destinationnode.position)
var opposite = true
if (sourcenode.position.x > destinationnode.position.x) {
opposite = false
} else {
opposite = true
}
edge.style = {
'control-point-distance': opposite ? ["25%", "-75%"] : ["-10%", "90%"],
'control-point-weight': ['0.3', '0.7'],
}
}
*/
return edge;
})
@@ -4506,7 +4581,7 @@ const AngularWorkflow = (props) => {
: null
}
onClick={() => {
console.log("CHANGE FIELD")
//console.log("CHANGE FIELD")
}}
onBlur={(e) => {
changeActionVariable(data.action_field, e.target.value)
@@ -4803,6 +4878,7 @@ const AngularWorkflow = (props) => {
onClick={() => {
setSelectedEdge({})
var data = {
condition: conditionValue,
source: sourceValue,
@@ -4821,6 +4897,18 @@ const AngularWorkflow = (props) => {
}
}
var label = ""
if (selectedEdge.conditions.length === 1) {
label = selectedEdge.conditions.length+" condition"
} else if (selectedEdge.conditions.length > 1) {
label = selectedEdge.conditions.length+" conditions"
}
var currentedge = cy.getElementById(selectedEdge.id)
if (currentedge !== undefined && currentedge !== null) {
currentedge.data().label = label
}
setSelectedEdge(selectedEdge)
workflow.branches[selectedEdgeIndex] = selectedEdge
setWorkflow(workflow)
@@ -6494,7 +6582,7 @@ const AngularWorkflow = (props) => {
</span>
</Tooltip>
{/* <FileMenu /> */}
<WorkflowMenu />
{workflow.configuration !== null && workflow.configuration !== undefined && workflow.configuration.exit_on_error !== undefined ? <WorkflowMenu /> : null}
</div>
</div>
)
@@ -7211,6 +7299,7 @@ const AngularWorkflow = (props) => {
stylesheet={cystyle}
boxSelectionEnabled={true}
autounselectify={false}
showGrid={true}
cy={(incy) => {
// FIXME: There's something specific loading when
// you do the first hover of a node. Why is this different?
+3 -3
View File
@@ -1565,9 +1565,9 @@ const AppCreator = (props) => {
}
// Url verification
if (currentAction.url.length === 0) {
errormessage.push("URL path can't be empty.")
} else if (!currentAction.url.startsWith("/") && baseUrl.length > 0) {
//if (currentAction.url.length === 0) {
// errormessage.push("URL path can't be empty.")
if (!currentAction.url.startsWith("/") && baseUrl.length > 0 && currentAction.url.length > 0) {
errormessage.push("URL must start with /")
}
+109 -96
View File
@@ -68,11 +68,9 @@ const flexContainerStyle = {
}
const flexBoxStyle = {
width: "333px",
height: "125px",
margin: "10px",
borderRadius: "4px",
boxShadow: "0px 1px 2px rgba(0, 0, 0, 0.16), 0px 2px 4px rgba(0, 0, 0, 0.12), 0px 1px 8px rgba(0, 0, 0, 0.1)",
width: 333,
height: 125,
borderRadius: 4,
boxSizing: "border-box",
letterSpacing: "0.4px",
color: "#D6791E",
@@ -299,37 +297,37 @@ const MyView = (props) => {
}
const paperAppContainer = {
cursor: "pointer",
display: "flex",
flexWrap: 'wrap',
alignContent: "space-between",
}
const paperAppStyle = {
minHeight: "148px",
width: "333px",
margin: "10px",
minHeight: 130,
width: "100%",
color: "white",
backgroundColor: surfaceColor,
padding: "12px 12px 0px 15px",
borderRadius: 5,
padding: "10px",
cursor: "pointer",
display: "flex",
boxSizing: "border-box",
position: "relative",
}
const gridContainer = {
cursor: "pointer",
height: "auto",
color: "white",
margin: "10px",
backgroundColor: surfaceColor,
}
const workFlowActionStyle = {
const workflowActionStyle = {
flex: "1",
display: "flex",
width: "150px",
width: 150,
height: 44,
justifyContent: "space-between",
overflow: "hidden"
}
@@ -535,23 +533,28 @@ const MyView = (props) => {
});
}
const getWorkFlowMeta = (data) => {
const getWorkflowMeta = (data) => {
let triggers = 0
let schedules = 0
let webhooks = 0
let webhookImg = ""
let scheduleImg = ""
let subflows = 0
if (data.triggers !== undefined && data.triggers !== null && data.triggers.length > 0) {
triggers = data.triggers.length
for (let key in data.triggers) {
if (data.triggers[key].app_name === "Webhook") {
webhooks += 1
webhookImg = data.triggers[key].large_image
//webhookImg = data.triggers[key].large_image
} else if (data.triggers[key].app_name === "Schedule") {
schedules += 1
scheduleImg = data.triggers[key].large_image
//scheduleImg = data.triggers[key].large_image
} else if (data.triggers[key].app_name === "Subflow") {
subflows += 1
}
}
}
return [schedules, webhooks, webhookImg, scheduleImg];
return [triggers, schedules, webhooks, subflows]
}
// dropdown with copy etc I guess
@@ -565,9 +568,9 @@ const MyView = (props) => {
boxWidth = "4px"
}
var boxColor = "orange"
var boxColor = "#FECC00"
if (data.is_valid) {
boxColor = "green"
boxColor = "#86c142"
}
const menuClick = (event) => {
@@ -575,45 +578,57 @@ const MyView = (props) => {
setAnchorEl(event.currentTarget);
}
const actions = data.actions !== null ? data.actions.length : 0
const [schedules, webhooks] = getWorkFlowMeta(data);
var parsedName = data.name
console.log("LEN: ", parsedName.length)
if (parsedName !== undefined && parsedName !== null && parsedName.length > 25) {
parsedName = parsedName.slice(0,25)+".."
}
const actions = data.actions !== null ? data.actions.length : 0
const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data)
const imgSize = 25
return (
<Paper square style={paperAppStyle} onClick={(e) => {
}}>
<div style={{width: boxWidth, backgroundColor: boxColor,}} onClick={() => {
if (selectedWorkflow.id !== data.id) {
setSelectedWorkflow(data)
//getWorkflowExecution(data.id)
}
}}/>
<Grid container style={{margin: "0px 10px 0px 10px", flex: 1}}>
<Grid item xs={4} spacing={4} style={{padding: 10,}}>
<Paper square style={paperAppStyle} >
<div style={{position: "absolute", bottom: 1, left: 1, height: 12, width: 12, backgroundColor: boxColor, borderRadius: "0 100px 0 0",}} />
<Grid item style={{display: "flex", flexDirection: "column", width: "100%"}}>
<Grid item style={{flex: 1, display: "flex"}}>
<div onClick={() => {
if (selectedWorkflow.id !== data.id) {
setSelectedWorkflow(data)
//getWorkflowExecution(data.id)
}
}}>
<Typography variant="h6">
{data.name}
</Typography>
</div>
<Grid item style={{flex: 1, display: "flex", maxHeight: 34,}}>
<Typography variant="h6" style={{marginBottom: 0, paddingBottom: 0, maxHeight: 30,}}>
{parsedName}
</Typography>
</Grid>
<Grid item style={workFlowActionStyle}>
<Tooltip color="primary" title="Edit workflow" placement="bottom">
<BubbleChartIcon/>
<Grid item style={workflowActionStyle}>
<Tooltip color="primary" title="Action amount" placement="bottom">
<span style={{color: "#979797", display: "flex"}}>
<BubbleChartIcon style={{marginTop: "auto", marginBottom: "auto",}} />
<Typography style={{marginLeft: 5, marginTop: "auto", marginBottom: "auto",}}>
{actions}
</Typography>
</span>
</Tooltip>
<Tooltip color="primary" title="Execute workflow" placement="bottom">
<PlayArrowIcon color="secondary" disabled={!data.is_valid} onClick={() => executeWorkflow(data.id)} />
<Tooltip color="primary" title="Trigger amount" placement="bottom">
<span style={{marginLeft: 10, color: "#979797", display: "flex"}}>
<RestoreIcon style={{color: "#979797", marginTop: "auto", marginBottom: "auto",}}/>
<Typography style={{marginLeft: 5, marginTop: "auto", marginBottom: "auto",}}>
{triggers}
</Typography>
</span>
</Tooltip>
<Tooltip color="primary" title="Subflows used" placement="bottom">
<span style={{marginLeft: 10, display: "flex", color: "#979797", }}>
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" style={{color: "#979797", marginTop: "auto", marginBottom: "auto",}}>
<path d="M0 0H15V15H0V0ZM16 16H18V18H16V16ZM16 13H18V15H16V13ZM16 10H18V12H16V10ZM16 7H18V9H16V7ZM16 4H18V6H16V4ZM13 16H15V18H13V16ZM10 16H12V18H10V16ZM7 16H9V18H7V16ZM4 16H6V18H4V16Z" fill="#979797"/>
</svg>
<Typography style={{marginLeft: 5, marginTop: "auto", marginBottom: "auto",}}>
{subflows}
</Typography>
</span>
</Tooltip>
{/*
<Tooltip color="primary" title={`Actions: ${data.actions.length}`} placement="bottom">
<AppsIcon />
</Tooltip>
{webhooks > 0 ?
<Tooltip color="primary" title={`Webhooks: ${webhooks}`} placement="bottom">
<RestoreIcon />
</Tooltip>
@@ -623,8 +638,9 @@ const MyView = (props) => {
<RestoreIcon />
</Tooltip>
: null}
*/}
</Grid>
<Grid item style={{flex: "1", justifyContent: "left", overflow: "hidden"}}>
<Grid item style={{flex: "1", justifyContent: "left", overflow: "hidden", marginTop: 5,}}>
{data.tags !== undefined ?
data.tags.map((tag, index) => {
if (index >= 3) {
@@ -634,7 +650,7 @@ const MyView = (props) => {
return (
<Chip
key={index}
style={{height: 30, marginRight: 5, marginTop: 2, cursor: "pointer",}}
style={{backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",}}
label={tag}
variant="outlined"
color="primary"
@@ -644,30 +660,29 @@ const MyView = (props) => {
: null}
</Grid>
</Grid>
</Grid>
{data.actions !== undefined && data.actions !== null ?
<Grid item style={{display:"flex",flexDirection:"column",justifyContent:"space-between"}}>
<Grid item style={{display:"flex",flexDirection:"column", justifyContent:"space-between"}}>
<Grid>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
style={{color: "white"}}
onClick={menuClick}
style={{padding:"0px",color:"white"}}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={() => {
setOpen(false)
setAnchorEl(null)
}}
>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
style={{color: "white"}}
onClick={menuClick}
style={{padding:"0px",color:"white", color:"#979797"}}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={() => {
setOpen(false)
setAnchorEl(null)
}}
>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setModalOpen(true)
setEditingWorkflow(data)
@@ -685,26 +700,25 @@ const MyView = (props) => {
exportWorkflow(data)
setOpen(false)
}} key={"export"}>{"Export"}</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
setDeleteModalOpen(true)
setSelectedWorkflowId(data.id)
setOpen(false)
}} key={"delete"}>{"Delete"}</MenuItem>
</Menu>
</Grid>
<Grid>
<Link to={"/workflows/"+data.id}>
<Tooltip title="Edit workflow" placement="bottom">
<EditIcon style={{background: "#F85A3E",
boxShadow: "0px 1px 2px rgba(0, 0, 0, 0.16), 0px 2px 4px rgba(0, 0, 0, 0.12), 0px 1px 8px rgba(0, 0, 0, 0.1)",
borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize: "small"}} />
</Tooltip>
</Link>
</Grid>
</Menu>
</Grid>
<Grid>
<Link to={"/workflows/"+data.id}>
<Tooltip title="Edit workflow" placement="bottom">
<EditIcon style={{background: "#F85A3E", borderRadius: "4px", color: "black", height: 20, width: 20, padding: 7, fontSize: "small"}} />
</Tooltip>
</Link>
</Grid>
</Grid>
: null}
</Paper>
: null}
</Paper>
</Grid>
)
}
@@ -772,7 +786,6 @@ borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize:
marginTop: "5px",
color: "white",
backgroundColor: surfaceColor,
cursor: "pointer",
display: "flex",
}
@@ -1294,13 +1307,12 @@ borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize:
let workflowData = "";
if (workflows.length > 0) {
const columns = [
{ field: 'id', headerName: 'ID', width: 70, sortable: false, },
{ field: 'title', headerName: 'Title', width: 330, },
{ field: 'actions', headerName: 'Actions', width: 200, sortable: false,
disableClickEventBubbling: true,
renderCell: (params) => {
const data = params.row.record;
let [schedules, webhooks] = getWorkFlowMeta(data);
let [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data);
return <Grid item>
<Link to={"/workflows/"+data.id}>
@@ -1340,10 +1352,11 @@ borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize:
if (index >= 3) {
return null
}
return (
<Chip
key={index}
style={{height: 30, marginRight: 5, marginTop: 2, cursor: "pointer",}}
style={{backgroundColor: "#3d3f43", height: 30, marginRight: 5, paddingLeft: 5, paddingRight: 5, height: 28, cursor: "pointer", borderColor: "#3d3f43", color: "white",}}
label={tag}
variant="outlined"
color="primary"
@@ -1445,13 +1458,13 @@ borderRadius: "4px", color: "black", height: "30px", "width": "30px", fontSize:
</div>
{view === "grid" && (
<div style={paperAppContainer}>
<Grid container spacing={4} style={paperAppContainer}>
{workflows.map((data, index) => {
return (
<WorkflowPaper key={index} data={data} />
)
})}
</div>
</Grid>
)}
{view === "list" && (
+12 -12
View File
@@ -447,12 +447,12 @@ const Workflows = (props) => {
for (var branchkey in data.branches) {
const branch = data.branches[branchkey]
if (branch.source_id === data.actions[key].id) {
console.log("CHANGING SOURCE ID IN ACTION")
//console.log("CHANGING SOURCE ID IN ACTION")
branch.source_id = newId
}
if (branch.destination_id === data.actions[key].id) {
console.log("CHANGING DESTINATION ID IN ACTION")
//console.log("CHANGING DESTINATION ID IN ACTION")
branch.destination_id = newId
}
}
@@ -505,10 +505,8 @@ const Workflows = (props) => {
data = sanitizeWorkflow(data)
alert.info("Sanitizing and publishing "+data.name)
const url = isCloud ? globalUrl : "https://shuffler.io"
// This ALWAYS talks to Shuffle cloud
fetch(url+"/api/v1/workflows/"+data.id+"/publish", {
fetch(globalUrl+"/api/v1/workflows/"+data.id+"/publish", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -521,7 +519,11 @@ const Workflows = (props) => {
if (response.status !== 200) {
console.log("Status not 200 for workflow publish :O!")
} else {
alert.success("Successfully published workflow")
if (isCloud) {
alert.success("Successfully published workflow")
} else {
alert.success("Successfully published workflow to https://shuffler.io")
}
}
return response.json()
@@ -690,12 +692,10 @@ const Workflows = (props) => {
setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags)))
}
}} key={"change"}>{"Change details"}</MenuItem>
{isCloud ?
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
console.log("Should publish", data)
publishWorkflow(data)
}} key={"publish"}>{"Publish Workflow"}</MenuItem>
: null}
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
console.log("Should publish", data)
publishWorkflow(data)
}} key={"publish"}>{"Publish Workflow"}</MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
copyWorkflow(data)
setOpen(false)
+1 -1
View File
@@ -1,5 +1,5 @@
NAME=shuffle-orborus
VERSION=0.8.64
VERSION=0.8.70
echo "Running docker build with $NAME:$VERSION"
#docker rmi frikky/shuffle:$NAME --force
+1 -1
View File
@@ -248,7 +248,7 @@ func initializeImages() {
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
}
if workerVersion == "" {
workerVersion = "0.8.64"
workerVersion = "0.8.70"
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
}
+3 -2
View File
@@ -11,7 +11,8 @@ RUN go get github.com/docker/docker/api/types && \
go get github.com/docker/docker/client && \
go get github.com/gorilla/mux && \
go get github.com/patrickmn/go-cache && \
go get github.com/frikky/shuffle-shared
go get github.com/frikky/shuffle-shared && \
go get github.com/satori/go.uuid
RUN go build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker .
@@ -21,7 +22,7 @@ FROM alpine:3.12
ENV SHUFFLE_BASE_IMAGE_REGISTRY=docker.io
ENV SHUFFLE_BASE_IMAGE_NAME=frikky/shuffle
ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.8.5
ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.8.70
RUN apk add --no-cache bash
COPY --from=builder /app/ /
+1 -1
View File
@@ -1,5 +1,5 @@
NAME=shuffle-worker
VERSION=0.8.64
VERSION=0.8.70
echo "Running docker build with $NAME:$VERSION"
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
+14 -11
View File
@@ -24,6 +24,7 @@ import (
//"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
dockerclient "github.com/docker/docker/client"
"github.com/satori/go.uuid"
"github.com/gorilla/mux"
"github.com/patrickmn/go-cache"
@@ -278,7 +279,7 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
// Waiting to see if it exits.. Stupid, but stable(r)
if workflowExecution.ExecutionSource != "default" {
log.Printf("Handling NON-default execution source %s - NOT waiting and validating!", workflowExecution.ExecutionSource)
log.Printf("[INFO] Handling NON-default execution source %s - NOT waiting and validating!", workflowExecution.ExecutionSource)
} else if workflowExecution.ExecutionSource == "default" {
time.Sleep(2 * time.Second)
@@ -850,12 +851,13 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
appname = strings.Replace(appname, ".", "-", -1)
appversion = strings.Replace(appversion, ".", "-", -1)
image := fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion)
image := fmt.Sprintf("%s:%s_%s", baseimagename, strings.ToLower(action.AppName), action.AppVersion)
if strings.Contains(image, " ") {
image = strings.ReplaceAll(image, " ", "-")
}
identifier := fmt.Sprintf("%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId)
// Added UUID to identifier just in case
identifier := fmt.Sprintf("%s_%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId, uuid.NewV4())
if strings.Contains(identifier, " ") {
identifier = strings.ReplaceAll(identifier, " ", "-")
}
@@ -938,15 +940,15 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
}
// Uses a few ways of getting / checking if an app is available
// 1. Try original
// 2. Go to lowercase
// 1. Try original with lowercase
// 2. Go to original
// 3. Add remote repo location
// 4. Actually download last repo
images := []string{
fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, strings.ToLower(action.AppName), action.AppVersion),
image,
fmt.Sprintf("%s:%s_%s", baseimagename, strings.ToLower(action.AppName), action.AppVersion),
fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion),
fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, strings.ToLower(action.AppName), action.AppVersion),
}
// If cleanup is set, it should run for efficiency
@@ -959,6 +961,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
}
log.Printf("[WARNING] Failed CLEANUP execution. Downloading image remotely.")
image = images[2]
reader, err := dockercli.ImagePull(context.Background(), image, pullOptions)
if err != nil {
log.Printf("[ERROR] Failed getting %s. The couldn't be find locally, AND is missing.", image)
@@ -996,7 +999,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
}
} else {
err = deployApp(dockercli, image, identifier, env, workflowExecution)
err = deployApp(dockercli, images[0], identifier, env, workflowExecution)
if err != nil {
if strings.Contains(err.Error(), "exited prematurely") {
shutdown(workflowExecution, action.ID, err.Error(), true)
@@ -1004,7 +1007,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
// Trying to replace with lowercase to deploy again. This seems to work with Dockerhub well.
// FIXME: Should try to remotely download directly if this persists.
image = fmt.Sprintf("%s:%s_%s", baseimagename, strings.ToLower(action.AppName), action.AppVersion)
image = images[1]
if strings.Contains(image, " ") {
image = strings.ReplaceAll(image, " ", "-")
}
@@ -1015,7 +1018,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
shutdown(workflowExecution, action.ID, err.Error(), true)
}
image = fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, strings.ToLower(action.AppName), action.AppVersion)
image = images[2]
if strings.Contains(image, " ") {
image = strings.ReplaceAll(image, " ", "-")
}
@@ -1026,7 +1029,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
shutdown(workflowExecution, action.ID, err.Error(), true)
}
log.Printf("[WARNING] Failed deploying image THRICE. Attempting to download the latter as last resort.")
log.Printf("[WARNING] Failed deploying image THREE TIMES. Attempting to download the latter as last resort.")
reader, err := dockercli.ImagePull(context.Background(), image, pullOptions)
if err != nil {
log.Printf("[ERROR] Failed getting %s. The couldn't be find locally, AND is missing.", image)