From 787ca47add3417c70f03f4dcfd100da8043161e7 Mon Sep 17 00:00:00 2001 From: Frikky Date: Wed, 27 Sep 2023 21:25:45 +0200 Subject: [PATCH] Added further admin panel fixes --- backend/app_sdk/app_base.py | 24 +- backend/go-app/go.mod | 4 +- frontend/src/components/Appsearch.jsx | 49 +--- frontend/src/components/Branding.jsx | 126 ++++++++-- frontend/src/components/Header.jsx | 1 - frontend/src/components/Priority.jsx | 2 +- frontend/src/views/Admin.jsx | 17 +- frontend/src/views/AppCreator.jsx | 328 +++++++++++++++++++++++--- frontend/src/views/Apps.jsx | 3 +- frontend/src/views/SettingsPage.jsx | 42 ++-- frontend/src/views/Workflows.jsx | 28 ++- functions/onprem/orborus/orborus.go | 4 +- 12 files changed, 474 insertions(+), 154 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 98a5f8aa..d22bc865 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -1,11 +1,12 @@ import os import ast -import copy import sys import re +import copy import time import base64 import json +import random import liquid import logging import urllib3 @@ -504,10 +505,12 @@ class AppBase: except Exception as e: print(f"[WARNING] Failed adding parameter for logs: {e}") - # FIXME: Adding retries here. try: finished = False for i in range (0, 10): + # Random sleeptime between 0 and 1 second, with 0.1 increments + sleeptime = float(random.randint(0, 10) / 10) + try: ret = requests.post(url, headers=headers, json=action_result, timeout=10, verify=False) @@ -516,29 +519,29 @@ class AppBase: finished = True break else: - self.logger.info(f"[ERROR] RESP: {ret.text}") + self.logger.info(f"[ERROR] Bad resp {ret.status_code}: {ret.text}") except requests.exceptions.RequestException as e: self.logger.info(f"[DEBUG] Request problem: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue except TimeoutError as e: self.logger.info(f"[DEBUG] Timeout or request: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue except requests.exceptions.ConnectionError as e: self.logger.info(f"[DEBUG] Connectionerror: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue except http.client.RemoteDisconnected as e: self.logger.info(f"[DEBUG] Remote: {e}") - time.sleep(0.1) + time.sleep(sleeptime) #time.sleep(5) continue @@ -555,8 +558,11 @@ class AppBase: # Not sure why this would work tho :) action_result["status"] = "FAILURE" action_result["result"] = json.dumps({"success": False, "reason": "POST error: Failed connecting to %s over 10 retries to the backend" % url}) - self.logger.info(f"[DEBUG] Before typeerror stream result - NOT finished after 10 requests") - ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result, verify=False) + self.logger.info(f"[ERROR] Before typeerror stream result - NOT finished after 10 requests") + + #ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result, verify=False) + self.send_result(action_result, {"Content-Type": "application/json", "Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams") + return self.logger.info(f"""[DEBUG] Successful request result request: Status= {ret.status_code} & Response= {ret.text}. Action status: {action_result["status"]}""") except requests.exceptions.ConnectionError as e: diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 274bde57..abb8c18e 100755 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -1,6 +1,6 @@ module shuffle-shared -replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared +//replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared go 1.19 @@ -19,7 +19,7 @@ require ( github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.4.41 + github.com/shuffle/shuffle-shared v0.4.47 golang.org/x/crypto v0.9.0 google.golang.org/api v0.125.0 google.golang.org/appengine v1.6.7 diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx index 090dc620..7408dad5 100644 --- a/frontend/src/components/Appsearch.jsx +++ b/frontend/src/components/Appsearch.jsx @@ -22,10 +22,9 @@ import { import aa from 'search-insights' const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") const Appsearch = props => { - const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList} = props + const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; - //const alert = useAlert(); const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows const xs = parsedXs === undefined || parsedXs === null ? 12 : parsedXs //const theme = useTheme(); @@ -41,45 +40,6 @@ const Appsearch = props => { const borderRadius = 3 window.title = "Shuffle | Apps | Find and integration any app" - const setUserSpecialzedApp = (user, data) => { - // var data = newfields] - console.log("data value", data) - const appData = {"user_id":user,"specialized_apps":[{}]} - console.log("User Check for appdata:", user) - appData["specialized_apps"][0]["name"] = data["name"] - appData["specialized_apps"][0]["image"] = data["image_url"] - appData["specialized_apps"][0]["category"] = data["categories"].toString() - console.log("AppData:",appData) - console.log("setActionImageList",setActionImageList) - console.log("actionImageList",actionImageList) - - const finalData = actionImageList.concat(appData["specialized_apps"]) - appData["specialized_apps"]=finalData - fetch(globalUrl + "/api/v1/users/updateuser", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - body: JSON.stringify(appData), - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for set creator :O!"); - } - toast("Sucessfully updated specialzed app.") - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success && responseJson.reason !== undefined) { - toast("Failed updating user: " + responseJson.reason); - } - }) - .catch((error) => { - console.log(error); - }); - }; // value={currentRefinement} const SearchBox = ({currentRefinement, refine, isSearchStalled} ) => { @@ -180,13 +140,8 @@ const Appsearch = props => { setMouseHoverIndex(-1) }} onClick={() => { if(isCreatorPage === true){ - console.log("data:",data) - console.log("userdata.id",userdata.id) - console.log("is creator", isCreatorPage) - if (setNewSelectedApp !== undefined) { - // setUserSpecialzedApp = data + if (setNewSelectedApp !== undefined && setUserSpecialzedApp !== undefined) { setUserSpecialzedApp(userdata.id, data) - //setActionImageList(userdata.id, data) } } if (setNewSelectedApp !== undefined) { diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index e565a013..9dd40791 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import ReactGA from 'react-ga4'; import theme from "../theme.jsx"; +import { ToastContainer, toast } from "react-toastify" import { Paper, @@ -14,22 +15,87 @@ import { //import { useAlert const Branding = (props) => { - const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; - //const alert = useAlert(); - const [publishingInfo, setPublishingInfo] = useState(""); + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; + //const alert = useAlert(); + const [publishingInfo, setPublishingInfo] = useState(""); + const [publishRequirements, setPublishRequirements] = useState([]) - // Should enable / disable org branding - const handleChangePublishing = () => { - console.log("Handle change publishing"); - } + + const handleEditOrg = (joinStatus) => { + const data = { + "org_id": selectedOrganization.id, + "creator_config": joinStatus, + }; + + const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + toast("Failed updating org: ", responseJson.reason); + } else { + if (joinStatus == "join") { + setPublishingInfo("Your organization is now part of the Creator Incentive Program. You can now create and publish content to your organization's page. You can also create a creator account to manage your organization's content.") + } else { + setPublishingInfo("Your organization is no longer part of the Creator Incentive Program. You can still create a creator account to manage your organization's content.") + } + handleGetOrg(selectedOrganization.id); + } + }) + ) + .catch((error) => { + toast("Err: " + error.toString()); + }); + }; + + // Should enable / disable org branding + const handleChangePublishing = () => { + console.log("Handle change publishing"); + + if (selectedOrganization.creator_id == "") { + handleEditOrg("join") + } else { + handleEditOrg("leave") + } + } const isOrganizationReady = () => { + console.log("Is organization ready?") + // A simple checklist to ensure the button shows up properly if (selectedOrganization.name === selectedOrganization.org) { + const comment = "Change the name of your organization" + if (!publishRequirements.includes(comment)) { + setPublishRequirements([...publishRequirements, comment]) + } + + return false; + } + + // Check if it's a suborg + if (selectedOrganization.creator_org !== "") { + const comment = "Child orgs can't become creators" + if (!publishRequirements.includes(comment)) { + setPublishRequirements([...publishRequirements, comment]) + } return false; } if (selectedOrganization.large_image === "" || selectedOrganization.large_image === theme.palette.defaultImage) { + const comment = "Add a logo for your organization" + if (!publishRequirements.includes(comment)) { + setPublishRequirements([...publishRequirements, comment]) + } return false; } @@ -38,40 +104,58 @@ const Branding = (props) => { return (
- - Branding - +

+ Branding +

You can customize your organization's branding by uploading a logo, changing the color scheme and a lot more.

- Creator Network + Creator Incentive Program

-
+
- By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be turned into a creator account. Support: support@shuffler.io - + By changing publishing settings, you agree to our Terms of Service, and acknowledge that your organization's non-sensitive data will be added as a creator account. None of your existing workflows, apps, or other stored data will be published. Any admin in your organization can manage the creator configuration. Becoming a creator organization is reversible.
Support: support@shuffler.io + {selectedOrganization.creator_id == "" ? + +   + + : + + + Modify your creator organization + + } + - + {publishingInfo} + + {publishRequirements.map((item) => { + return ( +
+ Required: {item} +
+ ) + })} +
diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 8ee71450..5f66af70 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -823,7 +823,6 @@ const { globalUrl, setNotifications, notifications, isLoggedIn, removeCookie, ho color: "white", height: 45, width: 85, - zIndex: 14999, }} value={userdata.active_org.id} diff --git a/frontend/src/components/Priority.jsx b/frontend/src/components/Priority.jsx index 046c7fd9..4559246a 100644 --- a/frontend/src/components/Priority.jsx +++ b/frontend/src/components/Priority.jsx @@ -70,7 +70,7 @@ const Priority = (props) => { return ( -
+
{priority.type === "usecase" || priority.type == "apps" ? : null} diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index 3e54131a..c8269a52 100755 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -749,7 +749,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user image: image, defaults: defaults, sso_config: sso_config, - lead_info: lead_info, + lead_info: lead_info, }; const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; @@ -2371,7 +2371,7 @@ Let me know if you're interested, or set up a call here: https://drift.me/${user */} {userdata.support === true ? - + {/**/}
); @@ -5360,6 +5447,167 @@ const AppCreator = (defaultprops) => { ) : null; + const validateRemote = () => { + setValidation(true); + + fetch(globalUrl + "/api/v1/get_openapi_uri", { + method: "POST", + headers: { + Accept: "application/json", + }, + body: JSON.stringify(openApi), + credentials: "include", + }) + .then((response) => { + setValidation(false); + if (response.status !== 200) { + return response.json(); + } + + return response.text(); + }) + .then((responseJson) => { + if (typeof responseJson !== "string" && !responseJson.success) { + console.log(responseJson.reason); + if (responseJson.reason !== undefined) { + setOpenApiError(responseJson.reason); + } else { + setOpenApiError("Undefined issue with OpenAPI validation"); + } + return; + } + + console.log("Validating response!"); + validateOpenApi(responseJson); + }) + .catch((error) => { + toast(error.toString()); + setOpenApiError(error.toString()); + }); + } + + const circularLoader = validation ? ( + + ) : null; + + const newApimodalView = openApiModal ? + { + setOpenApiModal(false) + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + + +
+ Merge with another OpenAPI document. You will get to choose Actions before they are merged. +
+
+ + Paste in the URI for the OpenAPI + 0} + color="primary" + onClick={() => { + setOpenApiError(""); + validateRemote(); + }} + > + Validate + + ), + }} + onChange={(e) => { + setOpenApi(e.target.value); + }} + helperText={ + + Must point to a version 2 or 3 OpenAPI specification. + + } + placeholder="OpenAPI URI" + fullWidth + /> + {/* +
+ Example: +
+ https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v2.0/json/uber.json + */} +

Or upload a YAML/JSON specification

+ + + {errorText} + + + {circularLoader} + + + + +
+ : null + // Random names for type & autoComplete. Didn't research :^) const landingpageDataBrowser = (
@@ -5391,9 +5639,30 @@ const AppCreator = (defaultprops) => { onChange={editHeaderImage} /> -

- General information -

+
+
+

+ General information +

+
+
+ + { + setOpenApiModal(true) + }} + > + { + setOpenApiModal(true) + }} + /> + + +
+
{ isLoaded && isAppLoaded ? (
{landingpageDataBrowser}
+ {newApimodalView}
) : (
diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 0e3868b0..218f9e39 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -1354,7 +1354,7 @@ const Apps = (props) => { border: hover ? "1px solid #f85a3e" : "1px solid rgba(255,255,255,0.3)", cursor: hover ? "pointer" : "default", textAlign: "center", - height: 150, + height: 125, }} > {icon} @@ -2556,6 +2556,7 @@ const Apps = (props) => { const circularLoader = validation ? ( ) : null; + const appsModalLoad = loadAppsModalOpen ? ( {

{passwordFormMessage}

-

Platform Earnings

+

Creator Incentive Program

-
- {isCloud ? - - - By connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: support@shuffler.io - - - - : null} -
+
+ {isCloud ? + + + By joining the Creator Incentive Program and connecting your Github account, you agree to our Terms of Service, and acknowledge that your non-sensitive data will be turned into a creator account. This enables you to earn a passive income from Shuffle. This IS reversible. Support: support@shuffler.io + + + + : null} +
{userdata.eth_info !== undefined && diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 73a4e68c..476dd9fc 100755 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -638,7 +638,7 @@ const Workflows = (props) => { window.location.host === "shuffler.io"; const findWorkflow = (filters) => { - console.log("Using filters: ", filters) + console.log("Using filters: ", filters) if (filters.length === 0) { setFilteredWorkflows(workflows); handleKeysetting(allUsecases, workflows) @@ -656,6 +656,12 @@ const Workflows = (props) => { ); } + if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.length > 0) { + // Make them all lowercase + curWorkflow.tags = curWorkflow.tags.map((tag) => tag.toLowerCase()) + } + + if (found.every((v) => v !== true)) { found = filters.map((filter) => { if (filter === undefined || filter === null) { @@ -666,7 +672,7 @@ const Workflows = (props) => { if (curWorkflow.name.toLowerCase().includes(filter.toLowerCase())) { return true; - } else if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.includes(filter)) { + } else if (curWorkflow.tags !== undefined && curWorkflow.tags !== null && curWorkflow.tags.includes(filter.toLowerCase())) { return true; } else if (curWorkflow.owner === filter) { return true; @@ -674,17 +680,17 @@ const Workflows = (props) => { return true; } else if (curWorkflow.usecase_ids !== undefined && curWorkflow.usecase_ids !== null && curWorkflow.usecase_ids.length > 0) { // Check if the usecase is the right category - for (var key in usecases) { - if (usecases[key].name.toLowerCase() !== newfilter) { - continue - } + for (var key in usecases) { + if (usecases[key].name.toLowerCase() !== newfilter) { + continue + } - for (var subkey in usecases[key].list) { - if (curWorkflow.usecase_ids.includes(usecases[key].list[subkey].name)) { - return true - } - } + for (var subkey in usecases[key].list) { + if (curWorkflow.usecase_ids.includes(usecases[key].list[subkey].name)) { + return true } + } + } } else if ( curWorkflow.actions !== null && curWorkflow.actions !== undefined diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 21e42e9e..2088ad28 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -224,7 +224,7 @@ func deployServiceWorkers(image string) { } if err != nil { - log.Printf("[ERROR] Failed to convert the default MTU to int: %s. Using 1500 instead", err) + log.Printf("[DEBUG] Failed to convert the default MTU to int: %s. Using 1500 instead", err) mtu = 1500 } @@ -1601,6 +1601,6 @@ func sendWorkerRequest(workflowExecution shuffle.ExecutionRequest) error { _ = body - log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s. DEBUGGING: docker service logs shuffle-workers 2&>1 | grep %s", workflowExecution.ExecutionId, streamUrl, workflowExecution.ExecutionId) + log.Printf("[DEBUG] Ran worker from request with execution ID: %s. Worker URL: %s. DEBUGGING: docker service logs shuffle-workers 2>&1 | grep %s", workflowExecution.ExecutionId, streamUrl, workflowExecution.ExecutionId) return nil }