diff --git a/backend/app_sdk/README.md b/backend/app_sdk/README.md index 754392ff..170781f3 100644 --- a/backend/app_sdk/README.md +++ b/backend/app_sdk/README.md @@ -1,9 +1,5 @@ # app_sdk.py This is the SDK used for apps to behave like they should. -To change it in the backend, upload it to Buckets/shuffler.appspot.com/generated_apps/baseline. - -# static_baseline.py -It's used for python code generation and should be under MIT. Has to be located here because it's used by the backend. ## If you want to update apps.. PS: downloads from docker hub do overrides.. :) 1. Write your code & check if runtime works diff --git a/backend/app_sdk/requirements.txt b/backend/app_sdk/requirements.txt index 0da82340..acde3f06 100644 --- a/backend/app_sdk/requirements.txt +++ b/backend/app_sdk/requirements.txt @@ -1,2 +1,2 @@ -urllib3 -requests +urllib3=1.25.9 +requests=2.25.1 diff --git a/backend/app_sdk/static_baseline.py b/backend/app_sdk/static_baseline.py deleted file mode 100644 index 152131cd..00000000 --- a/backend/app_sdk/static_baseline.py +++ /dev/null @@ -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"] diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx new file mode 100644 index 00000000..0e83f81b --- /dev/null +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -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 ( +
+ Name: {action.app_name}:{action.app_version}. +
+ ) + } + + console.log(requiredActions) + + return ( +
+ Workflow: {workflow.id} + {requiredActions.map((data, index) => { + return ( + + ) + })} +
+ ) +} + +export default Workflow diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index a90dad83..63af9c76 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -92,8 +92,7 @@ const AngularWorkflow = (props) => { const theme = useTheme(); const [bodyWidth, bodyHeight] = useWindowSize(); - const appBarSize = 74 - const headerSize = 60 + const appBarSize = 75 var to_be_copied = "" const [cystyle, ] = useState(cytoscapestyle) @@ -124,6 +123,7 @@ const AngularWorkflow = (props) => { const [newVariableDescription, setNewVariableDescription] = React.useState(""); const [newVariableValue, setNewVariableValue] = React.useState(""); const [workflowDone, setWorkflowDone] = React.useState(false) + const [authLoaded, setAuthLoaded] = React.useState(false) const [localFirstrequest, setLocalFirstrequest] = React.useState(true) const [requiresAuthentication, setRequiresAuthentication] = React.useState(false) const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false) @@ -1002,7 +1002,6 @@ const AngularWorkflow = (props) => { .then((response) => { if (response.status !== 200) { console.log("Status not 200 for apps :O!") - return } return response.json() @@ -1018,18 +1017,27 @@ const AngularWorkflow = (props) => { newauth.push(responseJson.data[key]) } - if (reset === true) { - console.log("APP RESET = reset cy") + if (cy !== undefined) { + 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', 'edge', (e) => onEdgeSelect(e)) } + setAppAuthentication(newauth) + setAuthLoaded(true) } else { + setAuthLoaded(true) alert.error("Failed getting authentications") } }) .catch(error => { - alert.error(error.toString()) - }); + setAuthLoaded(true) + alert.error("Auth loading error: ", error.toString()) + }) } @@ -1187,17 +1195,20 @@ const AngularWorkflow = (props) => { setSelectedTrigger({}) } + // Nodeselectbatching: + // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once const onNodeSelect = (event, newAppAuth) => { const data = event.target.data() setLastSaved(false) - const node = cy.getElementById(data.id) - if (node.length > 0) { - node.addClass('shuffle-hover-highlight') - } + //const node = cy.getElementById(data.id) + //if (node.length > 0) { + // node.addClass('shuffle-hover-highlight') + //} //const branch = workflow.branches.filter(branch => branch.source_id === data.id || branch.destination_id === data.id) console.log("NODE: ", data) + console.log("APPAUTH: ", newAppAuth) //console.log("BRANCHES: ", branch) if (data.type === "ACTION") { @@ -1213,13 +1224,14 @@ const AngularWorkflow = (props) => { return } - setSelectedAction(curaction) const curapp = apps.find(a => a.name === curaction.app_name && a.app_version === curaction.app_version) 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 } else { + + console.log("AUTHENTICATION: ", curapp.authentication) setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) if (curapp.authentication.required) { // Setup auth here :) @@ -1230,6 +1242,8 @@ const AngularWorkflow = (props) => { } var tmpAuth = JSON.parse(JSON.stringify(newAppAuth)) + console.log("FOUND AUTH: ", tmpAuth) + //console.log("Checking authentication: ", tmpAuth) for (var key in tmpAuth) { var item = tmpAuth[key] @@ -1248,6 +1262,7 @@ const AngularWorkflow = (props) => { } } + console.log("OPTIONS: ", authenticationOptions) curaction.authentication = authenticationOptions //console.log("Authentication: ", authenticationOptions) if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") { @@ -1260,6 +1275,7 @@ const AngularWorkflow = (props) => { } setSelectedApp(curapp) + setSelectedAction(curaction) } if (environments !== undefined && environments !== null) { @@ -1271,28 +1287,6 @@ const AngularWorkflow = (props) => { 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") { //console.log("Should handle trigger "+data.triggertype) //console.log(data) @@ -1631,7 +1625,12 @@ const AngularWorkflow = (props) => { if (elements.length === 0 && !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){ + } 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) cy.edgehandles({ handleNodes: (el) => el.isNode(), @@ -2648,9 +2647,9 @@ const AngularWorkflow = (props) => { const [hover, setHover] = React.useState(false) // 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 + var newAppname = app.name.replace("_", " ", -1) + newAppname = newAppname.charAt(0).toUpperCase()+newAppname.substring(1) if (newAppname.length > maxlen) { newAppname = newAppname.slice(0, maxlen)+".." } @@ -2660,7 +2659,6 @@ const AngularWorkflow = (props) => { const newAppStyle = JSON.parse(JSON.stringify(paperAppStyle)) const pixelSize = !hover ? "2px" : "4px" newAppStyle.borderLeft = app.is_valid ? `${pixelSize} solid green` : `${pixelSize} solid orange` - return ( { const rightsidebarStyle = { position: "fixed", right: 0, - top: headerSize+1, + top: appBarSize+1, height: "100%", bottom: 0, minWidth: 350, @@ -6537,9 +6535,11 @@ const AngularWorkflow = (props) => { //}}>Execute websocket // - const leftView = leftViewOpen ?
- -
: + const leftView = leftViewOpen ? +
+ +
+ :
{ setLeftViewOpen(true) @@ -7172,7 +7172,7 @@ const AngularWorkflow = (props) => { elements={elements} minZoom={0.35} 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} boxSelectionEnabled={true} autounselectify={false} diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index b7bc7626..fc08763c 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -2,8 +2,8 @@ import React, { useEffect } from 'react'; 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 {Apps as AppsIcon, Cached as CachedIcon, Publish as PublishIcon, CloudDownload as CloudDownloadIcon, Edit as EditIcon, Delete as DeleteIcon} from '@material-ui/icons'; +import {IconButton, Typography, Grid, Select, Paper, Divider, ButtonBase, Button, TextField, FormControl, MenuItem, Tooltip, FormControlLabel, Switch, Input, Breadcrumbs, Chip, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress} from '@material-ui/core'; +import {OpenInNew as OpenInNewIcon,Apps as AppsIcon, Cached as CachedIcon, Publish as PublishIcon, CloudDownload as CloudDownloadIcon, Edit as EditIcon, Delete as DeleteIcon} from '@material-ui/icons'; import { useTheme } from '@material-ui/core/styles'; @@ -607,7 +607,7 @@ const Apps = (props) => { //fetch(globalUrl+"/api/v1/get_openapi/"+urlParams.get("id"), var baseInfo = newAppname.length > 0 ? -
+
{imageline} @@ -618,6 +618,14 @@ const Apps = (props) => {

{description}

+ {isCloud ? + + + + + + : null} + {activateButton} {(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ?
@@ -886,7 +894,7 @@ const Apps = (props) => {
-

Your apps ({apps.length+searchableApps.length})

+

Activated apps ({apps.length+searchableApps.length})

{isCloud ? null : @@ -959,9 +967,13 @@ const Apps = (props) => {
: -

- Try a broader search term, e.g. http, alert, ticket etc. -

+ + + + Click here + to search ALL apps, not just your activated ones. + +
{appSearchLoading ?