From 86ae5dc59da220678740f82dc09326d0329a6141 Mon Sep 17 00:00:00 2001 From: Frikky Date: Mon, 2 Jun 2025 18:15:01 +0200 Subject: [PATCH] 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) {