Added simple frontend changes

This commit is contained in:
frikky
2021-03-25 10:06:30 +01:00
parent feab975320
commit 6810c44233
6 changed files with 162 additions and 133 deletions
-4
View File
@@ -1,9 +1,5 @@
# app_sdk.py # app_sdk.py
This is the SDK used for apps to behave like they should. This is the SDK used for apps to behave like they should.
To change it in the backend, upload it to Buckets/shuffler.appspot.com/generated_apps/baseline.
# static_baseline.py
It's used for python code generation and should be under MIT. Has to be located here because it's used by the backend.
## If you want to update apps.. PS: downloads from docker hub do overrides.. :) ## If you want to update apps.. PS: downloads from docker hub do overrides.. :)
1. Write your code & check if runtime works 1. Write your code & check if runtime works
+2 -2
View File
@@ -1,2 +1,2 @@
urllib3 urllib3=1.25.9
requests requests=2.25.1
-76
View File
@@ -1,76 +0,0 @@
import os
import sys
import time
import logging
import requests
# Goal here:
# * Make an app from WALKOFF able to run without app_base.py from WALKOFF
# # How:
# * Make it rely 100% on INPUT throug HTTP invocations instead of redis READS
# # But really, how?
# * Make a WORKER that reads the queue, and reuses a function
# Here to get it global
apikey = ""
try:
apikey = os.environ["FUNCTION_APIKEY"]
except KeyError:
pass
# Authorize the execution
def authorization(request):
# This is basically my issue, but it enforces the use of an internal API key for execution
try:
apikey = os.environ["FUNCTION_APIKEY"]
except KeyError:
return f"Internal server error", 500
# Check API key from ENV authentication
authentication = request.headers.get("Authorization")
if authentication == None: return f"Unauthorized", 401
apikey_split = authentication.split(" ")
if apikey_split[0] != "Bearer" or len(apikey_split) != 2:
return f"Apikey error", 401
if apikey != apikey_split[1]:
return f"Unauthorized", 401
return run(request)
class AppBase:
""" The base class for Python-based Walkoff applications, handles Redis and logging configurations. """
__version__ = None
app_name = None
def __init__(self, redis=None, logger=None, console_logger=None):#, docker_client=None):
self.logger = logger if logger is not None else logging.getLogger("AppBaseLogger")
self.redis=redis
self.console_logger=console_logger
self.current_execution_id = None
self.url = "https://shuffler.io"
self.apikey = apikey
@classmethod
async def run(cls, action):
""" Connect to Redis and HTTP session, await actions """
logging.basicConfig(format="{asctime} - {name} - {levelname}:{message}", style='{')
logger = logging.getLogger(f"{cls.__name__}")
logger.setLevel(logging.DEBUG)
app = cls(redis=None, logger=logger, console_logger=logger)
# Authorization for the app/function to control the workflow
# Function will crash if its wrong, which it probably should.
await app.execute_action(action)
async def execute_action(self, action):
# FIXME - add request for the function STARTING here. Use "results stream" or something
# PAUSED, AWAITING_DATA, PENDING, COMPLETED, ABORTED, EXECUTING, SUCCESS, FAILURE
self.authorization = action["authorization"]
self.execution_id = action["execution_id"]
self.current_execution_id = action["execution_id"]
@@ -0,0 +1,97 @@
import React, {useState} from 'react';
import {Typography, } from '@material-ui/core';
const Workflow = (props) => {
const { workflow, appAuthentication, apps } = props
const [requiredActions, setRequiredActions] = React.useState([])
const [firstLoad, setFirstLoad] = React.useState("")
// Rofl
if (workflow === undefined || workflow === null) {
return null
}
if (apps === undefined || apps === null) {
return null
}
if (appAuthentication === undefined || appAuthentication === null) {
return null
}
if (firstLoad.length === 0 || firstLoad !== workflow.id) {
setFirstLoad(workflow.id)
const newactions = []
for (var key in workflow.actions) {
var newaction = {
"large_image": "",
"app_name": "",
"app_version": "",
"must_activate": false,
"must_authenticate": false,
"action_ids": [],
}
const action = workflow.actions[key]
console.log(action)
const app = apps.find(app => app.name === action.app_name && app.app_version === action.app_version)
if (app === undefined || app === null) {
console.log("COULDNT FIND APP - SEARCH BACKEND")
newaction.app_name = action.app_name
newaction.app_version = action.app_version
} else {
newaction.app_name = app.name
newaction.app_version = app.app_version
console.log("APP: ", app)
if (action.authentication_id === "" && app.authentication.required === true) {
console.log("Requires auth!")
newaction.must_authenticate = true
newaction.action_ids.push(action.id)
}
//newaction.app_name = action.app_name
//newaction.app_name = action.app_version
}
if (action.errors !== undefined && action.errors !== null && action.errors.length > 0) {
console.log("Has errors!")
}
console.log("NEWACTION: ", newaction)
if (newaction.must_authenticate || newaction.must_activate) {
newactions.push(newaction)
}
}
console.log("ACTIONS: ", newactions)
setRequiredActions(newactions)
}
const AppSection = (props) => {
const {action} = props
return (
<div>
<Typography variant="body2">Name: {action.app_name}:{action.app_version}. </Typography>
</div>
)
}
console.log(requiredActions)
return (
<div>
<Typography variant="h6">Workflow: {workflow.id}</Typography>
{requiredActions.map((data, index) => {
return (
<AppSection key={index} action={data} />
)
})}
</div>
)
}
export default Workflow
+43 -43
View File
@@ -92,8 +92,7 @@ const AngularWorkflow = (props) => {
const theme = useTheme(); const theme = useTheme();
const [bodyWidth, bodyHeight] = useWindowSize(); const [bodyWidth, bodyHeight] = useWindowSize();
const appBarSize = 74 const appBarSize = 75
const headerSize = 60
var to_be_copied = "" var to_be_copied = ""
const [cystyle, ] = useState(cytoscapestyle) const [cystyle, ] = useState(cytoscapestyle)
@@ -124,6 +123,7 @@ const AngularWorkflow = (props) => {
const [newVariableDescription, setNewVariableDescription] = React.useState(""); const [newVariableDescription, setNewVariableDescription] = React.useState("");
const [newVariableValue, setNewVariableValue] = React.useState(""); const [newVariableValue, setNewVariableValue] = React.useState("");
const [workflowDone, setWorkflowDone] = React.useState(false) const [workflowDone, setWorkflowDone] = React.useState(false)
const [authLoaded, setAuthLoaded] = React.useState(false)
const [localFirstrequest, setLocalFirstrequest] = React.useState(true) const [localFirstrequest, setLocalFirstrequest] = React.useState(true)
const [requiresAuthentication, setRequiresAuthentication] = React.useState(false) const [requiresAuthentication, setRequiresAuthentication] = React.useState(false)
const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false) const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false)
@@ -1002,7 +1002,6 @@ const AngularWorkflow = (props) => {
.then((response) => { .then((response) => {
if (response.status !== 200) { if (response.status !== 200) {
console.log("Status not 200 for apps :O!") console.log("Status not 200 for apps :O!")
return
} }
return response.json() return response.json()
@@ -1018,18 +1017,27 @@ const AngularWorkflow = (props) => {
newauth.push(responseJson.data[key]) newauth.push(responseJson.data[key])
} }
if (reset === true) { if (cy !== undefined) {
console.log("APP RESET = reset cy") console.log("NEW AUTH = reset cy's onnodeselect")
// Remove the old listener for select, run with new one
cy.removeListener('select')
cy.on('select', 'node', (e) => onNodeSelect(e, newauth)) cy.on('select', 'node', (e) => onNodeSelect(e, newauth))
cy.on('select', 'edge', (e) => onEdgeSelect(e))
} }
setAppAuthentication(newauth) setAppAuthentication(newauth)
setAuthLoaded(true)
} else { } else {
setAuthLoaded(true)
alert.error("Failed getting authentications") alert.error("Failed getting authentications")
} }
}) })
.catch(error => { .catch(error => {
alert.error(error.toString()) setAuthLoaded(true)
}); alert.error("Auth loading error: ", error.toString())
})
} }
@@ -1187,17 +1195,20 @@ const AngularWorkflow = (props) => {
setSelectedTrigger({}) setSelectedTrigger({})
} }
// Nodeselectbatching:
// https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once
const onNodeSelect = (event, newAppAuth) => { const onNodeSelect = (event, newAppAuth) => {
const data = event.target.data() const data = event.target.data()
setLastSaved(false) setLastSaved(false)
const node = cy.getElementById(data.id) //const node = cy.getElementById(data.id)
if (node.length > 0) { //if (node.length > 0) {
node.addClass('shuffle-hover-highlight') // node.addClass('shuffle-hover-highlight')
} //}
//const branch = workflow.branches.filter(branch => branch.source_id === data.id || branch.destination_id === data.id) //const branch = workflow.branches.filter(branch => branch.source_id === data.id || branch.destination_id === data.id)
console.log("NODE: ", data) console.log("NODE: ", data)
console.log("APPAUTH: ", newAppAuth)
//console.log("BRANCHES: ", branch) //console.log("BRANCHES: ", branch)
if (data.type === "ACTION") { if (data.type === "ACTION") {
@@ -1213,13 +1224,14 @@ const AngularWorkflow = (props) => {
return return
} }
setSelectedAction(curaction)
const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version) const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version)
if (!curapp || curapp === undefined) { if (!curapp || curapp === undefined) {
alert.error("App "+curaction.app_name+" not found. Did someone delete it?") alert.error("App "+curaction.app_name+" not found. Is it activated?")
//return //return
} else { } else {
console.log("AUTHENTICATION: ", curapp.authentication)
setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null)
if (curapp.authentication.required) { if (curapp.authentication.required) {
// Setup auth here :) // Setup auth here :)
@@ -1230,6 +1242,8 @@ const AngularWorkflow = (props) => {
} }
var tmpAuth = JSON.parse(JSON.stringify(newAppAuth)) var tmpAuth = JSON.parse(JSON.stringify(newAppAuth))
console.log("FOUND AUTH: ", tmpAuth)
//console.log("Checking authentication: ", tmpAuth) //console.log("Checking authentication: ", tmpAuth)
for (var key in tmpAuth) { for (var key in tmpAuth) {
var item = tmpAuth[key] var item = tmpAuth[key]
@@ -1248,6 +1262,7 @@ const AngularWorkflow = (props) => {
} }
} }
console.log("OPTIONS: ", authenticationOptions)
curaction.authentication = authenticationOptions curaction.authentication = authenticationOptions
//console.log("Authentication: ", authenticationOptions) //console.log("Authentication: ", authenticationOptions)
if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") { if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") {
@@ -1260,6 +1275,7 @@ const AngularWorkflow = (props) => {
} }
setSelectedApp(curapp) setSelectedApp(curapp)
setSelectedAction(curaction)
} }
if (environments !== undefined && environments !== null) { if (environments !== undefined && environments !== null) {
@@ -1271,28 +1287,6 @@ const AngularWorkflow = (props) => {
setSelectedActionEnvironment(env) setSelectedActionEnvironment(env)
} }
/*
var params = []
const fixedName = "$"+curaction.label.toLowerCase().replace(" ", "_")
for (var actionkey in workflow.actions) {
if (workflow.actions[actionkey].id === curaction.id) {
continue
}
for (var paramkey in workflow.actions[actionkey].parameters) {
const param = workflow.actions[actionkey].parameters[paramkey]
if (param.value === null || param.value === undefined || !param.value.includes("$")) {
continue
}
const innername = param.value.toLowerCase().replace(" ", "_")
if (innername.includes(fixedName)) {
console.log("FOUND!: ", innername)
}
}
}
*/
} else if (data.type === "TRIGGER") { } else if (data.type === "TRIGGER") {
//console.log("Should handle trigger "+data.triggertype) //console.log("Should handle trigger "+data.triggertype)
//console.log(data) //console.log(data)
@@ -1631,7 +1625,12 @@ const AngularWorkflow = (props) => {
if (elements.length === 0 && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) { if (elements.length === 0 && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true) setGraphSetup(true)
setupGraph() setupGraph()
} else if (!established && cy !== undefined && apps !== null && apps !== undefined && apps.length > 0 && Object.getOwnPropertyNames(workflow).length > 0){ } 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)
setEstablished(true) setEstablished(true)
cy.edgehandles({ cy.edgehandles({
handleNodes: (el) => el.isNode(), handleNodes: (el) => el.isNode(),
@@ -2648,9 +2647,9 @@ const AngularWorkflow = (props) => {
const [hover, setHover] = React.useState(false) const [hover, setHover] = React.useState(false)
// FIXME - add label to apps, as this might be slow with A LOT of apps // FIXME - add label to apps, as this might be slow with A LOT of apps
var newAppname = app.name
newAppname = newAppname.replace("_", " ").charAt(0).toUpperCase()+newAppname.substring(1)
const maxlen = 24 const maxlen = 24
var newAppname = app.name.replace("_", " ", -1)
newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1)
if (newAppname.length > maxlen) { if (newAppname.length > maxlen) {
newAppname = newAppname.slice(0, maxlen)+".." newAppname = newAppname.slice(0, maxlen)+".."
} }
@@ -2661,7 +2660,6 @@ const AngularWorkflow = (props) => {
const pixelSize = !hover ? "2px" : "4px" const pixelSize = !hover ? "2px" : "4px"
newAppStyle.borderLeft = app.is_valid ? `${pixelSize} solid green` : `${pixelSize} solid orange` newAppStyle.borderLeft = app.is_valid ? `${pixelSize} solid green` : `${pixelSize} solid orange`
return ( return (
<Draggable <Draggable
onDrag={(e) => {handleAppDrag(e, app)}} onDrag={(e) => {handleAppDrag(e, app)}}
@@ -3991,7 +3989,7 @@ const AngularWorkflow = (props) => {
const rightsidebarStyle = { const rightsidebarStyle = {
position: "fixed", position: "fixed",
right: 0, right: 0,
top: headerSize+1, top: appBarSize+1,
height: "100%", height: "100%",
bottom: 0, bottom: 0,
minWidth: 350, minWidth: 350,
@@ -6537,9 +6535,11 @@ const AngularWorkflow = (props) => {
//}}>Execute websocket</Button> //}}>Execute websocket</Button>
// //
const leftView = leftViewOpen ? <div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}> const leftView = leftViewOpen ?
<div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}>
<HandleLeftView /> <HandleLeftView />
</div> : </div>
:
<div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}> <div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}>
<div style={{cursor: "pointer", height: 20, marginTop: 10, marginLeft: 10}} onClick={() => { <div style={{cursor: "pointer", height: 20, marginTop: 10, marginLeft: 10}} onClick={() => {
setLeftViewOpen(true) setLeftViewOpen(true)
@@ -7172,7 +7172,7 @@ const AngularWorkflow = (props) => {
elements={elements} elements={elements}
minZoom={0.35} minZoom={0.35}
maxZoom={2.00} maxZoom={2.00}
style={{width: bodyWidth-leftBarSize-15, height: bodyHeight-appBarSize-1, backgroundColor: surfaceColor}} style={{width: bodyWidth-leftBarSize-15, height: bodyHeight-appBarSize-5, backgroundColor: surfaceColor}}
stylesheet={cystyle} stylesheet={cystyle}
boxSelectionEnabled={true} boxSelectionEnabled={true}
autounselectify={false} autounselectify={false}
+19 -7
View File
@@ -2,8 +2,8 @@ import React, { useEffect } from 'react';
import { useInterval } from 'react-powerhooks'; import { useInterval } from 'react-powerhooks';
import {Grid, Select, Paper, Divider, ButtonBase, Button, TextField, FormControl, MenuItem, Tooltip, FormControlLabel, Switch, Input, Breadcrumbs, Chip, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress} from '@material-ui/core'; import {IconButton, Typography, Grid, Select, Paper, Divider, ButtonBase, Button, TextField, FormControl, MenuItem, Tooltip, FormControlLabel, Switch, Input, Breadcrumbs, Chip, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress} from '@material-ui/core';
import {Apps as AppsIcon, Cached as CachedIcon, Publish as PublishIcon, CloudDownload as CloudDownloadIcon, Edit as EditIcon, Delete as DeleteIcon} from '@material-ui/icons'; import {OpenInNew as OpenInNewIcon,Apps as AppsIcon, Cached as CachedIcon, Publish as PublishIcon, CloudDownload as CloudDownloadIcon, Edit as EditIcon, Delete as DeleteIcon} from '@material-ui/icons';
import { useTheme } from '@material-ui/core/styles'; import { useTheme } from '@material-ui/core/styles';
@@ -607,7 +607,7 @@ const Apps = (props) => {
//fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"),
var baseInfo = newAppname.length > 0 ? var baseInfo = newAppname.length > 0 ?
<div> <div style={{position: "relative"}}>
<div style={{display: "flex"}}> <div style={{display: "flex"}}>
<div style={{marginRight: 15, marginTop: 10}}> <div style={{marginRight: 15, marginTop: 10}}>
{imageline} {imageline}
@@ -618,6 +618,14 @@ const Apps = (props) => {
<p style={{marginTop: 5, marginBottom: 0, maxHeight: 150, overflowY: "auto", overflowX: "hidden",}}>{description}</p> <p style={{marginTop: 5, marginBottom: 0, maxHeight: 150, overflowY: "auto", overflowX: "hidden",}}>{description}</p>
</div> </div>
</div> </div>
{isCloud ?
<a href={"https://shuffler.io/apps/"+selectedApp.id} style={{textDecoration: "none", color: "#f85a3e"}} target="_blank">
<IconButton style={{top: -10, right: 0, position: "absolute", color: "#f85a3e"}} >
<OpenInNewIcon style={{}} />
</IconButton>
</a>
: null}
{activateButton} {activateButton}
{(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ? {(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ?
<div> <div>
@@ -886,7 +894,7 @@ const Apps = (props) => {
<div style={{flex: 1, marginLeft: 10, marginRight: 10}}> <div style={{flex: 1, marginLeft: 10, marginRight: 10}}>
<div style={{display: "flex", minHeight: 84.81}}> <div style={{display: "flex", minHeight: 84.81}}>
<div style={{flex: 1}}> <div style={{flex: 1}}>
<h2>Your apps ({apps.length+searchableApps.length})</h2> <h2>Activated apps ({apps.length+searchableApps.length})</h2>
</div> </div>
{isCloud ? null : {isCloud ? null :
<span> <span>
@@ -959,9 +967,13 @@ const Apps = (props) => {
</div> </div>
: :
<Paper square style={uploadViewPaperStyle}> <Paper square style={uploadViewPaperStyle}>
<h4 style={{margin: 10, }}> <Typography style={{margin: 10, }}>
Try a broader search term, e.g. http, alert, ticket etc. <span>
</h4> <Link to={"https://shuffler.io/search"} style={{textDecoration: "none", color: "#f85a3e"}}>
Click here
</Link> to search ALL apps, not just your activated ones.
</span>
</Typography>
<div/> <div/>
{appSearchLoading ? {appSearchLoading ?