Added simple frontend changes
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
urllib3
|
||||
requests
|
||||
urllib3=1.25.9
|
||||
requests=2.25.1
|
||||
|
||||
@@ -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
|
||||
@@ -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}
|
||||
|
||||
@@ -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 ?
|
||||
|
||||
Reference in New Issue
Block a user