diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 1a311ee7..ae57b0d2 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -101,7 +101,7 @@ class AppBase: except requests.exceptions.ConnectionError as e: print("Connectionerror: %s" % e) - action_result["result"] = "Bad setup during startup: %d" % e + action_result["result"] = "Bad setup during startup: %s" % e self.send_result(action_result, headers, stream_path) return @@ -288,7 +288,7 @@ class AppBase: # Do stuff here. innervalue = parse_nested_param(data, maxDepth(data)-0) outervalue = parse_nested_param(data, maxDepth(data)-1) - print("INNER: ", outervalue) + print("INNER: ", innervalue) print("OUTER: ", outervalue) if outervalue != innervalue: @@ -710,7 +710,17 @@ class AppBase: continue #print(destinationvalue) - if not run_validation(sourcevalue, condition["condition"]["value"], destinationvalue): + # NEGATE + validation = run_validation(sourcevalue, condition["condition"]["value"], destinationvalue) + + # Configuration = negated because of WorkflowAppActionParam.. + try: + if condition["condition"]["configuration"]: + validation = not validation + except KeyError: + pass + + if not validation: self.logger.info("Failed condition check for %s %s %s." % (sourcevalue, condition["condition"]["value"], destinationvalue)) return False, "Failed condition: %s %s %s" % (sourcevalue, condition["condition"]["value"], destinationvalue) diff --git a/backend/go-app/codegen.go b/backend/go-app/codegen.go index 0aa12fe4..f620cc7f 100644 --- a/backend/go-app/codegen.go +++ b/backend/go-app/codegen.go @@ -398,7 +398,7 @@ func makePythoncode(swagger *openapi3.Swagger, name, url, method string, paramet verifyAddin, ) - log.Printf("%s", data) + //log.Printf("%s", data) return functionname, data } diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 0b58ae5d..7004a6b5 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -662,7 +662,7 @@ func handleApiAuthentication(resp http.ResponseWriter, request *http.Request) (U if len(Userdata.Username) > 0 { return Userdata, nil } else { - return Userdata, errors.New("User is invalid") + return Userdata, errors.New(fmt.Sprintf("User is invalid - no username found: %#v", Userdata)) } } @@ -1605,8 +1605,11 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { } // This is a long check to see if an inactive admin can access the site + parsedAdmin := "false" if !userInfo.Active { if userInfo.Role == "admin" { + parsedAdmin = "true" + ctx := context.Background() q := datastore.NewQuery("Users") var users []User @@ -1672,7 +1675,14 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { Expires: expiration, }) - returnData := fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, userInfo.Session, expiration.Unix()) + returnData := fmt.Sprintf(` + { + "success": true, + "admin": %s, + "orgs": [{"name": "Shuffle", "id": "123", "role": "admin"}], + "selected_org": {"name": "Shuffle", "id": "123", "role": "admin"}, + "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}] + }`, parsedAdmin, userInfo.Session, expiration.Unix()) resp.WriteHeader(200) resp.Write([]byte(returnData)) @@ -6134,7 +6144,7 @@ func handleAppHotload(location string) error { } //log.Printf("Reading app folder: %#v", dir) - err = iterateAppGithubFolders(fs, dir, "", "") + err = iterateAppGithubFolders(fs, dir, "", "", false) if err != nil { log.Printf("Err: %s", err) return err @@ -6345,7 +6355,7 @@ func runInit(ctx context.Context) { //iterateAppGithubFolders(fs, dir, "", "testing") // FIXME: Get all the apps? - iterateAppGithubFolders(fs, dir, "", "") + iterateAppGithubFolders(fs, dir, "", "", false) // Hotloads locally location := os.Getenv("APP_HOTLOAD_FOLDER") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 037c8500..da75a0be 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -94,6 +94,7 @@ type AuthenticationUsage struct { } // An app inside Shuffle +// Source string `json:"source" datastore:"soure" yaml:"source"` - downloadlocation type WorkflowApp struct { Name string `json:"name" yaml:"name" required:true datastore:"name"` IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` @@ -4221,9 +4222,10 @@ func loadSpecificApps(resp http.ResponseWriter, request *http.Request) { // Field1 & 2 can be a lot of things.. type tmpStruct struct { - URL string `json:"url"` - Field1 string `json:"field_1"` - Field2 string `json:"field_2"` + URL string `json:"url"` + Field1 string `json:"field_1"` + Field2 string `json:"field_2"` + ForceUpdate bool `json:"force_update"` } //log.Printf("Body: %s", string(body)) @@ -4265,7 +4267,13 @@ func loadSpecificApps(resp http.ResponseWriter, request *http.Request) { log.Printf("FAiled reading folder: %s", err) } _ = r - iterateAppGithubFolders(fs, dir, "", "") + + if tmpBody.ForceUpdate { + log.Printf("Running with force update!") + } else { + log.Printf("Updating apps with updates") + } + iterateAppGithubFolders(fs, dir, "", "", tmpBody.ForceUpdate) } else if strings.Contains(tmpBody.URL, "s3") { //https://docs.aws.amazon.com/sdk-for-go/api/service/s3/ @@ -4490,8 +4498,13 @@ func iterateWorkflowGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra } // Onlyname is used to -func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra string, onlyname string) error { +func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra string, onlyname string, forceUpdate bool) error { var err error + + allapps := []WorkflowApp{} + + // It's here to prevent getting them in every iteration + ctx := context.Background() for _, file := range dir { if len(onlyname) > 0 && file.Name() != onlyname { continue @@ -4508,7 +4521,7 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin } // Go routine? Hmm, this can be super quick I guess - err = iterateAppGithubFolders(fs, dir, tmpExtra, "") + err = iterateAppGithubFolders(fs, dir, tmpExtra, "", forceUpdate) if err != nil { log.Printf("Error reading folder: %s", err) continue @@ -4591,12 +4604,12 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin fmt.Sprintf("%s:%s_%s", baseDockerName, newName, workflowapp.AppVersion), } - ctx := context.Background() - allapps, err := getAllWorkflowApps(ctx) - if err != nil { - log.Printf("Failed getting apps to verify: %s", err) - continue - //return err + if len(allapps) == 0 { + allapps, err = getAllWorkflowApps(ctx) + if err != nil { + log.Printf("Failed getting apps to verify: %s", err) + continue + } } // Make an option to override existing apps? @@ -4607,7 +4620,7 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin if app.Name == workflowapp.Name && app.AppVersion == workflowapp.AppVersion { // FIXME: Check if there's a new APP_SDK as well. // Skip this check if app_sdk is new. - if app.Hash == md5 && app.Hash != "" { + if app.Hash == md5 && app.Hash != "" && !forceUpdate { skip = true break } @@ -4617,7 +4630,7 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin } } - if skip { + if skip && !forceUpdate { continue } @@ -4677,6 +4690,7 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin if len(removeApps) > 0 { for _, item := range removeApps { + log.Printf("Removing duplicate: %s", item) err = DeleteKey(ctx, "workflowapp", item) if err != nil { log.Printf("Failed deleting %s", item) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2c23c389..8b7966f2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,22 +11,18 @@ import Webhooks from "./views/Webhooks"; import Workflows from "./views/Workflows"; import EditWebhook from "./views/EditWebhook"; import AngularWorkflow from "./views/AngularWorkflow"; -// import ForgotPassword from "./views/ForgotPassword"; -// import ForgotPasswordLink from "./components/ForgotPasswordLink"; import Header from './components/Header'; import Apps from './views/Apps'; import AppCreator from './views/AppCreator'; import Contact from './views/Contact'; import Oauth2 from './views/Oauth2'; -// import About from "./views/About"; -// import Post from "./views/Post"; + import Dashboard from "./views/Dashboard"; import AdminSetup from "./views/AdminSetup"; import Admin from "./views/Admin"; import Docs from "./views/Docs"; -// import RegisterLink from "./views/RegisterLink"; -// import LandingPage from "./views/Landingpage"; + import LandingPageNew from "./views/LandingpageNew"; import LoginPage from "./views/LoginPage"; import SettingsPage from "./views/SettingsPage"; @@ -165,4 +161,3 @@ const App = (message, props) => { }; export default App; - diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index 7a01cb53..cf3c4dfd 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -6,6 +6,9 @@ import {Link} from 'react-router-dom'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; + +import MenuItem from '@material-ui/core/MenuItem'; +import Select from '@material-ui/core/Select'; import Button from '@material-ui/core/Button'; import HomeIcon from '@material-ui/icons/Home'; import PolymerIcon from '@material-ui/icons/Polymer'; @@ -17,7 +20,8 @@ const hoverColor = "#f85a3e" const hoverOutColor = "#e8eaf6" const Header = props => { - const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded } = props; + + const { globalUrl, isLoggedIn, removeCookie, homePage, isLoaded } = props; const [HomeHoverColor, setHomeHoverColor] = useState(hoverOutColor); const [SoarHoverColor, setSoarHoverColor] = useState(hoverOutColor); @@ -100,7 +104,7 @@ const Header = props => { // Handle top bar or something - const loginTextBrowser = !isLoggedIn ? + const loginTextBrowser = !isLoggedIn ?
@@ -192,6 +196,32 @@ const Header = props => { + {userdata === undefined || userdata.orgs.length <= 1 ? null : + + + + } +
@@ -287,7 +317,7 @@ const Header = props => {
{loadedCheck}
- ); -}; + ) +} export default Header; diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 112c29f8..09db2c87 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -1,7 +1,9 @@ -import React, { useEffect } from 'react'; +import React, { useEffect} from 'react'; -import { Link } from 'react-router-dom'; +import {Link} from 'react-router-dom'; import Paper from '@material-ui/core/Paper'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Switch from '@material-ui/core/Switch'; import Select from '@material-ui/core/Select'; import MenuItem from '@material-ui/core/MenuItem'; import List from '@material-ui/core/List'; @@ -26,7 +28,6 @@ const Admin = (props) => { const { globalUrl } = props; const theme = useTheme(); - const [firstRequest, setFirstRequest] = React.useState(true); const [modalUser, setModalUser] = React.useState({}); const [modalOpen, setModalOpen] = React.useState(false); @@ -57,20 +58,20 @@ const Admin = (props) => { 'Content-Type': 'application/json', }, }) - .then(response => - response.json().then(responseJson => { - console.log("RESP: ", responseJson) - if (responseJson["success"] === false) { - alert.error("Failed stopping schedule") - } else { - getAppAuthentication() - alert.success("Successfully stopped schedule!") - } - }), - ) - .catch(error => { - console.log("Error in userdata: ", error) - }); + .then(response => + response.json().then(responseJson => { + console.log("RESP: ", responseJson) + if (responseJson["success"] === false) { + alert.error("Failed stopping schedule") + } else { + getAppAuthentication() + alert.success("Successfully stopped schedule!") + } + }), + ) + .catch(error => { + console.log("Error in userdata: ", error) + }); } const deleteSchedule = (data) => { @@ -106,6 +107,7 @@ const Admin = (props) => { const onPasswordChange = () => { const data = { "username": selectedUser.username, "newpassword": newPassword } const url = globalUrl + '/api/v1/users/passwordchange'; + fetch(url, { mode: 'cors', method: 'POST', @@ -141,24 +143,24 @@ const Admin = (props) => { 'Content-Type': 'application/json', }, }) - .then(response => { - if (response.status === 200) { - getUsers() - } + .then(response => { + if (response.status === 200) { + getUsers() + } - return response.json() - }) - .then((responseJson) => { - if (!responseJson.success && responseJson.reason !== undefined) { - alert.error("Failed to deactivate user: " + responseJson.reason) - } else { - alert.success("Deactivated user " + data.id) - } - }) + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success && responseJson.reason !== undefined) { + alert.error("Failed to deactivate user: "+responseJson.reason) + } else { + alert.success("Deactivated user "+data.id) + } + }) - .catch(error => { - console.log("Error in userdata: ", error) - }); + .catch(error => { + console.log("Error in userdata: ", error) + }); } const submitUser = (data) => { @@ -790,250 +792,342 @@ const Admin = (props) => { ) })} - + : null -const schedulesView = curTab === 3 ? -
-
-

Schedules

- Schedules used in Workflows. Makes locating and control easier. -
- - - - - - - - {schedules === undefined || schedules === null ? null : schedules.map(schedule => { - return ( - - - - - - - - ) - })} - -
- : null - -const authenticationView = curTab === 1 ? -
-
-

App Authentication

- Control the authentication options for individual apps. Actions can be destructive! -
- - - - - - - - - - - - {authentication === undefined ? null : authentication.map(data => { - return ( - - } - style={{ minWidth: 150, maxWidth: 150 }} + const schedulesView = curTab === 3 ? +
+
+

Schedules

+ Schedules used in Workflows. Makes locating and control easier. +
+ + + + + + + + {schedules === undefined || schedules === null ? null : schedules.map(schedule => { + return ( + + - - - - - { - return data.key - }).join(", ")} - style={{ minWidth: 200, maxWidth: 200, overflow: "hidden" }} - /> - - - - - ) - })} - -
- : null - -const environmentView = curTab === 2 ? -
-
-

Environments

- Decides what Orborus environment to execute an action in a workflow in. + + + ) + })} +
- + + + ) + })} + +
+ : null + + const environmentView = curTab === 2 ? +
+
+

Environments

+ Decides what Orborus environment to execute an action in a workflow in. +
+ - - - - - - - - - {environments === undefined ? null : environments.map(environment => { - return ( - - - - - - - - ) - })} - -
- : null + + + + + + + + + {environments === undefined ? null : environments.map(environment => { + return ( + + + + + + + + ) + })} + +
+ : null -// primary={environment.Registered ? "true" : "false"} + const organizationsTab = curTab === 5 ? +
+
+

Organizations

+ Global admin: control organizations +
+ + + + + + + + + + + + {console.log("INVERT")}} /> + style={{minWidth: 150, maxWidth: 150}} + /> + + +
+ : null -const setConfig = (event, newValue) => { - if (newValue === 1) { - getAppAuthentication() - } else if (newValue === 2) { - getEnvironments() - } else if (newValue === 3) { - getSchedules() + const hybridTab = curTab === 4 ? +
+
+

Hybrid

+ +
+ + + + + + + + + + + {console.log("INVERT")}} /> + style={{minWidth: 150, maxWidth: 150}} + /> + + +
+ : null + + // primary={environment.Registered ? "true" : "false"} + + const setConfig = (event, newValue) => { + if (newValue === 1) { + getAppAuthentication() + } else if (newValue === 2) { + getEnvironments() + } else if (newValue === 3) { + getSchedules() + } + + setModalUser({}) + setCurTab(newValue) } - setModalUser({}) - setCurTab(newValue) + const iconStyle = {marginRight: 10} + const data = +
+ + + Users /> + App Authentication/> + Environments/> + Schedules /> + {window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null} + {window.location.protocol == "http:" && window.location.port === "3000" ? Organizations/> : null} + + +
+ {authenticationView} + {usersView} + {environmentView} + {schedulesView} + {hybridTab} + {organizationsTab} +
+
+
+ + return ( +
+ {modalView} + {editUserModal} + {editAuthenticationModal} + {data} +
+ ) } -const data = -
- - - - - - - - -
- {authenticationView} - {usersView} - {environmentView} - {schedulesView} -
-
-
- -return ( -
- {modalView} - {editUserModal} - {editAuthenticationModal} - {data} -
-) -} - -export default Admin; +export default Admin \ No newline at end of file diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index db31586e..294b4d2a 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -949,6 +949,10 @@ const AngularWorkflow = (props) => { if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") { curaction.selectedAuthentication = {} } + } else { + curaction.authentication = [] + curaction.authentication_id = "" + curaction.selectedAuthentication = {} } setSelectedApp(curapp) @@ -2997,7 +3001,7 @@ const AngularWorkflow = (props) => { paddingLeft: 10, minHeight: "100%", zIndex: 1000, - resize: "horizontal", + resize: "vertical", overflow: "auto", } @@ -3512,14 +3516,6 @@ const AngularWorkflow = (props) => {
{data.name}
- -
{ - e.preventDefault() - changeActionParameterVariant("STATIC_VALUE") - }}> - -
-
{datafield} @@ -3552,11 +3548,21 @@ const AngularWorkflow = (props) => {
Condition
+ + +
-
- { setVariableAnchorEl(null) }} key={"less than"}>less than - -
+
diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 0f7daa29..35bef2d4 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -286,7 +286,7 @@ const Apps = (props) => { } return ( - { + { if (selectedApp.id !== data.id) { setSelectedApp(data) console.log(data) @@ -515,7 +515,7 @@ const Apps = (props) => { newActionname = newActionname.replace("_", " ") newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1) return ( - + {newActionname} @@ -540,7 +540,7 @@ const Apps = (props) => { const circleSize = 10 return ( - +
{data.name} @@ -743,7 +743,7 @@ const Apps = (props) => { null // Load data e.g. from github - const getSpecificApps = (url) => { + const getSpecificApps = (url, forceUpdate) => { setValidation(true) setIsLoading(true) @@ -761,6 +761,8 @@ const Apps = (props) => { parsedData["field_2"] = field2 } + parsedData["force_update"] = forceUpdate + alert.success("Getting specific apps from your URL.") var cors = "cors" fetch(globalUrl+"/api/v1/apps/get_existing", { @@ -977,8 +979,8 @@ const Apps = (props) => { window.location.href = "/apps/new?id="+appValidation } - const handleGithubValidation = () => { - getSpecificApps(openApi) + const handleGithubValidation = (forceUpdate) => { + getSpecificApps(openApi, forceUpdate) setLoadAppsModalOpen(false) } @@ -1034,7 +1036,7 @@ const Apps = (props) => { >
- Load from github repo + Load from github repo
@@ -1098,7 +1100,12 @@ const Apps = (props) => { Cancel + diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index 31ccc384..6ed887f3 100644 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -163,6 +163,7 @@ const LoginDialog = props => { // {formtitle} + console.log("THEME: ", theme.palette.surfaceColor) const basedata =
{ - const { globalUrl, isLoaded, userdata, surfaceColor, inputColor } = props; + const { globalUrl, isLoaded, userdata, } = props; + const theme = useTheme(); const alert = useAlert() const [username, setUsername] = useState(""); @@ -61,7 +49,7 @@ const Settings = (props) => { paddingRight: "30px", paddingBottom: "30px", paddingTop: "30px", - backgroundColor: surfaceColor, + backgroundColor: theme.palette.surfaceColor, display: "flex", flexDirection: "column" } @@ -190,7 +178,7 @@ const Settings = (props) => {

APIKEY

What is the API key used for? {

Settings

{
{ onChange={e => setFirstname(e.target.value)} /> {
{ onChange={e => setTitle(e.target.value)} /> {
{ onChange={e => setEmail(e.target.value)} /> {

Password

{
{ onChange={e => setNewPassword(e.target.value)} /> { } return ( - {}}> + {}}>
diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index d86ba371..fbee4052 100644 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -78,10 +78,11 @@ func init() { // Skip random containers. Only handle things related to Shuffle. for _, container := range containers { found := false - //log.Printf("Running? %#v", container) - if container.State != "running" { - continue - } + + // Bad states - it might just be created sometimes, leading to now netowkr + //if container.State == "restarting" || container.State == "paused" || container.State == "exited" || container.State == "dead" { + // continue + //} for _, name := range container.Names { if !strings.Contains(strings.ToLower(name), containerIdentifier) { @@ -120,17 +121,10 @@ func deployWorker(image string, identifier string, env []string) { }, } - // ROFL: https://docker-py.readthedocs.io/en/1.4.0/volumes/ - config := &container.Config{ - Image: image, - Env: env, - } - // Look for Shuffle network and set it - - // FIXME: Move this out of here and have it be a global setting. During init? networkConfig := &network.NetworkingConfig{} if len(shuffleNetwork) > 0 { + log.Printf("Starting worker with network %s", shuffleNetwork) networkConfig = &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ shuffleNetwork: { @@ -139,7 +133,15 @@ func deployWorker(image string, identifier string, env []string) { }, } - env = append(env, fmt.Sprintf("DOCKER_NETWORK", shuffleNetwork)) + env = append(env, fmt.Sprintf("DOCKER_NETWORK=%s", shuffleNetwork)) + } else { + log.Printf("Starting worker WITHOUT any specified network: %s", shuffleNetwork) + } + + // ROFL: https://docker-py.readthedocs.io/en/1.4.0/volumes/ + config := &container.Config{ + Image: image, + Env: env, } //test := &network.EndpointSettings{ diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index edb9c39c..48b11ebc 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -1107,6 +1107,13 @@ func main() { } } + shuffleNetwork := os.Getenv("DOCKER_NETWORK") + if len(shuffleNetwork) > 0 { + log.Printf("Running with Docker network %s", shuffleNetwork) + } else { + log.Printf("No docker network specified for Worker.") + } + // WORKER_TESTING_WORKFLOW should be a workflow ID authorization := "" executionId := ""