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
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
+2 -2
View File
@@ -1,2 +1,2 @@
urllib3
requests
urllib3=1.25.9
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
+44 -44
View File
@@ -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 (
<Draggable
@@ -3991,7 +3989,7 @@ const AngularWorkflow = (props) => {
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</Button>
//
const leftView = leftViewOpen ? <div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}>
<HandleLeftView />
</div> :
const leftView = leftViewOpen ?
<div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}>
<HandleLeftView />
</div>
:
<div style={{minWidth: leftBarSize, maxWidth: leftBarSize, borderRight: "1px solid rgb(91, 96, 100)"}}>
<div style={{cursor: "pointer", height: 20, marginTop: 10, marginLeft: 10}} onClick={() => {
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}
+19 -7
View File
@@ -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 ?
<div>
<div style={{position: "relative"}}>
<div style={{display: "flex"}}>
<div style={{marginRight: 15, marginTop: 10}}>
{imageline}
@@ -618,6 +618,14 @@ const Apps = (props) => {
<p style={{marginTop: 5, marginBottom: 0, maxHeight: 150, overflowY: "auto", overflowX: "hidden",}}>{description}</p>
</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}
{(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ?
<div>
@@ -886,7 +894,7 @@ const Apps = (props) => {
<div style={{flex: 1, marginLeft: 10, marginRight: 10}}>
<div style={{display: "flex", minHeight: 84.81}}>
<div style={{flex: 1}}>
<h2>Your apps ({apps.length+searchableApps.length})</h2>
<h2>Activated apps ({apps.length+searchableApps.length})</h2>
</div>
{isCloud ? null :
<span>
@@ -959,9 +967,13 @@ const Apps = (props) => {
</div>
:
<Paper square style={uploadViewPaperStyle}>
<h4 style={{margin: 10, }}>
Try a broader search term, e.g. http, alert, ticket etc.
</h4>
<Typography style={{margin: 10, }}>
<span>
<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/>
{appSearchLoading ?