From 400a3653acbda5cda6bae69356dee68aa517cf47 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 19 May 2025 21:42:50 +0200 Subject: [PATCH 01/14] Some CDN changes wut --- frontend/src/components/UserManagmentTab.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/UserManagmentTab.jsx b/frontend/src/components/UserManagmentTab.jsx index ec7036cc..8efd3e30 100644 --- a/frontend/src/components/UserManagmentTab.jsx +++ b/frontend/src/components/UserManagmentTab.jsx @@ -1359,7 +1359,7 @@ const UserManagmentTab = memo((props) => { }} > - {["Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => ( + {["Region", "Username", /*"API Key",*/ "Role", /*"Active",*/ "Type", "MFA", ...(selectedOrganization?.child_orgs?.length > 0 ? ["Suborgs"]: []), "Actions", "Last Login"].map((header, index) => ( { return ( + )} + style={{ display: 'table-cell', verticalAlign: 'middle', textAlign: 'center' }} + /> From 8814463a27e3def83159c4b9938191c0ceee9d04 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 19 May 2025 21:43:35 +0200 Subject: [PATCH 02/14] Sync over from shaffuru --- frontend/src/components/LicencePopup.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 6 ++++-- frontend/src/views/Apps2.jsx | 12 ++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx index eb7d719f..0e580cbb 100644 --- a/frontend/src/components/LicencePopup.jsx +++ b/frontend/src/components/LicencePopup.jsx @@ -1242,7 +1242,7 @@ const LicencePopup = (props) => {
- {selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0 ? + {(selectedOrganization.subscriptions === undefined || selectedOrganization.subscriptions === null || selectedOrganization.subscriptions.length === 0) && isCloud ? { Name { Delay { style={{ height: 45, minWidth: 45, - backgroundColor: "#2F2F2F", + backgroundColor: theme.palette.platformColor, borderRadius: 4, padding: "8px 16px", }} @@ -1950,9 +1950,9 @@ const Apps2 = (props) => { }} > {isLoading ? ( - + ) : ( - + )} @@ -1980,7 +1980,7 @@ const Apps2 = (props) => { style={{ height: 45, minWidth: 45, - backgroundColor: "#2F2F2F", + backgroundColor: theme.palette.platformColor, borderRadius: 4, padding: "8px 16px", }} @@ -1992,9 +1992,9 @@ const Apps2 = (props) => { }} > {isLoading ? ( - + ) : ( - + )} From f3678fcfdc1f82b2de57bfa57746a92f39c516f7 Mon Sep 17 00:00:00 2001 From: yashsinghcodes Date: Tue, 20 May 2025 01:23:25 +0530 Subject: [PATCH 03/14] fix for defaultNetwork failing service deployments --- functions/onprem/orborus/orborus.go | 58 ++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 3697735e..4b3076b6 100755 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -610,13 +610,31 @@ func deployServiceWorkers(image string) { if defaultNetworkAttach == true || strings.ToLower(os.Getenv("SHUFFLE_DEFAULT_NETWORK_ATTACH")) == "true" { targetName := "shuffle_shuffle" - log.Printf("[DEBUG] Adding network attach for network %s to worker in swarm", targetName) - serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{ - Target: targetName, - }) + isAttachable := false + networks, err := dockercli.NetworkList(ctx, network.ListOptions{}) + if err == nil { + for _, net := range networks { + if net.Name == targetName { + if net.Scope == "swarm" { + log.Printf("[DEBUG] Found swarm-scoped network: %s", targetName) + isAttachable = true + } else { + log.Printf("[WARNING] Network %s exist but is not swarm scoped (scope=%s)", targetName, net.Scope) + } + break + } + } + } - // FIXM: Remove this if deployment fails? - serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=%s", targetName)) + if isAttachable { + log.Printf("[DEBUG] Adding network attach for network %s to worker in swarm", targetName) + serviceSpec.Networks = append(serviceSpec.Networks, swarm.NetworkAttachmentConfig{ + Target: targetName, + }) + + // FIXM: Remove this if deployment fails? + serviceSpec.TaskTemplate.ContainerSpec.Env = append(serviceSpec.TaskTemplate.ContainerSpec.Env, fmt.Sprintf("SHUFFLE_SWARM_OTHER_NETWORK=%s", targetName)) + } } if dockerApiVersion != "" { @@ -709,6 +727,34 @@ func deployServiceWorkers(image string) { } else { if !strings.Contains(fmt.Sprintf("%s", err), "Already Exists") && !strings.Contains(fmt.Sprintf("%s", err), "is already in use by service") { log.Printf("[ERROR] Failed making service: %s", err) + if strings.Contains(fmt.Sprintf("%s", err), "networks scoped to the swarm can be used") { + log.Printf("[WARNING] Swarm network attachment failed, retrying without shuffle_shuffle") + + var updatedNetworks []swarm.NetworkAttachmentConfig + for _, net := range serviceSpec.Networks { + if net.Target != "shuffle_shuffle" { + updatedNetworks = append(updatedNetworks, net) + } + } + serviceSpec.Networks = updatedNetworks + + var updatedEnv []string + for _, env := range serviceSpec.TaskTemplate.ContainerSpec.Env { + if !strings.HasPrefix(env, "SHUFFLE_SWARM_OTHER_NETWORK=") { + updatedEnv = append(updatedEnv, env) + } + } + serviceSpec.TaskTemplate.ContainerSpec.Env = updatedEnv + serviceOptions := types.ServiceCreateOptions{} + _, err = dockercli.ServiceCreate( + ctx, + serviceSpec, + serviceOptions, + ) + if err != nil { + log.Printf("[ERROR] Failed to deploy service even without shuffle_shuffle network: %s", err) + } + } } else { log.Printf("[WARNING] Failed deploying workers: %s", err) if len(serviceSpec.Networks) > 1 { From c2c4a9c3235108f707c0f78fc04bf58a3c6387c1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 20 May 2025 01:07:16 +0200 Subject: [PATCH 04/14] Minor fixes for testing Singul locally --- backend/go-app/go.mod | 4 ++-- backend/go-app/walkoff.go | 19 ++++++++++------- frontend/src/views/AngularWorkflow.jsx | 29 ++++++++++++++++++-------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index c630b9ff..9accdf46 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.8 //replace github.com/frikky/schemaless => ../../../schemaless //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi -//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared require ( cloud.google.com/go/datastore v1.20.0 @@ -22,7 +22,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.8.58 + github.com/shuffle/shuffle-shared v0.8.60 golang.org/x/crypto v0.37.0 google.golang.org/api v0.228.0 google.golang.org/grpc v1.71.1 diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 8357de3e..4329e4af 100755 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1905,8 +1905,8 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { err = shuffle.SetSchedule(ctx, newSchedule) if err != nil { - log.Printf("Failed setting cloud schedule: %s", err) - resp.WriteHeader(401) + log.Printf("[ERROR] Failed setting cloud schedule: %s", err) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } @@ -1941,17 +1941,22 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { // FIXME - real error message lol if err != nil { - log.Printf("Failed creating schedule: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. Try cron */15 * * * *"}`))) + log.Printf("[ERROR] Failed creating schedule: %s", err) + + resp.WriteHeader(400) + if schedule.Environment == "cloud" { + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. For cloud schedules, try cron */15 * * * *"}`))) + } else { + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Invalid argument. For onprem schedules, try 60 for 60 seconds"}`))) + } return } //workflow.Schedules = append(workflow.Schedules, schedule) err = shuffle.SetWorkflow(ctx, *workflow, workflow.ID) if err != nil { - log.Printf("Failed setting workflow for schedule: %s", err) - resp.WriteHeader(401) + log.Printf("[ERROR] Failed setting workflow for schedule: %s", err) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 37e2e056..db5111bd 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -790,7 +790,7 @@ const AngularWorkflow = (defaultprops) => { }, { "name": "fields", - "value": "", + "value": '{\n "ticket_id": "123456",\n "comment": "This is a comment"\n}', "required": false, "multiline": true, }, @@ -7000,7 +7000,11 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (responseJson.success === false) { - toast("Failed to auto-activate the app. Go to /apps and activate it.") + if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) { + toast.error("Failed to auto-activate the app: " + responseJson.reason) + } else { + toast.error("Failed to auto-activate the app. Go to /apps and activate it.") + } } else { if (refresh === true) { setHighlightedApp(appid) @@ -10312,7 +10316,7 @@ const AngularWorkflow = (defaultprops) => { } } - toast("Creating schedule") + toast.info("Creating schedule") var data = { name: trigger.name, frequency: workflow.triggers[triggerindex].parameters[0].value, @@ -10361,9 +10365,9 @@ const AngularWorkflow = (defaultprops) => { }) .then((responseJson) => { if (!responseJson.success) { - toast("Failed to set schedule: " + responseJson.reason); + toast.error("Failed to set schedule: " + responseJson.reason); } else { - toast("Successfully created schedule"); + toast.success("Successfully created schedule"); workflow.triggers[triggerindex].status = "running"; trigger.status = "running"; setSelectedTrigger(trigger); @@ -11876,8 +11880,9 @@ const AngularWorkflow = (defaultprops) => { var type = "app" const baseImage = + const width = 230 return ( -
+
{hits.length === 0 ? @@ -11971,7 +11976,7 @@ const AngularWorkflow = (defaultprops) => { }} defaultPosition={{ x: 0, y: 0 }} > -
{ +
{ clickedApp(hit) }}> @@ -12125,7 +12130,12 @@ const AngularWorkflow = (defaultprops) => { } if ((app.id === "integration" || app.id === "shuffle_agent") && userdata.support !== true) { - return null + console.log("APPID: ", app.id, isCloud) + if (isCloud === false && app.id === "integration") { + } else { + console.log("RETURNING", app.id) + return null + } } if (viewedApps.includes(app.id)) { @@ -12190,7 +12200,7 @@ const AngularWorkflow = (defaultprops) => {
) : apps.length > 0 ? (
{ console.log("Should load in extra apps?") }} @@ -12198,6 +12208,7 @@ const AngularWorkflow = (defaultprops) => { Couldn't find the apps you were looking for? Searching unactivated apps. Click one of these apps to Activate it for your organisation. + { console.log("CLICKED") }}> From 065f6f933205fcbc663ebb613e400119c49cea0a Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 20 May 2025 18:48:52 +0200 Subject: [PATCH 05/14] Minor fixes for frontend on shuffle with no internet --- frontend/src/App.jsx | 2 +- frontend/src/views/AdminSetup.jsx | 12 +++++++++--- frontend/src/views/LoginPage.jsx | 7 ++++++- frontend/src/views/LoginPageOld.jsx | 8 +++++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index fa3ac7ea..caa9e1cb 100755 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -463,7 +463,7 @@ const App = (message, props) => { { .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); + setLoginInfo(responseJson["reason"]) + + if (responseJson?.reason?.toLowerCase().includes("connection refused")) { + navigate("/loginsetup") + } + } else { if (responseJson.reason === "redirect") { setTimeout(() => { - window.location.pathname = "/login"; + window.location.pathname = "/login" }, 2500) } + } }) ) @@ -111,7 +117,7 @@ const AdminAccount = (props) => { if (responseJson["success"] === false) { setLoginInfo(responseJson["reason"]); } else { - setLoginInfo("Successful register :)"); + setLoginInfo("Successful register! Redirecting in a moment..."); setTimeout(() => { window.location.pathname = "/login"; diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index ccba780d..26bf6782 100755 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -361,7 +361,7 @@ const LoginPage = props => { console.log("Should login instead of register!") setRegister(!register) } else { - console.log("Path: " + path, "Register: " + register) + //console.log("Path: " + path, "Register: " + register) } } @@ -468,6 +468,11 @@ const LoginPage = props => { response.json().then((responseJson) => { if (responseJson["success"] === false) { setLoginInfo(responseJson["reason"]); + + if (responseJson?.reason?.toLowerCase().includes("connection refused")) { + navigate("/loginsetup") + } + } else { if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) { setSSOUrl(responseJson.sso_url); diff --git a/frontend/src/views/LoginPageOld.jsx b/frontend/src/views/LoginPageOld.jsx index 541cd903..aaee73d6 100755 --- a/frontend/src/views/LoginPageOld.jsx +++ b/frontend/src/views/LoginPageOld.jsx @@ -85,7 +85,13 @@ const LoginDialog = (props) => { .then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - setLoginInfo(responseJson["reason"]); + setLoginInfo(responseJson["reason"]) + + if (responseJson?.reason?.toLowerCase().includes("connection refused")) { + setLoginViewLoading(true) + start() + } + } else { if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) { From c0aaebaff603a65acb5b0d676892649f94d13a3e Mon Sep 17 00:00:00 2001 From: Frikky Date: Fri, 23 May 2025 12:10:38 +0200 Subject: [PATCH 06/14] Synced over bugfixes and theme control --- frontend/src/components/Branding.jsx | 7 +- frontend/src/components/LeftSideBar.jsx | 46 +- frontend/src/components/Priorities.jsx | 2441 ++++++++++++----- .../src/components/ShuffleCodeEditor1.jsx | 10 +- frontend/src/views/AngularWorkflow.jsx | 18 +- frontend/src/views/LoginPage.jsx | 61 +- frontend/src/views/SettingsPage.jsx | 7 +- frontend/src/views/Workflows2.jsx | 6 +- 8 files changed, 1898 insertions(+), 698 deletions(-) diff --git a/frontend/src/components/Branding.jsx b/frontend/src/components/Branding.jsx index 649c509a..40a76846 100644 --- a/frontend/src/components/Branding.jsx +++ b/frontend/src/components/Branding.jsx @@ -39,7 +39,7 @@ const Branding = (props) => { const theme = getTheme(themeMode, brandColor) const [selectedBrandColor, setSelectedBrandColor] = useState(theme?.palette?.main || "#FF8544") const [selectedBrandName, setSelectedBrandName] = useState(selectedOrganization?.branding?.brand_name || "") - const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const [isLoading,setIsLoading] = useState(false); const handleEditOrg = (joinStatus) => { @@ -391,7 +391,7 @@ const Branding = (props) => {
- { integrationPartner ? ( + {integrationPartner ? ( <> @@ -414,9 +414,6 @@ const Branding = (props) => { if (changingTheme === true) { return } - - - toast.info("Changing theme to " + newTheme + ". Please wait a moment.") setChangingTheme(true) handleEditOrg(newTheme); }} diff --git a/frontend/src/components/LeftSideBar.jsx b/frontend/src/components/LeftSideBar.jsx index 2d9bad44..69a9b29e 100644 --- a/frontend/src/components/LeftSideBar.jsx +++ b/frontend/src/components/LeftSideBar.jsx @@ -403,7 +403,6 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { const handleChangeTheme = (newTheme) => { - toast.info("Changing theme to " + newTheme + " - please wait!"); const data = { "org_id": userdata?.active_org?.id, @@ -449,6 +448,38 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { }); }; + + const handleUpdateTheme = (newTheme) => { + + const data = { + "user_id": userdata?.id, + "theme": newTheme, + } + + const url = globalUrl + `/api/v1/users/updateuser`; + fetch(url, { + mode: "cors", + method: "PUT", + 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 theme: ", responseJson.reason); + } else { + handleThemeChange(newTheme); + setCurrentSelectedTheme(newTheme); + } + }) + ).catch((error) => { + console.log("Error changing theme: ", error); + }); + }; const removeCookie = (name, path = "/") => { @@ -487,6 +518,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { console.error("Logout error:", error); }); }; + console.log("userdata: ", userdata); const avatarMenu = ( { }, }} > - {userdata && ( + {userdata && (userdata?.org_status?.includes("integration_partner") && userdata?.org_status?.includes("sub_org")) ? null : ( <> { if (newTheme === currentSelectedTheme) { return; } - handleChangeTheme(newTheme); + if (userdata?.org_status?.includes("integration_partner")){ + handleChangeTheme(newTheme); + }else { + handleUpdateTheme(newTheme); + } }} aria-label="theme" style={{display: 'flex', justifyContent: "center", marginBottom: 10, }} @@ -1648,7 +1684,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { + + + + ) : null + + const modalView = notificationWorkflowModal ? ( + { + setNotificationWorkflowModal(false); + }} + > + + +
+ {`Configure ${selectedAppDetails.name} workflow`} +
+
+ + + {console.log("len Selected app details: ", selectedAppDetails)} + {(selectedAppDetails.authentication_data || (selectedAppDetails.auth_config && selectedAppDetails.auth_config.required == false) || (selectedAppDetails.authentication && selectedAppDetails.authentication.required == false)) ? + <> + + {true || (selectedAppDetails.auth_config && selectedAppDetails.auth_config.required == false || (selectedAppDetails.authentication && selectedAppDetails.authentication.required == false)) ? "No authentication required" : + <> + + Pick an authentication method from the list + + + Available authentications + + } + + + + Provide additional required details: + + { + setTextFieldOneValue(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} /> + { + setTextFieldValue(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} /> + + : + <> + 0) ? false : true} + // // setAuthenticationModalOpen={false} + selectedApp={{ ...selectedAppDetails, authentication: selectedAppDetails.auth_config }} + // getAppAuthentication={selectedAppDetails.name} + /> + + } + + + + + +
+
+ ) : null + + + const checkIfAlreadyGenerated = async (appList, workflows) => { // fixxxxxxxxxxxxxxxxxxxxx + + var workflowName = workflows.find(workflow => workflow.id === notificationWorkflow) + if (workflowName) { + workflowName = workflowName.name + } + else { + console.log("no workflow set") + return + } + if (workflowName) { + const parts = workflowName.split(' '); + console.log("parts", parts) + if (parts[0].toString() === "[GENERATED]" && parts.length > 1) { + console.log("parts1", parts[1]) + if ((appList.includes(parts[1]))) { + console.log("workflow already generated") + setGeneatedWorkflow({ "app_name": parts[1] }) + } + } + } + else { + return + } + } + + const renderChips = useCallback(() => { + const appList = Object.values(notificationAppsDetails); + return ( + + + {appList.map((app) => ( + { + console.log(`Clicked ${app.name}`) + console.log("app: ", app) + setSelectedAppDetails(app) + if (app.authentication_data && app.authentication_data.length > 0) { //fixxxxxxxx + console.log("authdata: ", app.authentication_data[0]) + setSelectedAuth(app.authentication_data[app.authentication_data.length - 1].id) + } + setNotificationWorkflowModal(true) + // getAppAuth(app.name) + console.log("selectedAppDEtails", selectedAppDetails) + }} + avatar={{app.name}} + /> + ))} + + + + + + Want access to more templates? + + Set up app authentication + + to unlock additional workflow options. + + + + ); + }, [notificationAppsDetails]) + useEffect(() => { getFramework() @@ -83,7 +1224,7 @@ const Priorities = memo((props) => { setSelectedExecutionId(execution_id) //toast.info("Execution-related notifications are highlighted.") - } + } if (workflow !== null) { setSelectedWorkflow(workflow) @@ -97,7 +1238,7 @@ const Priorities = memo((props) => { return } - if(workflows?.length === 0) { + if (workflows?.length === 0) { getAvailableWorkflows() } @@ -107,7 +1248,7 @@ const Priorities = memo((props) => { }, [selectedOrganization]) if (userdata === undefined || userdata === null) { - return + return } const getFramework = () => { @@ -119,67 +1260,67 @@ const Priorities = memo((props) => { }, credentials: "include", }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for framework!"); - } - - return response.json(); - }) - .then((responseJson) => { - if (responseJson.success === false) { - setAppFramework({}) - if (responseJson.reason !== undefined) { - //toast("Failed loading: " + responseJson.reason) - } else { - //toast("Failed to load framework for your org.") + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); } - } else { - setAppFramework(responseJson) - } - }) - .catch((error) => { - console.log("err in framework: ", error.toString()); - }) + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + setAppFramework({}) + if (responseJson.reason !== undefined) { + //toast("Failed loading: " + responseJson.reason) + } else { + //toast("Failed to load framework for your org.") + } + } else { + setAppFramework(responseJson) + } + }) + .catch((error) => { + console.log("err in framework: ", error.toString()); + }) } - const clearNotifications = () => { - // Don't really care about the logout + const clearNotifications = () => { + // Don't really care about the logout - toast("Clearing notifications") - fetch(`${globalUrl}/api/v1/notifications/clear`, { - credentials: "include", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } + toast("Clearing notifications") + fetch(`${globalUrl}/api/v1/notifications/clear`, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - // Reload the UI - const newNotifications = notifications.map((notification) => { - notification.read = true - return notification - }) + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success === true) { + // Reload the UI + const newNotifications = notifications.map((notification) => { + notification.read = true + return notification + }) - setNotifications(newNotifications) - setShowRead(true) - } else { - toast("Failed dismissing notifications. Please try again later."); - } - }) - .catch((error) => { - console.log("error in notification dismissal: ", error); - //removeCookie("session_token", {path: "/"}) - }); - }; + setNotifications(newNotifications) + setShowRead(true) + } else { + toast("Failed dismissing notifications. Please try again later."); + } + }) + .catch((error) => { + console.log("error in notification dismissal: ", error); + //removeCookie("session_token", {path: "/"}) + }); + }; const dismissNotification = (alert_id, disabled) => { var notificationurl = `${globalUrl}/api/v1/notifications/${alert_id}/markasread` @@ -189,82 +1330,82 @@ const Priorities = memo((props) => { notificationurl += "?disabled=false" } - fetch(notificationurl , { - credentials: "include", - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - .then(function (response) { - if (response.status !== 200) { - console.log("Error in response"); - } - - return response.json(); - }) - .then(function (responseJson) { - if (responseJson.success === true) { - // Mark current one as read - var newNotifications = notifications.map((notification) => { - if (notification.id === alert_id) { - notification.read = true + fetch(notificationurl, { + credentials: "include", + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); } - return notification + return response.json(); }) + .then(function (responseJson) { + if (responseJson.success === true) { + // Mark current one as read + var newNotifications = notifications.map((notification) => { + if (notification.id === alert_id) { + notification.read = true + } + + return notification + }) - if (disabled === true) { - toast("Notification disabled, and will not be shown again.") + if (disabled === true) { + toast("Notification disabled, and will not be shown again.") - newNotifications = newNotifications.map((notification) => { - if (notification.id === alert_id) { - notification.ignored = true + newNotifications = newNotifications.map((notification) => { + if (notification.id === alert_id) { + notification.ignored = true + } + + return notification + }) + + console.log("NEW NOTIFICATIONS: ", newNotifications); + } else if (disabled === false) { + toast("Notification re-enabled successfully") + + newNotifications = newNotifications.map((notification) => { + if (notification.id === alert_id) { + notification.ignored = false + } + + return notification + }) + + } else { + toast("Notification dismissed successfully") } - return notification - }) + //const newNotifications = notifications.filter( + // (data) => data.id !== alert_id + //) - console.log("NEW NOTIFICATIONS: ", newNotifications); - } else if (disabled === false) { - toast("Notification re-enabled successfully") + //console.log("NEW NOTIFICATIONS: ", newNotifications); - newNotifications = newNotifications.map((notification) => { - if (notification.id === alert_id) { - notification.ignored = false + if (setNotifications !== undefined && newNotifications !== undefined) { + setNotifications(newNotifications) } - - return notification - }) - - } else { - toast("Notification dismissed successfully") - } - - //const newNotifications = notifications.filter( - // (data) => data.id !== alert_id - //) - - //console.log("NEW NOTIFICATIONS: ", newNotifications); - - if (setNotifications !== undefined && newNotifications !== undefined) { - setNotifications(newNotifications) - } - } else { - toast("Failed dismissing notification. Please try again later."); - } - }) - .catch((error) => { - console.log("error in notification dismissal: ", error); - //removeCookie("session_token", {path: "/"}) - }) + } else { + toast("Failed dismissing notification. Please try again later."); + } + }) + .catch((error) => { + console.log("error in notification dismissal: ", error); + //removeCookie("session_token", {path: "/"}) + }) } - - const notificationWidth = "100%" + + const notificationWidth = "100%" const imagesize = 22 - const boxColor = "#86c142" + const boxColor = "#86c142" const getAvailableWorkflows = () => { @@ -354,134 +1495,168 @@ const Priorities = memo((props) => { } return ( -
-
-
- - Notification Workflow - - - The notification workflow triggers when an error occurs in one of your workflows. Each individual one will only start a workflow once every 2 minutes. You can point child org notifications into the parent org notification by choosing it in the list. - - -
+
+
+
+ + Notification Workflow + + + The notification workflow triggers when an error occurs in one of your workflows. Each individual one will only start a workflow once every 2 minutes. You can point child org notifications into the parent org notification by choosing it in the list. + - {workflows !== undefined && workflows !== null && workflows.length > 0 ? - { - setOpenNotification(true); - }} - onClose={() => { - setOpenNotification(false); - }} - freeSolo - //autoSelect - value={workflows?.find(w => w.id === notificationWorkflow) || null} - classes={{ inputRoot: classes.inputRoot }} - ListboxProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: theme.palette.text.primary, - borderRadius: theme.palette.borderRadius, - }, - }} - getOptionLabel={(option) => { - if ( - option === undefined || - option === null || - option.name === undefined || - option.name === null - ) { - return "No Workflow Selected"; - } + {modalView} + {/*{testWorkflowModal} */} +
+ {renderChips()} +
- const newname = ( - option.name.charAt(0).toUpperCase() + option.name.substring(1) - ).replaceAll("_", " "); - return newname; - }} - options={workflows} - fullWidth - style={{ - backgroundColor: theme.palette.textFieldStyle.backgroundColor, - borderRadius: theme.palette.textFieldStyle.borderRadius, - color: theme.palette.textFieldStyle.color, - height: 35, - marginBottom: 40, - }} - onChange={(event, newValue) => { - console.log("Found value: ", newValue) +
- var parsedinput = { target: { value: newValue } } - - // For variables - if (typeof newValue === 'string' && newValue.startsWith("$")) { - parsedinput = { - target: { - value: { - "name": newValue, - "id": newValue, - "actions": [], - "triggers": [], - } - } - } - } - - handleWorkflowSelectionUpdate(parsedinput) - }} - renderOption={(props, data, state) => { - if (data.id === workflow.id) { - data = workflow; - } - - return ( - - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.name} - : null} - - Choose {data.name} - - - } placement="bottom"> - 0 ? + { + setOpenNotification(true); + }} + onClose={() => { + setOpenNotification(false); + }} + freeSolo + //autoSelect + value={workflows?.find(w => w.id === notificationWorkflow) || null} + classes={{ inputRoot: classes.inputRoot }} + ListboxProps={{ + style: { backgroundColor: theme.palette.surfaceColor, - color: data.id === workflow.id ? "red" : theme.palette.text.primary, - borderBottom: data.id === "parent" ? "2px solid rgba(255,255,255,0.5)" : null - }} - value={data} - onClick={(e) => { - props.onMouseDown?.(null); - var parsedinput = { target: { value: data } } - handleWorkflowSelectionUpdate(parsedinput) - }} - > - {data.name} - - - ) - }} - renderInput={(params) => { - return ( - { + if ( + option === undefined || + option === null || + option.name === undefined || + option.name === null + ) { + return "No Workflow Selected"; + } + + const newname = ( + option.name.charAt(0).toUpperCase() + option.name.substring(1) + ).replaceAll("_", " "); + return newname; + }} + options={workflows} + fullWidth style={{ backgroundColor: theme.palette.textFieldStyle.backgroundColor, - color: theme.palette.textFieldStyle.color, borderRadius: theme.palette.textFieldStyle.borderRadius, + color: theme.palette.textFieldStyle.color, height: 35, - fontSize: 16, - marginTop: "16px" + marginBottom: 40, }} + onChange={(event, newValue) => { + console.log("Found value: ", newValue) + + var parsedinput = { target: { value: newValue } } + + // For variables + if (typeof newValue === 'string' && newValue.startsWith("$")) { + parsedinput = { + target: { + value: { + "name": newValue, + "id": newValue, + "actions": [], + "triggers": [], + } + } + } + } + + handleWorkflowSelectionUpdate(parsedinput) + }} + renderOption={(props, data, state) => { + if (data.id === workflow.id) { + data = workflow; + } + + return ( + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Choose {data.name} + + + } placement="bottom"> + { + props.onMouseDown?.(null); + var parsedinput = { target: { value: data } } + handleWorkflowSelectionUpdate(parsedinput) + }} + > + {data.name} + + + ) + }} + renderInput={(params) => { + return ( + + ); + }} + /> + : + { borderRadius: 4, }, inputProps: { - ...params.inputProps, style: { height: "100%", boxSizing: "border-box", @@ -499,198 +1673,171 @@ const Priorities = memo((props) => { } }} - // label="Find a notification workflow" - variant="outlined" - placeholder="Select a notification workflow" + style={{ + backgroundColor: theme.palette.textFieldStyle.backgroundColor, + color: theme.palette.textFieldStyle.color, + borderRadius: 4, + height: 35, + fontSize: 16, + marginBottom: 30 + }} + fullWidth={true} + type="name" + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="ID of the workflow to receive notifications" + value={notificationWorkflow} + onChange={(e) => { + setNotificationWorkflow(e.target.value); + }} /> - ); - }} - /> - : - { - setNotificationWorkflow(e.target.value); - }} - /> - } - {/*
+ {/*
{orgSaveButton}
*/} -
+
- {notificationWorkflow === undefined || notificationWorkflow === null || notificationWorkflow.length === 0 ? null : -
- + { + if (notificationWorkflow === "parent") { + toast.error("Can't open parent org's notification workflow from here.") + return + } + + window.open(`/workflows/${notificationWorkflow}?view=executions`, "_blank") + }} + > + + +
} - fetch(`${globalUrl}/api/v1/workflows/${notificationWorkflow}/execute`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - }, - credentials: "include", - body: JSON.stringify({ - "title": "Test Notification", - "description": "This is a test notification to check if the notification workflow is working correctly.", - "org_id": selectedOrganization.id, - "id": uuidv4(), - "reference_url": "/admin?type=test&admin_tab=notifications", - "created_at": Math.floor(new Date().getTime() / 1000), - "updated_at": Math.floor(new Date().getTime() / 1000), + Notifications ({ + notifications?.filter((notification) => showRead === true || notification.read === false).length + }) + + + Notifications help you find potential problems with your workflows and apps.  + + Learn more + + +
+
+ { + setShowRead(!showRead); + }} + />  Show read + {notifications !== undefined && notifications !== null && notifications.length > 1 ? ( + + ) : null} +
+ + + + {clickedFromOrgTab ? null : } + + Suggestions + + Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company.
These range from simple configurations in Shuffle to Usecases you may have missed.  + + Learn more + +
+
+ { + setShowDismissed(!showDismissed); + }} + />  Show dismissed + {userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? + + No Suggestions found + + : + userdata.priorities.map((priority, index) => { + if (showDismissed === false && priority.active === false) { + return null + } + + return ( + + ) }) - }) - .then((response) => { - if (response.status === 200) { - toast.success("Test notification sent successfully.") - } else { - toast.error("Failed to send test notification. Please contact support if this persists") - } - }).catch((error) => { - toast.error("Failed to send test notification (2). Please contact support if this persists") - }) - }}> - Send test notification - - { - if (notificationWorkflow === "parent") { - toast.error("Can't open parent org's notification workflow from here.") - return - } - - window.open(`/workflows/${notificationWorkflow}?view=executions`, "_blank") - }} - > - - -
- } - - Notifications ({ - notifications?.filter((notification) => showRead === true || notification.read === false).length - }) - - - Notifications help you find potential problems with your workflows and apps.  - - Learn more - - -
-
- { - setShowRead(!showRead); - }} - />  Show read - {notifications !== undefined && notifications !== null && notifications.length > 1 ? ( - - ) : null} -
- - - - {clickedFromOrgTab? null : } - - Suggestions - - Suggestions are tasks identified by Shuffle to help you discover ways to protect your and customers' company.
These range from simple configurations in Shuffle to Usecases you may have missed.  - - Learn more - -
-
- { - setShowDismissed(!showDismissed); - }} - />  Show dismissed - {userdata.priorities === null || userdata.priorities === undefined || userdata.priorities.length === 0 ? - - No Suggestions found - - : - userdata.priorities.map((priority, index) => { - if (showDismissed === false && priority.active === false) { - return null } - - return ( - - ) - }) - } -
-
+
+
) }) @@ -699,15 +1846,15 @@ export default Priorities; const NotificationItem = memo((props) => { - const {data, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification} = props + const { data, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification } = props var image = ""; var orgName = ""; var orgId = ""; const { themeMode, brandColor } = useContext(Context); - const theme = getTheme(themeMode, brandColor); - - var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow) + const theme = getTheme(themeMode, brandColor); + + var highlighted = selectedExecutionId === "" && selectedWorkflow === "" ? false : data.reference_url === undefined || data.reference_url === null || data.reference_url.length === 0 ? false : data.reference_url.includes(selectedExecutionId) || data.reference_url.includes(selectedWorkflow) if (!highlighted && highlightKMS) { if (data.title !== undefined && data.title !== null && data.title.toLowerCase().includes("kms")) { @@ -719,45 +1866,45 @@ const NotificationItem = memo((props) => { } if (userdata.orgs !== undefined) { - const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); - if (foundOrg !== undefined && foundOrg !== null) { - //position: "absolute", bottom: 5, right: -5, - const imageStyle = { - width: imagesize, - height: imagesize, - pointerEvents: "none", - marginLeft: - data.creator_org !== undefined && data.creator_org.length > 0 - ? 20 - : 0, - borderRadius: 10, - border: - foundOrg.id === userdata.active_org.id - ? `3px solid ${boxColor}` - : null, - cursor: "pointer", - marginRight: 10, - }; + const foundOrg = userdata.orgs.find((org) => org.id === data["org_id"]); + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = { + width: imagesize, + height: imagesize, + pointerEvents: "none", + marginLeft: + data.creator_org !== undefined && data.creator_org.length > 0 + ? 20 + : 0, + borderRadius: 10, + border: + foundOrg.id === userdata.active_org.id + ? `3px solid ${boxColor}` + : null, + cursor: "pointer", + marginRight: 10, + }; - image = - foundOrg.image === "" ? ( - {foundOrg.name} - ) : ( - {foundOrg.name} {}} - /> - ); + image = + foundOrg.image === "" ? ( + {foundOrg.name} + ) : ( + {foundOrg.name} { }} + /> + ); - orgName = foundOrg.name; - orgId = foundOrg.id; - } + orgName = foundOrg.name; + orgId = foundOrg.id; + } } return ( @@ -776,185 +1923,185 @@ const NotificationItem = memo((props) => { backgroundColor: theme.palette.cardHoverColor, }, }} - > -
- {data.amount === 1 && data.read === false ? - - : null} - {data.ignored === true ? - - : null} - {data.read === false ? - - : - - } - - {data.title} - -
- - {data.image !== undefined && data.image !== null && data.image.length > 0 ? - {data.title} - : - null - } - - {data.description} - -
- - - - {data.read === false ? ( - - ) : null} - - - - - +
+ {data.amount === 1 && data.read === false ? + + : null} + {data.ignored === true ? + + : null} + {data.read === false ? + + : + + } + + {data.title} + +
- 0 ? + {data.title} + : + null + } + + {data.description} + +
+ + - }} - > - First seen:{" "} - {new Date(data.created_at * 1000).toISOString().slice(0, 19)} - + {data.read === false ? ( + + ) : null} - - Last seen:{" "} - {new Date(data.updated_at * 1000).toISOString().slice(0, 19)} - + + + + - - Times seen: {data.amount} - -
+ + }} + > + First seen:{" "} + {new Date(data.created_at * 1000).toISOString().slice(0, 19)} + + + + Last seen:{" "} + {new Date(data.updated_at * 1000).toISOString().slice(0, 19)} + + + + Times seen: {data.amount} + +
+ + ); }) -const NotificationComponent = memo(({notifications, showRead, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification}) => { +const NotificationComponent = memo(({ notifications, showRead, selectedExecutionId, selectedWorkflow, highlightKMS, userdata, imagesize, boxColor, clickedFromOrgTab, notificationWidth, dismissNotification }) => { - return( + return (
{notifications === null || notifications === undefined || notifications?.length === 0 ? ( - null - ) : -
- {notifications?.map((notification, index) => { - if (showRead === false && notification.read === true) { - return null - } + null + ) : +
+ {notifications?.map((notification, index) => { + if (showRead === false && notification.read === true) { + return null + } - return ( - - ) - })} -
- } + return ( + + ) + })} +
+ }
) }) diff --git a/frontend/src/components/ShuffleCodeEditor1.jsx b/frontend/src/components/ShuffleCodeEditor1.jsx index 33b37483..3132034f 100644 --- a/frontend/src/components/ShuffleCodeEditor1.jsx +++ b/frontend/src/components/ShuffleCodeEditor1.jsx @@ -132,7 +132,7 @@ const CodeEditor = (props) => { fieldname, contentLoading, editorData, - handleSubflowParamChange, + handleTriggerParamChange, setAiQueryModalOpen, fullScreenMode, environment, @@ -212,7 +212,7 @@ const CodeEditor = (props) => { const triggerField = searchParams.get('trigger_field'); const triggerName = searchParams.get('trigger_name'); const conditionId = searchParams.get('condition_id'); - const conditionField = searchParams.get('field'); + const conditionField = searchParams.get('condition_field'); useEffect(() => { if (actionId === undefined || actionId === null) { @@ -251,7 +251,7 @@ const CodeEditor = (props) => { setSelectedCondition(condition); // Update available variables when condition changes updateAvailableVariables(actionlist); - }, [conditionId, fieldName]) + }, [conditionId, conditionField]) // Extract variable updating logic into a separate function const updateAvailableVariables = (actionlist) => { @@ -2588,7 +2588,7 @@ const CodeEditor = (props) => { // Handle condition fields if (conditionField !== null && handleConditionFieldChange !== undefined) { - handleConditionFieldChange(conditionField, fieldName, fixedcodedata); + handleConditionFieldChange(conditionField, fixedcodedata); } // Handle action fields else if (actionId !== undefined && actionId !== null && actionId.length > 0) { @@ -2596,7 +2596,7 @@ const CodeEditor = (props) => { } // Handle trigger fields else if (triggerId !== undefined && triggerId !== null && triggerId.length > 0) { - handleSubflowParamChange(triggerId, triggerField, fixedcodedata) + handleTriggerParamChange(triggerId, triggerField, fixedcodedata) } setExpansionModalOpen(false) diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index db5111bd..95b3e462 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -13032,7 +13032,7 @@ const AngularWorkflow = (defaultprops) => { event.preventDefault() setExpansionModalOpen(true) setActiveDialog("codeeditor") - navigate(`?condition_id=${data.id}&field=${data.name}`) + navigate(`?condition_id=${data.id}&condition_field=${data.name}`) setEditorData({ "name": data.name, "value": data.value || "", @@ -13117,9 +13117,9 @@ const AngularWorkflow = (defaultprops) => { // Update the field value based on type if (type === "source") { - handleConditionFieldChange("source", "value", toComplete); + handleConditionFieldChange("source", toComplete); } else if (type === "destination") { - handleConditionFieldChange("destination", "value", toComplete); + handleConditionFieldChange("destination", toComplete); } handleMenuClose(); @@ -13996,7 +13996,7 @@ const AngularWorkflow = (defaultprops) => { /> - const handleConditionFieldChange = (fieldType, fieldName, value) => { + const handleConditionFieldChange = (fieldType, value) => { if (fieldType === "source") { setSourceValue({ ...sourceValue, @@ -15197,7 +15197,7 @@ const AngularWorkflow = (defaultprops) => { } ] - const handleSubflowParamChange = (triggerId, triggerField, newData) => { + const handleTriggerParamChange = (triggerId, triggerField, newData) => { var updateFail = "" if (workflow !== undefined && workflow !== null) { @@ -17336,9 +17336,13 @@ const AngularWorkflow = (defaultprops) => { fullWidth rows="4" multiline - defaultValue={selectedTrigger.parameters[0]?.value} + value={selectedTriggerValue || ""} color="primary" placeholder="" + onChange={(e) => { + setLastSaved(false) + setSelectedTriggerValue(e.target.value) + }} onBlur={(e) => { setLastSaved(false) setTriggerTextInformationWrapper(e.target.value); @@ -25472,7 +25476,7 @@ const AngularWorkflow = (defaultprops) => { // selectedTrigger={selectedTrigger} aiSubmit={aiSubmit} toolsAppId={toolsApp.id} - handleSubflowParamChange={handleSubflowParamChange} + handleTriggerParamChange={handleTriggerParamChange} codedata={editorData.value} setcodedata={setcodedata} selectedEdge={selectedEdge} diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index 26bf6782..b2cb9426 100755 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -314,12 +314,15 @@ const LoginPage = props => { }, }) - if (serverside !== true) { - const tmpMessage = new URLSearchParams(window.location.search).get("message") - if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) { - setMessage(tmpMessage) + useEffect(() => { + if (serverside !== true) { + const tmpMessage = new URLSearchParams(window.location.search).get("message") + if (tmpMessage !== undefined && tmpMessage !== null && message !== tmpMessage) { + setMessage(tmpMessage) + toast(tmpMessage) + } } - } + }, []) if (document !== undefined) { if (register) { @@ -444,13 +447,17 @@ const LoginPage = props => { } if (isLoggedIn === true && serverside !== true) { - const tmpView = new URLSearchParams(window.location.search).get("view") - if (tmpView !== undefined && tmpView !== null && tmpView === "pricing") { - window.location.pathname = "/pricing" - return - } else if (tmpView !== undefined && tmpView !== null) { - window.location.pathname = tmpView - return + const tmpView = new URLSearchParams(window.location.search).get("view"); + if (tmpView !== undefined && tmpView !== null) { + let pathOnly = tmpView.split("?")[0]; + if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly; + + if (pathOnly === "/pricing" || pathOnly === "admin") { + window.location.replace(pathOnly + window.location.search); + } else { + window.location.replace(pathOnly); + } + return; } window.location.pathname = "/workflows" @@ -575,20 +582,17 @@ const LoginPage = props => { setCookie(responseJson["cookies"][key].key, responseJson["cookies"][key].value, { path: "/" }) } - const tmpView = new URLSearchParams(window.location.search).get("view") + const tmpView = new URLSearchParams(window.location.search).get("view"); if (tmpView !== undefined && tmpView !== null) { - //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}` - // Check if slash in the url + let pathOnly = tmpView.split("?")[0]; + if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly; - var newUrl = `/${tmpView}` - if (tmpView.startsWith("/")) { - newUrl = `${tmpView}` + if (pathOnly === "/pricing" || pathOnly === "admin") { + window.location.replace(pathOnly + window.location.search); + } else { + window.location.replace(pathOnly); } - - console.log("Found url: ", newUrl) - - window.location.pathname = newUrl - return + return; } console.log("LOGIN DATA: ", responseJson) @@ -647,9 +651,14 @@ const LoginPage = props => { const tmpView = new URLSearchParams(window.location.search).get("view") if (tmpView !== undefined && tmpView !== null) { - //const newUrl = `/${tmpView}${decodeURIComponent(window.location.search)}` - const newUrl = `/${tmpView}` - window.location.pathname = newUrl + let pathOnly = tmpView.split("?")[0]; + if (!pathOnly.startsWith("/")) pathOnly = "/" + pathOnly; + + if (pathOnly === "/pricing" || pathOnly === "admin") { + window.location.replace(pathOnly + window.location.search); + } else { + window.location.replace(pathOnly); + } return } diff --git a/frontend/src/views/SettingsPage.jsx b/frontend/src/views/SettingsPage.jsx index f10dbfb7..ec5ba658 100755 --- a/frontend/src/views/SettingsPage.jsx +++ b/frontend/src/views/SettingsPage.jsx @@ -458,7 +458,12 @@ const Settings = (props) => { if (responseJson["success"] === false) { setPasswordFormMessage(responseJson["reason"]); } else { - toast("Changed password!"); + var reason = "" + if (responseJson.reason !== undefined && responseJson.reason !== null && responseJson.reason.length > 0) { + reason += responseJson.reason + } + + toast.success("Changed password! " + reason); setPasswordFormMessage(""); } }) diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx index 1655c901..c059f991 100644 --- a/frontend/src/views/Workflows2.jsx +++ b/frontend/src/views/Workflows2.jsx @@ -5071,7 +5071,7 @@ const Workflows2 = (props) => { //isLoaded && isLoggedIn && workflowDone ? ( const loadedCheck = - workflowDone ? ( + workflowDone && isLoaded ? (
{/* @@ -5134,7 +5134,9 @@ const Workflows2 = (props) => { }} > - Loading Workflows + + Loading Workflows and Apps +
); From 425c4d080826b8e2a8dd55928d0af14a52b75e9b Mon Sep 17 00:00:00 2001 From: Frikky Date: Tue, 27 May 2025 23:31:19 +0200 Subject: [PATCH 07/14] Changed Algolia public API Key --- backend/go-app/main.go | 5 +--- frontend/src/components/AppAuthTab.jsx | 2 +- frontend/src/components/AppGrid.jsx | 2 +- frontend/src/components/AppModal.jsx | 2 +- frontend/src/components/AppSearch1.jsx | 2 +- frontend/src/components/Appsearch.jsx | 2 +- frontend/src/components/Billing.jsx | 8 +++--- frontend/src/components/CloudSyncTab.jsx | 21 +++++++++++++-- frontend/src/components/ConfigureWorkflow.jsx | 2 +- frontend/src/components/CreatorGrid.jsx | 2 +- frontend/src/components/DocsGrid.jsx | 2 +- frontend/src/components/EditWorkflow.jsx | 6 ++--- frontend/src/components/LeftSideBar.jsx | 2 +- frontend/src/components/SearchData.jsx | 2 +- frontend/src/components/WorkflowGrid.jsx | 2 +- frontend/src/components/Workflowsearch.jsx | 2 +- frontend/src/views/AngularWorkflow.jsx | 27 +++++++++++++------ frontend/src/views/ApiExplorerWrapper.jsx | 2 +- frontend/src/views/AppExplorer.jsx | 6 ++--- frontend/src/views/Apps.jsx | 6 ++--- frontend/src/views/Apps2.jsx | 2 +- frontend/src/views/Workflows2.jsx | 4 +-- 22 files changed, 69 insertions(+), 42 deletions(-) diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 60f162b4..1aaa954e 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -447,7 +447,7 @@ func checkGitProxy(cloneOptions *git.CloneOptions) *git.CloneOptions { func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) error { // Returns false if there is an issue // Use this for register - err := shuffle.CheckPasswordStrength(password) + err := shuffle.CheckPasswordStrength(username, password) if err != nil { log.Printf("[WARNING] Bad password strength: %s", err) return err @@ -460,8 +460,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) } ctx := context.Background() - //users, err := FindUser(ctx context.Context, username string) ([]User, error) { - users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(username))) if err != nil && len(users) == 0 { log.Printf("[WARNING] Failed getting user %s: %s", username, err) @@ -486,7 +484,6 @@ func createNewUser(username, password, role, apikey string, org shuffle.OrgMini) newUser.Active = true newUser.Orgs = []string{org.Id} - // FIXME - Remove this later if role == "admin" { newUser.Role = "admin" newUser.Roles = []string{"admin"} diff --git a/frontend/src/components/AppAuthTab.jsx b/frontend/src/components/AppAuthTab.jsx index 643aa8dc..c5b5c57f 100644 --- a/frontend/src/components/AppAuthTab.jsx +++ b/frontend/src/components/AppAuthTab.jsx @@ -66,7 +66,7 @@ import { Context } from '../context/ContextApi.jsx'; const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ) const AppAuthTab = memo((props) => { diff --git a/frontend/src/components/AppGrid.jsx b/frontend/src/components/AppGrid.jsx index 8a5d9a18..e647085b 100644 --- a/frontend/src/components/AppGrid.jsx +++ b/frontend/src/components/AppGrid.jsx @@ -53,7 +53,7 @@ import { const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ); //const searchClient = algoliasearch("L55H18ZINA", "a19be455e7e75ee8f20a93d26b9fc6d6") diff --git a/frontend/src/components/AppModal.jsx b/frontend/src/components/AppModal.jsx index 4b3b9516..38c8581f 100644 --- a/frontend/src/components/AppModal.jsx +++ b/frontend/src/components/AppModal.jsx @@ -35,7 +35,7 @@ import { Context } from '../context/ContextApi.jsx'; const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" );; const AppModal = ({ open, onClose, app, globalUrl, getApps}) => { diff --git a/frontend/src/components/AppSearch1.jsx b/frontend/src/components/AppSearch1.jsx index 3947f640..449ad925 100644 --- a/frontend/src/components/AppSearch1.jsx +++ b/frontend/src/components/AppSearch1.jsx @@ -13,7 +13,7 @@ import { InputAdornment, Typography, } from '@mui/material'; -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const Appsearch = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp, placeholder, diff --git a/frontend/src/components/Appsearch.jsx b/frontend/src/components/Appsearch.jsx index d13eb9a1..8fbb7e38 100644 --- a/frontend/src/components/Appsearch.jsx +++ b/frontend/src/components/Appsearch.jsx @@ -20,7 +20,7 @@ import { } from '@mui/material'; import aa from 'search-insights' -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const Appsearch = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, userdata, cy, isCreatorPage, actionImageList, setActionImageList, setUserSpecialzedApp } = props const { themeMode } = useContext(Context) diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index b0e85c8d..af8bb78c 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -52,11 +52,13 @@ import { //import { useAlert import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; -import BillingStats from "./BillingStats.jsx"; +import BillingStats from "../components/BillingStats.jsx"; +import LicencePopup from "../components/LicencePopup.jsx"; import { handlePayasyougo } from "../views/HandlePaymentNew.jsx" -import DeleteIcon from '@mui/icons-material/Delete'; + import { Context } from "../context/ContextApi.jsx"; -import LicencePopup from "./LicencePopup.jsx"; + +import DeleteIcon from '@mui/icons-material/Delete'; import { DataGrid } from "@mui/x-data-grid"; const Billing = memo((props) => { diff --git a/frontend/src/components/CloudSyncTab.jsx b/frontend/src/components/CloudSyncTab.jsx index d4aaa072..dd43bc4a 100644 --- a/frontend/src/components/CloudSyncTab.jsx +++ b/frontend/src/components/CloudSyncTab.jsx @@ -48,15 +48,21 @@ const CloudSyncTab = (props) => { const [, forceUpdate] = React.useState(); const itemColor = "white"; const isCloud = window?.location?.host === "localhost:3002" || window?.location?.host === "shuffler.io"; + const { themeMode, brandColor } = useContext(Context); const theme = getTheme(themeMode, brandColor); + useEffect(() => { getSettings(); }, []); + const GridItem = (props) => { const [expanded, setExpanded] = React.useState(false); const [showEdit, setShowEdit] = React.useState(false); const [newValue, setNewValue] = React.useState(-100); - const primary = props.data.primary; + var primary = props.data.primary + + const shownName = props.data.newname !== undefined && props.data.newname !== null && props.data.newname !== primary ? props.data.newname : primary + const secondary = props.data.secondary; const primaryIcon = props.data.icon; const secondaryIcon = props.data.active ? @@ -191,7 +197,7 @@ const CloudSyncTab = (props) => { {isCloud && userdata.support === true ? @@ -717,7 +723,9 @@ const CloudSyncTab = (props) => { {selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? + {[...Array(18)].map((_, i) => ( +
{ } const newkey = key.replaceAll("_", " "); + + // Rewrites to frontend names + var newname = newkey + if (newkey === "app executions") { + newname = "app runs" + } + const griditem = { primary: newkey, secondary: @@ -770,6 +785,8 @@ const CloudSyncTab = (props) => { data_collection: "None", active: item.active, icon: , + + newname: newname, }; return ( diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 8dea326c..b6f0c734 100755 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -633,7 +633,7 @@ const ConfigureWorkflow = (props) => { if (aa !== undefined) { aa('init', { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }) const timestamp = new Date().getTime() diff --git a/frontend/src/components/CreatorGrid.jsx b/frontend/src/components/CreatorGrid.jsx index f3922e3e..96e9c059 100644 --- a/frontend/src/components/CreatorGrid.jsx +++ b/frontend/src/components/CreatorGrid.jsx @@ -37,7 +37,7 @@ import { AvatarGroup, } from "@mui/material" -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const CreatorGrid = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, isHeader } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows diff --git a/frontend/src/components/DocsGrid.jsx b/frontend/src/components/DocsGrid.jsx index 7a32001c..d22beaa4 100644 --- a/frontend/src/components/DocsGrid.jsx +++ b/frontend/src/components/DocsGrid.jsx @@ -29,7 +29,7 @@ import { -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const DocsGrid = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, userdata, } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows diff --git a/frontend/src/components/EditWorkflow.jsx b/frontend/src/components/EditWorkflow.jsx index 8265f079..939b3998 100644 --- a/frontend/src/components/EditWorkflow.jsx +++ b/frontend/src/components/EditWorkflow.jsx @@ -226,8 +226,8 @@ const EditWorkflow = (props) => {
- - {newWorkflow ? "New" : "Editing"} workflow + + {newWorkflow ? "New" : "Editing"} Workflow {newWorkflow === true ? null : @@ -393,7 +393,7 @@ const EditWorkflow = (props) => {
- +
{ diff --git a/frontend/src/components/LeftSideBar.jsx b/frontend/src/components/LeftSideBar.jsx index 69a9b29e..92f03b7e 100644 --- a/frontend/src/components/LeftSideBar.jsx +++ b/frontend/src/components/LeftSideBar.jsx @@ -518,7 +518,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { console.error("Logout error:", error); }); }; - console.log("userdata: ", userdata); + const avatarMenu = ( { const { serverside, globalUrl, userdata } = props let navigate = useNavigate(); diff --git a/frontend/src/components/WorkflowGrid.jsx b/frontend/src/components/WorkflowGrid.jsx index 5de17156..218cc060 100644 --- a/frontend/src/components/WorkflowGrid.jsx +++ b/frontend/src/components/WorkflowGrid.jsx @@ -24,7 +24,7 @@ import { import WorkflowPaper from "../components/WorkflowPaper.jsx" import WorkflowPaperNew from "../components/WorkflowPaperNew.jsx" -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const AppGrid = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, alternativeView, onlyResults, inputsearch } = props diff --git a/frontend/src/components/Workflowsearch.jsx b/frontend/src/components/Workflowsearch.jsx index b8f4c022..1b342645 100644 --- a/frontend/src/components/Workflowsearch.jsx +++ b/frontend/src/components/Workflowsearch.jsx @@ -10,7 +10,7 @@ import algoliasearch from 'algoliasearch'; import { InstantSearch, connectSearchBox, connectHits } from 'react-instantsearch-dom'; import { Grid, Paper, TextField, ButtonBase, InputAdornment, Typography, Button, Tooltip} from '@mui/material'; -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const WorkflowSearch = props => { const { maxRows, showName, showSuggestion, isMobile, globalUrl, parsedXs, newSelectedApp, setNewSelectedApp, defaultSearch, showSearch, ConfiguredHits, selectAble, } = props const rowHandler = maxRows === undefined || maxRows === null ? 50 : maxRows diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 95b3e462..7526c9d7 100755 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -438,7 +438,7 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); //const referenceUrl = "https://shuffler.io/functions/webhooks/" //const referenceUrl = window.location.origin+"/api/v1/hooks/" -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const AngularWorkflow = (defaultprops) => { const { globalUrl, setCookie, isLoggedIn, isLoaded, userdata, data_id, ReactGA, } = defaultprops; const {themeMode, supportEmail, brandColor} = useContext(Context) @@ -3573,6 +3573,12 @@ const AngularWorkflow = (defaultprops) => { if (curapp?.actions === undefined || curapp?.actions === null || curapp?.actions?.length === 0 || curapp?.actions?.length === 1) { loadAppConfig(curapp?.id, false, true) } + + if (key > 10) { + console.log("Breaking on 10 sideloads of total", responseJson.length) + break + + } } // Find app with ID "794e51c3c1a8b24b89ccc573a3defc47" (gmail) to force-break it, @@ -11856,7 +11862,7 @@ const AngularWorkflow = (defaultprops) => { if (queryID !== undefined && queryID !== null) { aa('init', { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }) const timestamp = new Date().getTime() @@ -18698,7 +18704,9 @@ const AngularWorkflow = (defaultprops) => { {originalWorkflow?.suborg_distribution === undefined || originalWorkflow?.suborg_distribution === null || originalWorkflow?.suborg_distribution?.length === 0 || originalWorkflow?.suborg_distribution.includes("none") ? - originalWorkflow?.parentorg_workflow !== undefined && originalWorkflow?.parentorg_workflow !== null && originalWorkflow?.parentorg_workflow.length > 0 || workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0 ? +
+ {originalWorkflow?.parentorg_workflow !== undefined && originalWorkflow?.parentorg_workflow !== null && originalWorkflow?.parentorg_workflow.length > 0 || workflow?.parentorg_workflow !== undefined && workflow?.parentorg_workflow !== null && workflow?.parentorg_workflow.length > 0 ? + - : userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 1 && workflow?.id !== undefined && workflow?.id && workflow?.id?.length > 0 ? + + : null} + + {userdata !== undefined && userdata !== null && userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 1 && workflow?.id !== undefined && workflow?.id && workflow?.id?.length > 0 ? - : null - + : null} +
: { if (queryID !== undefined && queryID !== null) { aa('init', { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }) const timestamp = new Date().getTime() diff --git a/frontend/src/views/ApiExplorerWrapper.jsx b/frontend/src/views/ApiExplorerWrapper.jsx index 45f9d762..d104d43d 100644 --- a/frontend/src/views/ApiExplorerWrapper.jsx +++ b/frontend/src/views/ApiExplorerWrapper.jsx @@ -50,7 +50,7 @@ import { green } from "../views/AngularWorkflow.jsx" const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ) // Lazy loading of ApiExplorer component to reduce initial load time diff --git a/frontend/src/views/AppExplorer.jsx b/frontend/src/views/AppExplorer.jsx index c2b138a9..94bd3ed3 100644 --- a/frontend/src/views/AppExplorer.jsx +++ b/frontend/src/views/AppExplorer.jsx @@ -93,7 +93,7 @@ import aa from "search-insights"; // 2 = OpenAPI (Invalid) const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ) const AppExplorer = (props) => { @@ -3996,7 +3996,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"; if (queryID !== undefined && queryID !== null) { aa("init", { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }); const timestamp = new Date().getTime(); @@ -4085,7 +4085,7 @@ const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)"; if (queryID !== undefined && queryID !== null) { aa("init", { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }); const timestamp = new Date().getTime(); diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 8b4eadd4..684d8e9c 100755 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -280,7 +280,7 @@ export const GetParsedPaths = (inputdata, basekey) => { return parsedValues; }; -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240") +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e") const Apps = (props) => { const { globalUrl, isLoggedIn, isLoaded, userdata, serverside, } = props; @@ -1305,7 +1305,7 @@ const Apps = (props) => { if (queryID !== undefined && queryID !== null) { aa("init", { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }); const timestamp = new Date().getTime(); @@ -2036,7 +2036,7 @@ const Apps = (props) => { if (queryID !== undefined && queryID !== null) { aa('init', { appId: "JNSS5CFDZZ", - apiKey: "db08e40265e2941b9a7d8f644b6e5240", + apiKey: "c8f882473ff42d41158430be09ec2b4e", }) const timestamp = new Date().getTime() diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index d054c232..6b062800 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -46,7 +46,7 @@ import AppCreationModal from "../components/AppCreationModal.jsx"; const searchClient = algoliasearch( "JNSS5CFDZZ", - "db08e40265e2941b9a7d8f644b6e5240" + "c8f882473ff42d41158430be09ec2b4e" ); // AppCard Component diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx index c059f991..9c7f5466 100644 --- a/frontend/src/views/Workflows2.jsx +++ b/frontend/src/views/Workflows2.jsx @@ -111,7 +111,7 @@ import { removeQuery } from "../components/ScrollToTop.jsx"; import {green, yellow, red, grey } from "../views/AngularWorkflow.jsx" -const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240"); +const searchClient = algoliasearch("JNSS5CFDZZ", "c8f882473ff42d41158430be09ec2b4e"); const svgSize = 24; const imagesize = 22; @@ -5071,7 +5071,7 @@ const Workflows2 = (props) => { //isLoaded && isLoggedIn && workflowDone ? ( const loadedCheck = - workflowDone && isLoaded ? ( + workflowDone ? (
{/* From 1281f254055e44b133c7f448ac67920aa679c438 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 29 May 2025 21:52:22 +0200 Subject: [PATCH 08/14] Autobuild backend pls :)) --- backend/go-app/go.mod | 4 ++-- backend/go-app/go.sum | 4 ++-- backend/go-app/main.go | 34 ++++++++++++++++++++++------------ 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 9accdf46..449a215a 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.8 //replace github.com/frikky/schemaless => ../../../schemaless //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi -replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared require ( cloud.google.com/go/datastore v1.20.0 @@ -22,7 +22,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.8.60 + github.com/shuffle/shuffle-shared v0.8.70 golang.org/x/crypto v0.37.0 google.golang.org/api v0.228.0 google.golang.org/grpc v1.71.1 diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index e4b4e8e0..34ccd9f6 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -341,8 +341,8 @@ github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fc github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shuffle/shuffle-shared v0.8.58 h1:QzCKtQjuaozb+xyLkw6IjwqCNVWzpuJJnxkpXkLCP9M= -github.com/shuffle/shuffle-shared v0.8.58/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So= +github.com/shuffle/shuffle-shared v0.8.70 h1:Balbk7kIVUSgM9Lc+VJ09ZpaF18MlFxMPZdkPTD/+gU= +github.com/shuffle/shuffle-shared v0.8.70/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 1aaa954e..657e4a1f 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1850,6 +1850,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { // return //} + log.Printf("[DEBUG] HOOKS: webhook callback: %s", request.URL.String()) + if request.Method != "POST" { request.Method = "POST" } @@ -1861,6 +1863,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { path := strings.Split(request.URL.String(), "/") if len(path) < 4 { + log.Printf("[DEBUG] HOOKS: Invalid webhook path: %s", request.URL.String()) resp.WriteHeader(403) resp.Write([]byte(`{"success": false}`)) return @@ -1876,7 +1879,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { if location[1] == "api" { if len(location) <= 4 { log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location)) - resp.WriteHeader(401) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } @@ -1893,6 +1896,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } } + log.Printf("[DEBUG] HOOKS: Pre user agent check") + // Find user agent header userAgent := request.Header.Get("User-Agent") if strings.Contains(strings.ToLower(userAgent), "microsoftpreview") || strings.Contains(strings.ToLower(userAgent), "googlebot") { @@ -1915,8 +1920,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { //log.Printf("HookID: %s", hookId) hook, err := shuffle.GetHook(ctx, hookId) if err != nil { - log.Printf("[WARNING] Failed getting hook %s (callback): %s", hookId, err) - resp.WriteHeader(401) + log.Printf("[WARNING] HOOKS: Failed getting hook %s (callback): %s", hookId, err) + resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } @@ -1928,21 +1933,21 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { //resp.WriteHeader(200) //resp.Write([]byte(`{"success": true}`)) if hook.Status == "stopped" { - log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id) - resp.WriteHeader(401) + log.Printf("[WARNING] HOOKS: Not running %s because hook status is stopped", hook.Id) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`))) return } if len(hook.Workflows) == 0 { - log.Printf("[DEBUG] Not running because hook isn't connected to any workflows") - resp.WriteHeader(401) + log.Printf("[DEBUG] HOOKS: Not running because hook isn't connected to any workflows") + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) return } if hook.Environment == "cloud" { - log.Printf("[DEBUG] This should trigger in the cloud. Duplicate action allowed onprem.") + log.Printf("[DEBUG] HOOKS: This should trigger in the cloud. Duplicate action allowed onprem.") } // Check auth @@ -1958,7 +1963,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { body, err := ioutil.ReadAll(request.Body) if err != nil { - log.Printf("[DEBUG] Body data error: %s", err) + log.Printf("[DEBUG] HOOKS: data read error: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -1999,7 +2004,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { b, err := json.Marshal(newBody) if err != nil { - log.Printf("[ERROR] Failed newBody marshaling for webhook: %s", err) + log.Printf("[ERROR] HOOKS: Failed newBody marshaling for webhook: %s", err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false}`)) return @@ -2015,7 +2020,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } if len(hook.Start) == 0 { - log.Printf("[WARNING] No start node for hook %s - running with workflow default.", hook.Id) + log.Printf("[ERROR] HOOKS: No start node for hook %s - running with workflow default.", hook.Id) //bodyWrapper = string(parsedBody) } @@ -2027,7 +2032,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { // OrgId: activeOrgs[0].Id, workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest, hook.OrgId) - if err == nil { if hook.Version == "v2" { timeout := 15 @@ -2062,6 +2066,7 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { } else { resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s"}`, workflowExecution.ExecutionId))) } + return } @@ -2069,6 +2074,11 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp))) } + log.Printf("[ERROR] HOOKS: END OF FUNCTION FOR '%s'. IF this is reached, something went wrong.", hook.Id) + resp.WriteHeader(500) + resp.Write([]byte(`{"success": false, "reason": "Failed to run workflow. Check logs."}`)) + + } func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { From 063c82ee0d6444b8378519593107b2e2327830a1 Mon Sep 17 00:00:00 2001 From: Frikky Date: Thu, 29 May 2025 23:26:24 +0200 Subject: [PATCH 09/14] Many fixes: Notifications not being rendered even if api fetches the notifications Add copy auth id button on auth tab of admin page Added workflow link redirection for datastore key . Open docs in new tab for integration partner when it is external link. Fix multiple light theme text issue and dialog box issues. change usecase title for light theme to: --- frontend/src/components/AdminNavBar.jsx | 10 ++- frontend/src/components/AppAuthTab.jsx | 41 +++++++++++++ frontend/src/components/CacheView.jsx | 64 +++++++++++++++++++- frontend/src/components/LeftSideBar.jsx | 5 +- frontend/src/components/ParsedAction.jsx | 52 ++++++++-------- frontend/src/components/Priorities.jsx | 1 - frontend/src/components/UserManagmentTab.jsx | 30 ++++++++- frontend/src/views/Usecases2.jsx | 9 ++- frontend/src/views/Workflows2.jsx | 34 +++++++---- 9 files changed, 201 insertions(+), 45 deletions(-) diff --git a/frontend/src/components/AdminNavBar.jsx b/frontend/src/components/AdminNavBar.jsx index a37b8c15..f52e0a79 100644 --- a/frontend/src/components/AdminNavBar.jsx +++ b/frontend/src/components/AdminNavBar.jsx @@ -208,9 +208,17 @@ const AdminNavBar = (props) => { const ComponentToRender = selectedItemData.component; const componentProps = selectedItemData.props; - return ; + const updatedProps = { + ...componentProps, + notifications: notifications, + setNotifications: setNotifications, + userdata: userdata, + selectedOrganization: selectedOrganization }; + return ; +}; + const defaultImage = "/images/logos/orange_logo.svg" const imageData = selectedOrganization?.image === undefined || selectedOrganization?.image.length === 0 diff --git a/frontend/src/components/AppAuthTab.jsx b/frontend/src/components/AppAuthTab.jsx index c5b5c57f..8908590f 100644 --- a/frontend/src/components/AppAuthTab.jsx +++ b/frontend/src/components/AppAuthTab.jsx @@ -1181,6 +1181,47 @@ const AppAuthTab = memo((props) => { )} + + { + navigator.clipboard.writeText(data.id); + document.execCommand("copy"); + + toast(data.id + " copied to clipboard"); + }} + > + + + + + + + { overflowX: "auto", }}> - {["Key", "Value", "Actions", "Updated", "Distribution"].map((header, index) => ( + {["Key", "Value", "workflow", "Actions", "Updated", "Distribution"].map((header, index) => ( { backgroundColor: theme.palette.platformColor, }} > - {Array(5) + {Array(6) .fill() .map((_, colIndex) => ( { data.value } /> + + + + : ( + + + + + + + + + + ) + } + style={{ + display: "table-cell", + overflow: "hidden", + verticalAlign: "middle", + padding: "8px 8px 8px 15px", + maxWidth: 200, + overflowX: "auto", + }} + /> { - + 0 ? userdata?.active_org?.branding?.documentation_link : "/docs" } target={userdata?.active_org?.branding?.documentation_link?.length > 0 && userdata?.org_status?.includes("integration_partner") ? "_blank" : "_self" } style={hrefStyle}> { handleClose(); @@ -1633,7 +1633,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
diff --git a/frontend/src/components/BillingStats.jsx b/frontend/src/components/BillingStats.jsx index 7b94c67c..19bda1b8 100644 --- a/frontend/src/components/BillingStats.jsx +++ b/frontend/src/components/BillingStats.jsx @@ -33,8 +33,15 @@ import { import { BarChart, + BarSeries, + Bar, + BarLabel, + GridlineSeries, Gridline, + TooltipArea, + ChartTooltip, + TooltipTemplate, } from 'reaviz'; import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; @@ -45,20 +52,32 @@ const LineChartWrapper = ({keys, inputname, height, width}) => { const inputdata = keys.data === undefined ? keys : keys.data const {themeMode} = useContext(Context) const theme = getTheme(themeMode) - + + return (
- + {inputname} + + } + /> + } gridlines={ } /> } /> +
) } @@ -70,6 +89,7 @@ const AppStats = (defaultprops) => { const [keys, setKeys] = useState([]) const [searches, setSearches] = useState([]); const [appRuns, setAppruns] = useState(undefined); + const [childOrgsAppRuns, setChildOrgsAppRuns] = useState(undefined); const [appRunCosts, setApprunCosts] = useState(undefined); const [workflowRuns, setWorkflowRuns] = useState(undefined); const [subflowRuns, setSubflowRuns] = useState(undefined); @@ -401,6 +421,11 @@ const AppStats = (defaultprops) => { "data": [] } + var childorgappRuns = { + "key": "Child Org App Runs", + "data": [] + } + var workflowRuns = { "key": "Workflow Runs (includes subflows)", "data": [] @@ -442,6 +467,13 @@ const AppStats = (defaultprops) => { }) } + if (item["child_app_executions"] !== undefined && item["child_app_executions"] !== null) { + childorgappRuns["data"].push({ + key: new Date(item["date"]), + data: inputdata["child_app_executions"] + }) + } + // Check if workflow_executions key in item if (item["workflow_executions"] !== undefined && item["workflow_executions"] !== null) { workflowRuns["data"].push({ @@ -471,6 +503,15 @@ const AppStats = (defaultprops) => { }) } + if (inputdata["daily_child_app_executions"] !== undefined && inputdata["daily_app_executions"] !== null) { + childorgappRuns["data"].push({ + key: new Date(), + data: inputdata["daily_child_app_executions"] + }) + + //setApprunCosts(appcostRuns) + } + if (inputdata["daily_workflow_executions"] !== undefined && inputdata["daily_workflow_executions"] !== null) { workflowRuns["data"].push({ key: new Date(), @@ -485,6 +526,11 @@ const AppStats = (defaultprops) => { }) } + // Only for parent orgs + if (childorgappRuns["data"].length > 0) { + setChildOrgsAppRuns(childorgappRuns) + } + setSubflowRuns(subflowRuns) setWorkflowRuns(workflowRuns) setAppruns(appRuns) @@ -895,7 +941,13 @@ const AppStats = (defaultprops) => { {appRuns === undefined ? null : - + + } + + {childOrgsAppRuns === undefined ? + null + : + } {workflowRuns === undefined ? @@ -922,8 +974,9 @@ const AppStats = (defaultprops) => {
Loading usage for selected period (may take a while) + + -
: { } const newWorkflow = isEditing === true ? false : true - const priority = userdata === undefined || userdata === null ? null : userdata.priorities.find(prio => prio.type === "usecase" && prio.active === true) + const priority = userdata === undefined || userdata === null || userdata.priorities === null || userdata.priorities === undefined ? null : userdata?.priorities?.find(prio => prio.type === "usecase" && prio.active === true) var upload = ""; var total_count = 0 diff --git a/frontend/src/components/LeftSideBar.jsx b/frontend/src/components/LeftSideBar.jsx index 7e0ef311..d10d4f00 100644 --- a/frontend/src/components/LeftSideBar.jsx +++ b/frontend/src/components/LeftSideBar.jsx @@ -450,6 +450,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { const handleUpdateTheme = (newTheme) => { + handleThemeChange(newTheme); + setCurrentSelectedTheme(newTheme) const data = { "user_id": userdata?.id, @@ -470,14 +472,11 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { }).then((response) => response.json().then((responseJson) => { if (responseJson["success"] === false) { - toast("Failed updating theme: ", responseJson.reason); - } else { - handleThemeChange(newTheme); - setCurrentSelectedTheme(newTheme); + toast("Failed saving your theme: ", responseJson.reason); } }) ).catch((error) => { - console.log("Error changing theme: ", error); + console.log("Error saving your theme: ", error); }); }; @@ -589,7 +588,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { } if (userdata?.org_status?.includes("integration_partner")){ handleChangeTheme(newTheme); - }else { + } else { + handleUpdateTheme(newTheme); } }} @@ -671,7 +671,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => { - Version: 2.0.2 + Version: 2.1.0-rc1
diff --git a/frontend/src/components/LicencePopup.jsx b/frontend/src/components/LicencePopup.jsx index 0e580cbb..0267267d 100644 --- a/frontend/src/components/LicencePopup.jsx +++ b/frontend/src/components/LicencePopup.jsx @@ -1127,7 +1127,6 @@ const LicencePopup = (props) => { color: "white", } - console.log("Priceitem: ", shuffleVariant) // const isLoggedInHandler = () => { // if (calculatedCost === payasyougo) { // handlePayasyougo(props.userdata) @@ -1237,7 +1236,6 @@ const LicencePopup = (props) => { }); }; - console.log("Selected Organization: ", selectedOrganization.subscriptions) return (
diff --git a/frontend/src/views/Admin2.jsx b/frontend/src/views/Admin2.jsx index aaff1429..15a82580 100644 --- a/frontend/src/views/Admin2.jsx +++ b/frontend/src/views/Admin2.jsx @@ -334,7 +334,8 @@ const Admin2 = (props) => { } return ( -
+ //
+
); diff --git a/frontend/src/views/Workflows2.jsx b/frontend/src/views/Workflows2.jsx index 4832777a..123f8d86 100644 --- a/frontend/src/views/Workflows2.jsx +++ b/frontend/src/views/Workflows2.jsx @@ -1349,7 +1349,9 @@ const Workflows2 = (props) => { // When there are no workflows, we can set the loading to false setIsLoadingWorkflow(false) if (currTab !== 2) { - toast("No workflows found. Showing workflow discovery") + toast.info("No workflows found in this org. Feel free to look into our public workflows!" , { + timeout: 7500, + }) setCurrTab(2) } } From cd86ce9b7457a55e3cbaf37f76979296b95eb889 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 2 Jun 2025 00:20:34 +0200 Subject: [PATCH 11/14] Changed just in case for onprem fixes to take place --- backend/app_gen/openapi/baseline/requirements.txt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/app_gen/openapi/baseline/requirements.txt b/backend/app_gen/openapi/baseline/requirements.txt index dfad3eb9..c0d2f96f 100755 --- a/backend/app_gen/openapi/baseline/requirements.txt +++ b/backend/app_gen/openapi/baseline/requirements.txt @@ -1,3 +1,11 @@ # No extra requirements needed -requests -urllib3 +requests==2.32.3 +urllib3==2.3.0 +liquidpy==0.8.2 +MarkupSafe==3.0.2 +flask[async]==3.1.0 +python-dateutil==2.9.0.post0 +PyJWT==2.10.1 +cryptography==44.0.2 +shufflepy==0.1.0 +shuffle-sdk==0.0.25 From 75b6d60dd436b29a79c893dd0e7e0af6fd89e1a3 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 2 Jun 2025 00:30:46 +0200 Subject: [PATCH 12/14] Fixed an issue with app building onprem --- backend/go-app/docker.go | 147 +++++++++++++++++++++------------------ backend/go-app/go.mod | 2 +- backend/go-app/go.sum | 4 +- backend/go-app/main.go | 10 +-- 4 files changed, 87 insertions(+), 76 deletions(-) diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index 9f0c72e2..b7ee7e6d 100755 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -211,7 +211,7 @@ func fixTags(tags []string) []string { func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string, downloadIfFail bool) error { ctx := context.Background() client, err := client.NewEnvClient() - defer client.Close() + defer client.Close() if err != nil { log.Printf("Unable to create docker client: %s", err) return err @@ -473,73 +473,84 @@ func buildImage(tags []string, dockerfileLocation string) error { } } } - } else { - - ctx := context.Background() - client, err := client.NewEnvClient() - defer client.Close() - if err != nil { - log.Printf("Unable to create docker client: %s", err) - return err - } - - log.Printf("[INFO] Docker Tags: %s", tags) - dockerfileSplit := strings.Split(dockerfileLocation, "/") - - // Create a buffer - buf := new(bytes.Buffer) - tw := tar.NewWriter(buf) - defer tw.Close() - baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/") - - // Builds the entire folder into buf - err = getParsedTar(tw, baseDir, "") - if err != nil { - log.Printf("Tar issue: %s", err) - } - - dockerFileTarReader := bytes.NewReader(buf.Bytes()) - buildOptions := types.ImageBuildOptions{ - Remove: true, - Tags: tags, - BuildArgs: map[string]*string{}, - } - //NetworkMode: "host", - - httpProxy := os.Getenv("HTTP_PROXY") - if len(httpProxy) > 0 { - buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy - } - httpsProxy := os.Getenv("HTTPS_PROXY") - if len(httpProxy) > 0 { - buildOptions.BuildArgs["https_proxy"] = &httpsProxy - } - - // Build the actual image - imageBuildResponse, err := client.ImageBuild( - ctx, - dockerFileTarReader, - buildOptions, - ) - - if err != nil { - return err - } - - // Read the STDOUT from the build process - defer imageBuildResponse.Body.Close() - buildBuf := new(strings.Builder) - _, err = io.Copy(buildBuf, imageBuildResponse.Body) - if err != nil { - return err - } else { - if strings.Contains(buildBuf.String(), "errorDetail") { - log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n")) - return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ","))) - } - } + return nil } + + ctx := context.Background() + client, err := client.NewEnvClient() + defer client.Close() + if err != nil { + log.Printf("Unable to create docker client: %s", err) + return err + } + + log.Printf("[INFO] Docker Tags: %s", tags) + dockerfileSplit := strings.Split(dockerfileLocation, "/") + + // Create a buffer + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + defer tw.Close() + baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/") + + // Builds the entire folder into buf + err = getParsedTar(tw, baseDir, "") + if err != nil { + log.Printf("[ERROR] Tar issue during app build: %s", err) + } + + dockerFileTarReader := bytes.NewReader(buf.Bytes()) + buildOptions := types.ImageBuildOptions{ + Remove: true, + Tags: tags, + BuildArgs: map[string]*string{}, + } + //NetworkMode: "host", + + httpProxy := os.Getenv("HTTP_PROXY") + if len(httpProxy) > 0 { + buildOptions.BuildArgs["HTTP_PROXY"] = &httpProxy + } + httpsProxy := os.Getenv("HTTPS_PROXY") + if len(httpProxy) > 0 { + buildOptions.BuildArgs["https_proxy"] = &httpsProxy + } + + // Print the actual file content from dockerFileTarReader + /* + data, err := ioutil.ReadAll(dockerFileTarReader) + if err != nil { + log.Printf("[ERROR] Failed reading Dockerfile TAR reader: %s", err) + } else { + log.Printf("[DEBUG] Dockerfile TAR reader content: %s", string(data)) + } + */ + + // Build the actual image + imageBuildResponse, err := client.ImageBuild( + ctx, + dockerFileTarReader, + buildOptions, + ) + + if err != nil { + return err + } + + // Read the STDOUT from the build process + defer imageBuildResponse.Body.Close() + buildBuf := new(strings.Builder) + _, err = io.Copy(buildBuf, imageBuildResponse.Body) + if err != nil { + return err + } else { + if strings.Contains(buildBuf.String(), "errorDetail") { + log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n")) + return errors.New(fmt.Sprintf("Failed building %s. Check backend logs for details. Most likely means you have an old version of Docker.", strings.Join(tags, ","))) + } + } + return nil } @@ -671,7 +682,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "No image name"}`))) return - + } log.Printf("[INFO] Trying to download image: '%s'. Appname: '%s'. BaseAppname: '%s', Split2: %s", version.Name, appname, baseAppname, appnameSplit2) @@ -870,7 +881,7 @@ func handleRemoteDownloadApp(resp http.ResponseWriter, ctx context.Context, user type tmpapp struct { Success bool `json:"success"` OpenAPI string `json:"openapi"` - App string `json:"app"` + App string `json:"app"` } app := tmpapp{} diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 449a215a..32b30400 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -22,7 +22,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.8.70 + github.com/shuffle/shuffle-shared v0.8.71 golang.org/x/crypto v0.37.0 google.golang.org/api v0.228.0 google.golang.org/grpc v1.71.1 diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index 34ccd9f6..d52c6821 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -341,8 +341,8 @@ github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fc github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shuffle/shuffle-shared v0.8.70 h1:Balbk7kIVUSgM9Lc+VJ09ZpaF18MlFxMPZdkPTD/+gU= -github.com/shuffle/shuffle-shared v0.8.70/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So= +github.com/shuffle/shuffle-shared v0.8.71 h1:OhiBpIEkn+1+uRs4nn9jPtjdT0dVfVl6VQKtaFzUtZY= +github.com/shuffle/shuffle-shared v0.8.71/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 657e4a1f..c78103f5 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -35,9 +35,9 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/storage/memory" gitProxy "github.com/go-git/go-git/v5/plumbing/transport" http2 "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/storage/memory" // Random xj "github.com/basgys/goxml2json" @@ -2078,7 +2078,6 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { resp.WriteHeader(500) resp.Write([]byte(`{"success": false, "reason": "Failed to run workflow. Check logs."}`)) - } func handlePipelineCallback(resp http.ResponseWriter, request *http.Request) { @@ -3095,7 +3094,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s return } - if user.Id == app.Owner || (user.Role == "admin" && user.ActiveOrg.Id == app.ReferenceOrg) || shuffle.ArrayContains(app.Contributors, user.Id) { + if user.Id == app.Owner || (user.Role == "admin" && user.ActiveOrg.Id == app.ReferenceOrg) || shuffle.ArrayContains(app.Contributors, user.Id) { log.Printf("[DEBUG] Editing app %s with user %s (%s) in org %s", test.Id, user.Username, user.Id, user.ActiveOrg.Id) } else { log.Printf("[WARNING] Wrong user (%s) for app %s when verifying swagger", user.Username, app.Name) @@ -3383,7 +3382,6 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User, s } } - log.Printf("[DEBUG] Successfully built app %s (%s)", api.Name, api.ID) if len(user.Id) > 0 { resp.WriteHeader(200) @@ -3826,6 +3824,8 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { } } + // Send stats once every 10 times or so..? + // For now, just send every time info, err := shuffle.GetOrgStatistics(ctx, org.Id) if err != nil { log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err) @@ -4385,7 +4385,7 @@ func runInitEs(ctx context.Context) { } if os.Getenv("SHUFFLE_HEALTHCHECK_DISABLED") != "true" { - healthcheckInterval := 60 + healthcheckInterval := 60 log.Printf("[INFO] Starting healthcheck job every %d minute. Stats available on /api/v1/health/stats, and dashboard on /health. Disable with SHUFFLE_HEALTHCHECK_DISABLED=true", healthcheckInterval) job := func() { // Prepare a fake http.responsewriter From 86ae5dc59da220678740f82dc09326d0329a6141 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 2 Jun 2025 18:15:01 +0200 Subject: [PATCH 13/14] Cloud sync fixes --- backend/go-app/go.mod | 2 +- backend/go-app/main.go | 115 +++++++----- frontend/src/components/Billing.jsx | 73 +++++--- frontend/src/components/BillingStats.jsx | 228 +++++++++++++---------- frontend/src/components/CloudSyncTab.jsx | 8 +- frontend/src/views/Apps2.jsx | 156 ++++++---------- frontend/src/views/Workflows2.jsx | 50 ++++- 7 files changed, 358 insertions(+), 274 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 32b30400..7f30ce38 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -22,7 +22,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/h2non/filetype v1.1.3 github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.8.71 + github.com/shuffle/shuffle-shared v0.8.72 golang.org/x/crypto v0.37.0 google.golang.org/api v0.228.0 google.golang.org/grpc v1.71.1 diff --git a/backend/go-app/main.go b/backend/go-app/main.go index c78103f5..70bf90ee 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -11,17 +11,18 @@ import ( "crypto/md5" "strconv" + "os" + "io" + "log" + "fmt" + "errors" + "net/url" + "os/exec" + "net/http" + "io/ioutil" + "math/rand" "encoding/hex" "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - "os/exec" "net/http/httptest" "strings" @@ -60,7 +61,8 @@ var baseDockerName = "frikky/shuffle" var registryName = "registry.hub.docker.com" var runningEnvironment = "onprem" -var syncUrl = "https://shuffler.io" +//var syncUrl = "https://shuffler.io" +var syncUrl = "http://localhost:5002" type retStruct struct { Success bool `json:"success"` @@ -3805,32 +3807,55 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { } } - if org.SyncConfig.WorkflowBackup { - workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "") - if err != nil { - log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err) - } else { - backupJob.Workflows = workflows - } + shouldBackupData := false + randomNumber := rand.Intn(20) + if randomNumber == 0 { + shouldBackupData = true } - if org.SyncConfig.AppBackup && len(org.Users) > 0 { - - apps, err := shuffle.GetPrioritizedApps(ctx, foundUser) - if err != nil { - log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err) - } else { - backupJob.Apps = apps + // Check if it's 1/20 times (600 seconds - 10 min on average) + // Just to prevent it from spamming large outbound requests + if shouldBackupData { + if org.SyncConfig.WorkflowBackup { + workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "") + if err != nil { + log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err) + } else { + backupJob.Workflows = workflows + } } - } - // Send stats once every 10 times or so..? - // For now, just send every time - info, err := shuffle.GetOrgStatistics(ctx, org.Id) - if err != nil { - log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err) - } else { - backupJob.Stats = *info + if org.SyncConfig.AppBackup && len(org.Users) > 0 { + foundUser.ActiveOrg.Id = org.Id + apps, err := shuffle.GetPrioritizedApps(ctx, foundUser) + if err != nil { + log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err) + } else { + parsedApps := []shuffle.WorkflowApp{} + for _, app := range apps { + if len(app.Actions) == 0 { + continue + } + + if !app.Generated { + continue + } + + parsedApps = append(parsedApps, app) + } + + backupJob.Apps = parsedApps + } + } + + // Send stats once every 10 times or so..? + // For now, just send every time + info, err := shuffle.GetOrgStatistics(ctx, org.Id) + if err != nil { + log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err) + } else { + backupJob.Stats = *info + } } backupJobData, err := json.Marshal(backupJob) @@ -3867,6 +3892,7 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { //log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err) return err } + return nil } @@ -4004,6 +4030,8 @@ func runInitEs(ctx context.Context) { time.Sleep(30 * time.Second) } + // FIXME: This should ONLY run on one backend instance + schedules, err := shuffle.GetAllSchedules(ctx, "ALL") if err != nil { log.Printf("[WARNING] Failed getting schedules during service init: %s", err) @@ -4147,7 +4175,7 @@ func runInitEs(ctx context.Context) { } //interval := int(org.SyncConfig.Interval) - interval := 15 + interval := 30 if interval == 0 { log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id) continue @@ -4249,17 +4277,17 @@ func runInitEs(ctx context.Context) { continue } - if newresp.StatusCode != 200 { - log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d", environment, newresp.StatusCode) + + respBody, err := ioutil.ReadAll(newresp.Body) + if err != nil { + log.Printf("[ERROR] Failed setting respbody %s for execution stop. Status: %d", err, newresp.StatusCode) continue } - //respBody, err := ioutil.ReadAll(newresp.Body) - //if err != nil { - // log.Printf("[ERROR] Failed setting respbody %s", err) - // continue - //} - //log.Printf("[DEBUG] Successfully ran workflow cleanup request for %s. Body: %s", environment, string(respBody)) + if newresp.StatusCode != 200 { + log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d. Body: %s", environment, newresp.StatusCode, string(respBody)) + continue + } url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment) req, err = http.NewRequest( @@ -4677,7 +4705,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { // If you want to disable cloud sync, see previous section. if org.CloudSync { log.Printf("[WARNING] Org %s is already syncing. Skip", org.Id) - resp.WriteHeader(401) + resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Your org is already syncing. Nothing to set up."}`))) return } @@ -4754,6 +4782,9 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { org.SyncConfig = shuffle.SyncConfig{ Apikey: responseData.SessionKey, Interval: responseData.IntervalSeconds, + + WorkflowBackup: true, + AppBackup: true, } interval := int(responseData.IntervalSeconds) diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx index 2e86fd5c..0b9daa6f 100644 --- a/frontend/src/components/Billing.jsx +++ b/frontend/src/components/Billing.jsx @@ -2599,40 +2599,50 @@ const Billing = memo((props) => { Utilization & Stats
- {isChildOrg ? ( - - ): ( - + setCurrentTab(newValue)} + onChange={(event, newValue) => { + setCurrentTab(-1) + + // Force re-render + setTimeout(() => { + setCurrentTab(newValue) + }, 100); + }} style={{ marginTop: 20 }} TabIndicatorProps={{ style: { - height: 3, - backgroundColor: theme.palette.primary.main, - marginLeft: 12, - marginRight: 12, + height: 3, + backgroundColor: theme.palette.primary.main, + marginLeft: 12, + marginRight: 12, } }} > - - + + + {isCloud ? + + : null} + + -
+
{currentTab === 0 ?
{ userdata={userdata} />
+ : currentTab === 1 ? +
+ +
: { /> }
- - )} +
) diff --git a/frontend/src/components/BillingStats.jsx b/frontend/src/components/BillingStats.jsx index 19bda1b8..5bb09ddb 100644 --- a/frontend/src/components/BillingStats.jsx +++ b/frontend/src/components/BillingStats.jsx @@ -84,7 +84,15 @@ const LineChartWrapper = ({keys, inputname, height, width}) => { const AppStats = (defaultprops) => { - const { globalUrl, selectedOrganization, userdata, isCloud, inputWorkflows,clickedFromOrgTab } = defaultprops; + const { + globalUrl, + selectedOrganization, + userdata, + isCloud, + inputWorkflows, + clickedFromOrgTab, + syncStats, + } = defaultprops; const [keys, setKeys] = useState([]) const [searches, setSearches] = useState([]); @@ -119,9 +127,6 @@ const AppStats = (defaultprops) => { const getWorkflowStats = async (workflow, startTime, endTime) => { - if (!userdata.support) { - return workflow - } if (workflow.id === undefined || workflow.id === null || workflow.id === "") { return workflow @@ -186,12 +191,8 @@ const AppStats = (defaultprops) => { } const loadWorkflowStats = (foundWorkflows, startTime, endTime) => { - if (!userdata.support) { - return - } - if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) { - console.log("Not workflows") + setResultLoading(false) return } @@ -200,6 +201,9 @@ const AppStats = (defaultprops) => { const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime)); const allData = Promise.all(promises); + if (allData === undefined || allData === null) { + setResultLoading(false) + } allData.then((data) => { var total = 0 @@ -259,15 +263,16 @@ const AppStats = (defaultprops) => { return } - if (statistics["daily_statistics"] === undefined || statistics["daily_statistics"] === null) { + const statKey = syncStats === true ? "onprem_stats" : "daily_statistics" + if (statistics[statKey] === undefined || statistics[statKey] === null) { setFilteredStatistics(statistics) return } // Calculate month to date cost var mtd_cost = 0 - for (let key in statistics["daily_statistics"]) { - const item = statistics["daily_statistics"][key] + for (let key in statistics[statKey]) { + const item = statistics[statKey][key] if (item["date"] === undefined) { continue } @@ -325,8 +330,8 @@ const AppStats = (defaultprops) => { // Check if start time is before the daily statistics["date"] string var newlist = [] - for (let key in statistics["daily_statistics"]) { - const item = statistics["daily_statistics"][key] + for (let key in statistics[statKey]) { + const item = statistics[statKey][key] if (item["date"] === undefined) { continue } @@ -357,7 +362,7 @@ const AppStats = (defaultprops) => { var appexecutions = 0 var estimatedcost = 0 if (newlist.length > 0) { - tmpstats["daily_statistics"] = newlist + tmpstats[statKey] = newlist for (let key in newlist) { const item = newlist[key] @@ -411,7 +416,8 @@ const AppStats = (defaultprops) => { return } - const dailyStats = inputdata.daily_statistics + const statKey = syncStats === true ? "onprem_stats" : "daily_statistics" + const dailyStats = inputdata[statKey] if (dailyStats === undefined || dailyStats === null) { return } @@ -705,44 +711,57 @@ const AppStats = (defaultprops) => { style={{ textDecoration: "none", color: theme.palette.linkColor,}} >Your Organisation Statistics. It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. The billing tracker is in Beta, and is always calculated manually before being invoiced. + +
+ {syncStats !== true ? null : + "PS: You are currently looking at data from your onprem synced org"}
{filteredStatistics !== undefined ?
- - The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}. - - }> - - - ${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ? - 0 - : - apprunCost - } + + {syncStats == true ? null : + + The cost of app runs in the selected period based on {filteredStatistics.monthly_app_executions} App Runs. These numbers do not exclude your included 10.000/month or {includedExecutions} App Runs per month. App Run cost: ${invocationCost}. - - Period Cost - - - + }> + + + ${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ? + 0 + : + apprunCost + } + + + + Period Cost + + + + } + + {syncStats === true ? null : App runs in the selected period }> - - - {filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions} - - - App Runs - - + + + {filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions} + + + App Runs + + + } + + {syncStats === true ? null : Workflow runs in the selected period @@ -757,20 +776,24 @@ const AppStats = (defaultprops) => { - - Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}. - - }> - - - ${monthTotalCost} + } + + {syncStats === true ? null : + + Estimated cost to be billed at the end of the current month. Subtracted contractually included app runs. Actual cost month to date: ${monthToDateCost}. App Run cost: ${invocationCost}. - - Estimated cost - - - + }> + + + ${monthTotalCost} + + + Estimated cost + + + + }
: null}
@@ -968,57 +991,58 @@ const AppStats = (defaultprops) => { */} + {syncStats === true ? null : +
+ {resultLoading ? +
+ + Loading usage for selected period (may take a while) + + + +
+ : + { + //setRowsPerPage(newPageSize) + //submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize) + }} + // event for when clicking next page + // Hide page changer + onPageChange={(params) => { + console.log("page params: ", params) + }} + onSelectionModelChange={(newSelection) => { + console.log("newSelection: ", newSelection) + //console.log("newSelection: ", newSelection) + //setSelectedWorkflowExecutionsIndexes(newSelection) + //var found = [] + //for (var i = 0; i < newSelection.length; i++) { + // // Find the workflow in the resultRows + // var selected = resultRows.find((workflow) => { + // return workflow.id === newSelection[i] + // }) -
- {resultLoading ? -
- - Loading usage for selected period (may take a while) - - - -
- : - { - //setRowsPerPage(newPageSize) - //submitSearch(workflowId, status, startTime, endTime, rowCursor, newPageSize) - }} - // event for when clicking next page - // Hide page changer - onPageChange={(params) => { - console.log("page params: ", params) - }} - onSelectionModelChange={(newSelection) => { - console.log("newSelection: ", newSelection) - //console.log("newSelection: ", newSelection) - //setSelectedWorkflowExecutionsIndexes(newSelection) - //var found = [] - //for (var i = 0; i < newSelection.length; i++) { - // // Find the workflow in the resultRows - // var selected = resultRows.find((workflow) => { - // return workflow.id === newSelection[i] - // }) + // if (selected === undefined || selected === null) { + // continue + // } - // if (selected === undefined || selected === null) { - // continue - // } + // found.push(selected) + //} - // found.push(selected) - //} - - //setSelectedWorkflowExecutions(found) - }} - // Track which items are selected - /> - } -
+ //setSelectedWorkflowExecutions(found) + }} + // Track which items are selected + /> + } +
+ }
) diff --git a/frontend/src/components/CloudSyncTab.jsx b/frontend/src/components/CloudSyncTab.jsx index dd43bc4a..88df2610 100644 --- a/frontend/src/components/CloudSyncTab.jsx +++ b/frontend/src/components/CloudSyncTab.jsx @@ -483,7 +483,7 @@ const CloudSyncTab = (props) => { } else { toast("Cloud Syncronization successfully set up!"); setOrgSyncResponse( - "Successfully started syncronization. Cloud features you now have access to can be seen below." + "Successfully started syncronization. Cloud/Hybrid features are available below." ); } @@ -541,8 +541,8 @@ const CloudSyncTab = (props) => { Cloud syncronization - What does cloud sync do? Cloud synchronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach. - + What does cloud sync do? Cloud synchronization is a way of getting more out of Shuffle. Shuffle will ALWAYS make every option open source, but features relying on other users can't be done without a collaborative approach. This will by default back up apps and workflows. +
{isCloud ? ( @@ -714,7 +714,7 @@ const CloudSyncTab = (props) => { )} - Features + {isCloud ? "Cloud" : "Hybrid"} Features Features and Limitations that are currently available to you in your Cloud or Hybrid Organization. App Executions (App Runs) reset monthly. If the organization is a customer or in a trial, these features limitations are not always enforced. diff --git a/frontend/src/views/Apps2.jsx b/frontend/src/views/Apps2.jsx index 6b062800..8c8e4284 100644 --- a/frontend/src/views/Apps2.jsx +++ b/frontend/src/views/Apps2.jsx @@ -1122,6 +1122,7 @@ const Apps2 = (props) => { const [defaultSearch, setDefaultSearch] = useState(""); const [apps, setApps] = useState([]); + const [backupApps, setBackupApps] = useState([]); const [filteredApps, setFilteredApps] = useState([]); const [appSearchLoading, setAppSearchLoading] = useState(false); const [creatorProfile, setCreatorProfile] = useState({}); @@ -1198,57 +1199,9 @@ const Apps2 = (props) => { getFramework(); }, []); - // Fetch apps based on the current tab : 0 -> org_apps, 1 -> my_apps, 2 -> all_apps - const fetchApps = async () => { - const baseUrl = globalUrl; - let url; - setIsLoading(true); - const userId = userdata?.id; - if (currTab === 1 && userId) { - url = `${baseUrl}/api/v1/users/${userId}/apps`; - } else if (currTab === 0) { - url = `${baseUrl}/api/v1/apps`; - } - try { - const response = await fetch(url, { - method: "GET", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }); - const data = await response.json(); - if (currTab === 1) { - setAppsToShow(data); - setUserApps(data); - } else if (currTab === 0) { - setAppsToShow(data); - setOrgApps(data); - // For testing the empty state - // setAppsToShow([]); - // setOrgApps([]); - } - setIsLoading(false); - } catch (err) { - console.error("Error fetching apps:", err); - setIsLoading(false); - } - }; - useEffect(() => { - - // Only fetch if we have required data - if (globalUrl && (currTab === 0 || (currTab === 1 && userdata?.id))) { - fetchApps(); - } - }, [currTab, globalUrl, userdata?.id]); // Remove location.search dependency - - // useEffect(() => { - // // setSearchQuery(""); - // setSelectedCategory([]); - // setSelectedLabel([]); - // }, [currTab]) - + getApps() + }, []) // Find top categories and tags based on the current tab useEffect(() => { @@ -1293,11 +1246,13 @@ const Apps2 = (props) => { }); }; + /* useEffect(() => { if (serverside) { return null; } }, [serverside]); + */ const getApps = () => { // Get apps from localstorage @@ -1308,7 +1263,7 @@ const Apps2 = (props) => { if (storageApps === null || storageApps === undefined || storageApps.length === 0) { storageApps = [] } else { - setAppsToShow(storageApps) + //setAppsToShow(storageApps) setOrgApps(storageApps) setApps(storageApps) // setFilteredApps(storageApps) @@ -1344,18 +1299,25 @@ const Apps2 = (props) => { var privateapps = []; var valid = []; var invalid = []; + + var backups = [] for (var key in responseJson) { const app = responseJson[key]; - if (app.categories !== undefined && app.categories !== null && app?.categories.includes("Eradication")) { + if (app?.reference_info?.onprem_backup === true) { + backups.push(app) + continue + } + + if (app?.categories !== undefined && app?.categories !== null && app?.categories?.includes("Eradication")) { app.categories = ["EDR"] } - if (app.is_valid && !(!app.activated && app.generated)) { + if (app?.is_valid && !(!app?.activated && app?.generated)) { privateapps.push(app); } else if ( - app.private_id !== undefined && - app.private_id.length > 0 + app?.private_id !== undefined && + app?.private_id.length > 0 ) { valid.push(app); } else { @@ -1363,6 +1325,11 @@ const Apps2 = (props) => { } } + console.log("BACKUPAPPS: ", backups) + if (backups.length > 0) { + setBackupApps(backups) + } + privateapps.push(...valid); privateapps.push(...invalid); console.log("privateapps: setting apps ", privateapps) @@ -1372,39 +1339,22 @@ const Apps2 = (props) => { // setFilteredApps(privateapps); if (privateapps.length > 0) { - if (selectedApp.id === undefined || selectedApp.id === null) { - if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) { - getUserProfile(privateapps[0].owner); + if (selectedApp?.id === undefined || selectedApp?.id === null) { + if (privateapps[0]?.owner !== undefined && privateapps[0]?.owner !== null) { + getUserProfile(privateapps[0]?.owner); } - - // setContact(privateapps[0].contact_info) - - // setSelectedApp(privateapps[0]); - // setSharingConfiguration(privateapps[0].sharing === true ? "public" : "you") } - - // if ( - // privateapps[0].actions !== null && - // privateapps[0].actions.length > 0 - // ) { - // setSelectedAction(privateapps[0].actions[0]); - // } else { - // setSelectedAction({}); - // } } - if (privateapps.length > 0 && storageApps.length === 0) { + if (privateapps?.length > 0 && storageApps?.length === 0) { try { localStorage.setItem("apps", JSON.stringify(privateapps)) } catch (e) { console.log("Failed to set apps in localstorage: ", e) } } - - //setTimeout(() => { - // setFirstLoad(false) - //}, 5000) }) .catch((error) => { + console.log("Failed to get apps: ", error.toString()); toast(error.toString()); setIsLoading(false); }); @@ -1780,7 +1730,6 @@ const Apps2 = (props) => { // setOpenModal(true); }; - useEffect(() => { const apps = currTab === 1 ? userApps : orgApps; const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel); @@ -1798,33 +1747,38 @@ const Apps2 = (props) => { } else if (newTab === 1) { const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel); setAppsToShow(filteredUserApps); - } + } else if (newTab === 3) { + const filteredUserApps = filterApps(backupApps, searchQuery, selectedCategory, selectedLabel); + setAppsToShow(filteredUserApps); + return + } // Update URL query params based on tab index const tabMapping = { 0: 'org_apps', 1: 'my_apps', - 2: 'all_apps' + 2: 'all_apps', + 3: 'backup_apps', }; - const queryParams = new URLSearchParams(location.search); - queryParams.set('tab', tabMapping[newTab]); + const queryParams = new URLSearchParams(location.search); + queryParams.set('tab', tabMapping[newTab]); - // Maintain search query in URL regardless of tab - if (searchQuery) { - queryParams.set('q', searchQuery); - } else { - queryParams.delete('q'); - } + // Maintain search query in URL regardless of tab + if (searchQuery) { + queryParams.set('q', searchQuery); + } else { + queryParams.delete('q'); + } - navigate(`${location.pathname}?${queryParams.toString()}`); + navigate(`${location.pathname}?${queryParams.toString()}`); }; // Update useEffect for filtering without URL manipulation useEffect(() => { if (currTab === 2) return; // Skip for "Discover Apps" tab as it uses Algolia - const apps = currTab === 1 ? userApps : orgApps; + const apps = currTab === 1 ? userApps : currTab === 3 ? backupApps : orgApps; const filteredApps = filterApps(apps, searchQuery, selectedCategory, selectedLabel); setAppsToShow(filteredApps); }, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]); @@ -1914,7 +1868,7 @@ const Apps2 = (props) => {
- {currTab === 0 ? "Org" : currTab === 1 ? "Your" : "Discover"} Apps + {currTab === 0 ? "Org" : currTab === 1 ? "Your" : currTab === 3 ? "Backup" : "Discover"} Apps {isCloud ? null : ( @@ -2027,6 +1981,7 @@ const Apps2 = (props) => { ...(currTab === 1 ? tabActive : {}) }} /> + { ...(currTab === 2 ? tabActive : {}) }} /> + + {backupApps.length > 0 && + + }
@@ -2043,7 +2009,7 @@ const Apps2 = (props) => { minWidth: "25%", maxWidth: "25%" }}> - {(currTab === 0 || currTab === 1) ? ( + {(currTab === 0 || currTab === 1 || currTab === 3) ? ( {
{ - currTab === 0 && ( + currTab === 0 || currTab === 3 && (
{isLoading ? ( @@ -2285,7 +2251,7 @@ const Apps2 = (props) => { handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick} userdata={userdata} - fetchApps={fetchApps} + fetchApps={getApps} setUserApps={setUserApps} appsToShow={appsToShow} @@ -2338,7 +2304,7 @@ const Apps2 = (props) => { {appsToShow.map((data, index) => ( { var upload = ""; const [workflows, setWorkflows] = React.useState([]); + const [backupWorkflows, setBackupWorkflows] = React.useState([]); const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove const [selectedUsecases, setSelectedUsecases] = React.useState([]); const [filteredWorkflows, setFilteredWorkflows] = React.useState([]); @@ -756,7 +757,8 @@ const Workflows2 = (props) => { const tabMapping = { 0: 'org_workflows', 1: 'my_workflows', - 2: 'all_workflows' + 2: 'all_workflows', + 3: 'backup_apps', }; const queryParams = new URLSearchParams(location.search); queryParams.set('tab', tabMapping[newValue]); @@ -1357,15 +1359,25 @@ const Workflows2 = (props) => { } var newarray = [] + var backupWf = [] for (var wfkey in responseJson) { const wf = responseJson[wfkey] if (wf.public === true || wf.hidden === true) { continue } + if (wf?.backup_config?.onprem_backup === true) { + backupWf.push(wf) + continue + } + newarray.push(wf) } + if (backupWf.length > 0) { + setBackupWorkflows(backupWf) + } + var setProdFilter = false var actionnamelist = []; @@ -4332,6 +4344,17 @@ const Workflows2 = (props) => { }} /> + {backupWorkflows.length > 0 && + + } + { @@ -4341,7 +4364,7 @@ const Workflows2 = (props) => { ...tabStyle, marginRight: 0, marginLeft: 25, - ...(currTab === 3 ? tabActive : {}) + ...(currTab === 4 ? tabActive : {}) }} /> @@ -4676,8 +4699,6 @@ const Workflows2 = (props) => { paddingBottom: 40 }}> - - {currTab === 0 && orgWorkflows.map((data, index) => { // Shouldn't be a part of this list if (data.public === true) { @@ -4699,6 +4720,27 @@ const Workflows2 = (props) => { ) })} + {currTab === 3 && backupWorkflows.map((data, index) => { + // Shouldn't be a part of this list + if (data.public === true) { + return null + } + + // if (firstLoad) { + // workflowDelay += 75 + // } else { + // return + // } + + return ( + + {/**/} + + {/**/} + + ) + })} + { currTab === 1 && myWorkflows.map((data, index) => { if (data.public === true) { From 4a867d9f1b5a61581f89c4e30b5d8a97bfaf7ebf Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 2 Jun 2025 22:45:07 +0200 Subject: [PATCH 14/14] Repush with proper sync url --- backend/go-app/go.sum | 4 ++-- backend/go-app/main.go | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/go-app/go.sum b/backend/go-app/go.sum index d52c6821..154bcdde 100644 --- a/backend/go-app/go.sum +++ b/backend/go-app/go.sum @@ -341,8 +341,8 @@ github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fc github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shuffle/shuffle-shared v0.8.71 h1:OhiBpIEkn+1+uRs4nn9jPtjdT0dVfVl6VQKtaFzUtZY= -github.com/shuffle/shuffle-shared v0.8.71/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So= +github.com/shuffle/shuffle-shared v0.8.72 h1:HVOsRt83/1k9P+8q1FAxXnDKyROoDAFa1A3MnoRJYb0= +github.com/shuffle/shuffle-shared v0.8.72/go.mod h1:OLAwH/Ym4941Jn5DF1oZaq6iBpmjG2SNrTZ9Xqck5So= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 70bf90ee..eaab40da 100755 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -61,8 +61,8 @@ var baseDockerName = "frikky/shuffle" var registryName = "registry.hub.docker.com" var runningEnvironment = "onprem" -//var syncUrl = "https://shuffler.io" -var syncUrl = "http://localhost:5002" +var syncUrl = "https://shuffler.io" +//var syncUrl = "http://localhost:5002" type retStruct struct { Success bool `json:"success"` @@ -3807,13 +3807,14 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error { } } + // Check if it's 1/20 times (600 seconds - 10 min on average) + // Only problem: May take time to sync the first time, which is annoying shouldBackupData := false randomNumber := rand.Intn(20) if randomNumber == 0 { shouldBackupData = true } - // Check if it's 1/20 times (600 seconds - 10 min on average) // Just to prevent it from spamming large outbound requests if shouldBackupData { if org.SyncConfig.WorkflowBackup { @@ -4285,7 +4286,10 @@ func runInitEs(ctx context.Context) { } if newresp.StatusCode != 200 { - log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d. Body: %s", environment, newresp.StatusCode, string(respBody)) + if !strings.Contains(string(respBody), "is active") { + log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d. Body: %s", environment, newresp.StatusCode, string(respBody)) + } + continue }