Cloud sync fixes
This commit is contained in:
@@ -22,7 +22,7 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/satori/go.uuid v1.2.0
|
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
|
golang.org/x/crypto v0.37.0
|
||||||
google.golang.org/api v0.228.0
|
google.golang.org/api v0.228.0
|
||||||
google.golang.org/grpc v1.71.1
|
google.golang.org/grpc v1.71.1
|
||||||
|
|||||||
+73
-42
@@ -11,17 +11,18 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"fmt"
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"os/exec"
|
||||||
|
"net/http"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -60,7 +61,8 @@ var baseDockerName = "frikky/shuffle"
|
|||||||
var registryName = "registry.hub.docker.com"
|
var registryName = "registry.hub.docker.com"
|
||||||
var runningEnvironment = "onprem"
|
var runningEnvironment = "onprem"
|
||||||
|
|
||||||
var syncUrl = "https://shuffler.io"
|
//var syncUrl = "https://shuffler.io"
|
||||||
|
var syncUrl = "http://localhost:5002"
|
||||||
|
|
||||||
type retStruct struct {
|
type retStruct struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
@@ -3805,32 +3807,55 @@ func remoteOrgJobHandler(org shuffle.Org, interval int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if org.SyncConfig.WorkflowBackup {
|
shouldBackupData := false
|
||||||
workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "")
|
randomNumber := rand.Intn(20)
|
||||||
if err != nil {
|
if randomNumber == 0 {
|
||||||
log.Printf("[ERROR] Failed getting backup workflows for org %s: %s", org.Id, err)
|
shouldBackupData = true
|
||||||
} else {
|
|
||||||
backupJob.Workflows = workflows
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if org.SyncConfig.AppBackup && len(org.Users) > 0 {
|
// Check if it's 1/20 times (600 seconds - 10 min on average)
|
||||||
|
// Just to prevent it from spamming large outbound requests
|
||||||
apps, err := shuffle.GetPrioritizedApps(ctx, foundUser)
|
if shouldBackupData {
|
||||||
if err != nil {
|
if org.SyncConfig.WorkflowBackup {
|
||||||
log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err)
|
workflows, err := shuffle.GetAllWorkflowsByQuery(ctx, foundUser, 250, "")
|
||||||
} else {
|
if err != nil {
|
||||||
backupJob.Apps = apps
|
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..?
|
if org.SyncConfig.AppBackup && len(org.Users) > 0 {
|
||||||
// For now, just send every time
|
foundUser.ActiveOrg.Id = org.Id
|
||||||
info, err := shuffle.GetOrgStatistics(ctx, org.Id)
|
apps, err := shuffle.GetPrioritizedApps(ctx, foundUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed getting org statistics backup for org %s: %s", org.Id, err)
|
log.Printf("[ERROR] Failed getting backup apps for org %s: %s", org.Id, err)
|
||||||
} else {
|
} else {
|
||||||
backupJob.Stats = *info
|
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)
|
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)
|
//log.Printf("[ERROR] Failed cloud sync job controller run for '%s': %s", respBody, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4004,6 +4030,8 @@ func runInitEs(ctx context.Context) {
|
|||||||
time.Sleep(30 * time.Second)
|
time.Sleep(30 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: This should ONLY run on one backend instance
|
||||||
|
|
||||||
schedules, err := shuffle.GetAllSchedules(ctx, "ALL")
|
schedules, err := shuffle.GetAllSchedules(ctx, "ALL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARNING] Failed getting schedules during service init: %s", err)
|
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 := int(org.SyncConfig.Interval)
|
||||||
interval := 15
|
interval := 30
|
||||||
if interval == 0 {
|
if interval == 0 {
|
||||||
log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id)
|
log.Printf("[WARNING] Skipping org %s because sync isn't set (0).", org.Id)
|
||||||
continue
|
continue
|
||||||
@@ -4249,17 +4277,17 @@ func runInitEs(ctx context.Context) {
|
|||||||
continue
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//respBody, err := ioutil.ReadAll(newresp.Body)
|
if newresp.StatusCode != 200 {
|
||||||
//if err != nil {
|
log.Printf("[WARNING] Failed stopping runs in environment %s. Status code: %d. Body: %s", environment, newresp.StatusCode, string(respBody))
|
||||||
// log.Printf("[ERROR] Failed setting respbody %s", err)
|
continue
|
||||||
// continue
|
}
|
||||||
//}
|
|
||||||
//log.Printf("[DEBUG] Successfully ran workflow cleanup request for %s. Body: %s", environment, string(respBody))
|
|
||||||
|
|
||||||
url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment)
|
url = fmt.Sprintf("http://localhost:%s/api/v1/environments/%s/rerun", backendPort, environment)
|
||||||
req, err = http.NewRequest(
|
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 you want to disable cloud sync, see previous section.
|
||||||
if org.CloudSync {
|
if org.CloudSync {
|
||||||
log.Printf("[WARNING] Org %s is already syncing. Skip", org.Id)
|
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."}`)))
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Your org is already syncing. Nothing to set up."}`)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -4754,6 +4782,9 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
|||||||
org.SyncConfig = shuffle.SyncConfig{
|
org.SyncConfig = shuffle.SyncConfig{
|
||||||
Apikey: responseData.SessionKey,
|
Apikey: responseData.SessionKey,
|
||||||
Interval: responseData.IntervalSeconds,
|
Interval: responseData.IntervalSeconds,
|
||||||
|
|
||||||
|
WorkflowBackup: true,
|
||||||
|
AppBackup: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
interval := int(responseData.IntervalSeconds)
|
interval := int(responseData.IntervalSeconds)
|
||||||
|
|||||||
@@ -2599,40 +2599,50 @@ const Billing = memo((props) => {
|
|||||||
Utilization & Stats
|
Utilization & Stats
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
{isChildOrg ? (
|
<span>
|
||||||
<BillingStats
|
|
||||||
isCloud={isCloud}
|
|
||||||
clickedFromOrgTab={clickedFromOrgTab}
|
|
||||||
globalUrl={globalUrl}
|
|
||||||
selectedOrganization={selectedOrganization}
|
|
||||||
userdata={userdata}
|
|
||||||
/>
|
|
||||||
): (
|
|
||||||
<span>
|
|
||||||
<Tabs
|
<Tabs
|
||||||
value={currentTab}
|
value={currentTab}
|
||||||
onChange={(event, newValue) => setCurrentTab(newValue)}
|
onChange={(event, newValue) => {
|
||||||
|
setCurrentTab(-1)
|
||||||
|
|
||||||
|
// Force re-render
|
||||||
|
setTimeout(() => {
|
||||||
|
setCurrentTab(newValue)
|
||||||
|
}, 100);
|
||||||
|
}}
|
||||||
style={{ marginTop: 20 }}
|
style={{ marginTop: 20 }}
|
||||||
TabIndicatorProps={{
|
TabIndicatorProps={{
|
||||||
style: {
|
style: {
|
||||||
height: 3,
|
height: 3,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
marginLeft: 12,
|
marginLeft: 12,
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tab
|
<Tab
|
||||||
label="Parent Organization"
|
label="Parent Organization"
|
||||||
style={{ textTransform: 'none', fontSize: 16, minWidth: 'auto', paddingLeft: 12, paddingRight: 12 }}
|
style={{ textTransform: 'none',}}
|
||||||
/>
|
value={0}
|
||||||
<Tab
|
/>
|
||||||
label="Child Organization Stats"
|
|
||||||
style={{ textTransform: 'none', fontSize: 16, minWidth: 'auto', paddingLeft: 12, paddingRight: 12 }}
|
{isCloud ?
|
||||||
/>
|
<Tab
|
||||||
|
label="Cloud-Synced Stats"
|
||||||
|
style={{ textTransform: 'none', }}
|
||||||
|
value={1}
|
||||||
|
/>
|
||||||
|
: null}
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
label="Child Organization Stats"
|
||||||
|
disabled={isChildOrg}
|
||||||
|
style={{ textTransform: 'none', }}
|
||||||
|
value={2}
|
||||||
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div style={{paddingBottom: 200, }}>
|
<div style={{paddingBottom: 200, minHeight: 750, }}>
|
||||||
{currentTab === 0 ?
|
{currentTab === 0 ?
|
||||||
<div style={{ marginTop: 30,}}>
|
<div style={{ marginTop: 30,}}>
|
||||||
<BillingStats
|
<BillingStats
|
||||||
@@ -2643,6 +2653,18 @@ const Billing = memo((props) => {
|
|||||||
userdata={userdata}
|
userdata={userdata}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
: currentTab === 1 ?
|
||||||
|
<div style={{ marginTop: 30,}}>
|
||||||
|
<BillingStats
|
||||||
|
isCloud={isCloud}
|
||||||
|
clickedFromOrgTab={clickedFromOrgTab}
|
||||||
|
globalUrl={globalUrl}
|
||||||
|
selectedOrganization={selectedOrganization}
|
||||||
|
userdata={userdata}
|
||||||
|
|
||||||
|
syncStats={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
:
|
:
|
||||||
<BillingStatsChildOrg
|
<BillingStatsChildOrg
|
||||||
isCloud={isCloud}
|
isCloud={isCloud}
|
||||||
@@ -2657,8 +2679,7 @@ const Billing = memo((props) => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -84,7 +84,15 @@ const LineChartWrapper = ({keys, inputname, height, width}) => {
|
|||||||
|
|
||||||
|
|
||||||
const AppStats = (defaultprops) => {
|
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 [keys, setKeys] = useState([])
|
||||||
const [searches, setSearches] = useState([]);
|
const [searches, setSearches] = useState([]);
|
||||||
@@ -119,9 +127,6 @@ const AppStats = (defaultprops) => {
|
|||||||
|
|
||||||
|
|
||||||
const getWorkflowStats = async (workflow, startTime, endTime) => {
|
const getWorkflowStats = async (workflow, startTime, endTime) => {
|
||||||
if (!userdata.support) {
|
|
||||||
return workflow
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workflow.id === undefined || workflow.id === null || workflow.id === "") {
|
if (workflow.id === undefined || workflow.id === null || workflow.id === "") {
|
||||||
return workflow
|
return workflow
|
||||||
@@ -186,12 +191,8 @@ const AppStats = (defaultprops) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadWorkflowStats = (foundWorkflows, startTime, endTime) => {
|
const loadWorkflowStats = (foundWorkflows, startTime, endTime) => {
|
||||||
if (!userdata.support) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) {
|
if (foundWorkflows === undefined || foundWorkflows === null || foundWorkflows.length === 0) {
|
||||||
console.log("Not workflows")
|
setResultLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +201,9 @@ const AppStats = (defaultprops) => {
|
|||||||
const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime));
|
const promises = foundWorkflows.slice(0, 50).map(wf => getWorkflowStats(wf, startTime, endTime));
|
||||||
|
|
||||||
const allData = Promise.all(promises);
|
const allData = Promise.all(promises);
|
||||||
|
if (allData === undefined || allData === null) {
|
||||||
|
setResultLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
allData.then((data) => {
|
allData.then((data) => {
|
||||||
var total = 0
|
var total = 0
|
||||||
@@ -259,15 +263,16 @@ const AppStats = (defaultprops) => {
|
|||||||
return
|
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)
|
setFilteredStatistics(statistics)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate month to date cost
|
// Calculate month to date cost
|
||||||
var mtd_cost = 0
|
var mtd_cost = 0
|
||||||
for (let key in statistics["daily_statistics"]) {
|
for (let key in statistics[statKey]) {
|
||||||
const item = statistics["daily_statistics"][key]
|
const item = statistics[statKey][key]
|
||||||
if (item["date"] === undefined) {
|
if (item["date"] === undefined) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -325,8 +330,8 @@ const AppStats = (defaultprops) => {
|
|||||||
|
|
||||||
// Check if start time is before the daily statistics["date"] string
|
// Check if start time is before the daily statistics["date"] string
|
||||||
var newlist = []
|
var newlist = []
|
||||||
for (let key in statistics["daily_statistics"]) {
|
for (let key in statistics[statKey]) {
|
||||||
const item = statistics["daily_statistics"][key]
|
const item = statistics[statKey][key]
|
||||||
if (item["date"] === undefined) {
|
if (item["date"] === undefined) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -357,7 +362,7 @@ const AppStats = (defaultprops) => {
|
|||||||
var appexecutions = 0
|
var appexecutions = 0
|
||||||
var estimatedcost = 0
|
var estimatedcost = 0
|
||||||
if (newlist.length > 0) {
|
if (newlist.length > 0) {
|
||||||
tmpstats["daily_statistics"] = newlist
|
tmpstats[statKey] = newlist
|
||||||
|
|
||||||
for (let key in newlist) {
|
for (let key in newlist) {
|
||||||
const item = newlist[key]
|
const item = newlist[key]
|
||||||
@@ -411,7 +416,8 @@ const AppStats = (defaultprops) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const dailyStats = inputdata.daily_statistics
|
const statKey = syncStats === true ? "onprem_stats" : "daily_statistics"
|
||||||
|
const dailyStats = inputdata[statKey]
|
||||||
if (dailyStats === undefined || dailyStats === null) {
|
if (dailyStats === undefined || dailyStats === null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -705,44 +711,57 @@ const AppStats = (defaultprops) => {
|
|||||||
style={{ textDecoration: "none", color: theme.palette.linkColor,}}
|
style={{ textDecoration: "none", color: theme.palette.linkColor,}}
|
||||||
>Your Organisation Statistics. </a>
|
>Your Organisation Statistics. </a>
|
||||||
It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. <b>The billing tracker is in Beta, and is always calculated manually before being invoiced.</b>
|
It exists to give you more insight into your workflows, and to understand your utilization of the Shuffle platform. <b>The billing tracker is in Beta, and is always calculated manually before being invoiced.</b>
|
||||||
|
|
||||||
|
<br style={{}}/>
|
||||||
|
{syncStats !== true ? null :
|
||||||
|
"PS: You are currently looking at data from your onprem synced org"}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<div style={{display: "flex", flexDirection: "column", textAlign: "center",}}>
|
<div style={{display: "flex", flexDirection: "column", textAlign: "center",}}>
|
||||||
<div style={{flexDirection: "row", }}>
|
<div style={{flexDirection: "row", }}>
|
||||||
{filteredStatistics !== undefined ?
|
{filteredStatistics !== undefined ?
|
||||||
<div style={{flex: 1, display: "flex", textAlign: "center",}}>
|
<div style={{flex: 1, display: "flex", textAlign: "center",}}>
|
||||||
<Tooltip title={
|
|
||||||
<Typography variant="body1" style={{padding: 10, }}>
|
{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}.
|
<Tooltip title={
|
||||||
</Typography>
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
}>
|
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}.
|
||||||
<Box sx={paperStyle}>
|
|
||||||
<Typography variant="h4">
|
|
||||||
${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ?
|
|
||||||
0
|
|
||||||
:
|
|
||||||
apprunCost
|
|
||||||
}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h6">
|
}>
|
||||||
Period Cost
|
<Box sx={paperStyle}>
|
||||||
</Typography>
|
<Typography variant="h4">
|
||||||
</Box>
|
${selectedOrganization?.lead_info?.customer === false && selectedOrganization?.lead_info?.pov === false ?
|
||||||
</Tooltip>
|
0
|
||||||
|
:
|
||||||
|
apprunCost
|
||||||
|
}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography variant="h6">
|
||||||
|
Period Cost
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
|
||||||
|
{syncStats === true ? null :
|
||||||
<Tooltip title={
|
<Tooltip title={
|
||||||
<Typography variant="body1" style={{padding: 10, }}>
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
App runs in the selected period
|
App runs in the selected period
|
||||||
</Typography>
|
</Typography>
|
||||||
}>
|
}>
|
||||||
<Box sx={paperStyle}>
|
<Box sx={paperStyle}>
|
||||||
<Typography variant="h4">
|
<Typography variant="h4">
|
||||||
{filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions}
|
{filteredStatistics.monthly_app_executions === null || filteredStatistics.monthly_app_executions === undefined ? 0 : filteredStatistics.monthly_app_executions}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
App Runs
|
App Runs
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
}
|
||||||
|
|
||||||
|
{syncStats === true ? null :
|
||||||
<Tooltip title={
|
<Tooltip title={
|
||||||
<Typography variant="body1" style={{padding: 10, }}>
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
Workflow runs in the selected period
|
Workflow runs in the selected period
|
||||||
@@ -757,20 +776,24 @@ const AppStats = (defaultprops) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={
|
}
|
||||||
<Typography variant="body1" style={{padding: 10, }}>
|
|
||||||
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}.
|
{syncStats === true ? null :
|
||||||
</Typography>
|
<Tooltip title={
|
||||||
}>
|
<Typography variant="body1" style={{padding: 10, }}>
|
||||||
<Box sx={paperStyle}>
|
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}.
|
||||||
<Typography variant="h4">
|
|
||||||
${monthTotalCost}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h6">
|
}>
|
||||||
Estimated cost
|
<Box sx={paperStyle}>
|
||||||
</Typography>
|
<Typography variant="h4">
|
||||||
</Box>
|
${monthTotalCost}
|
||||||
</Tooltip>
|
</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
Estimated cost
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
@@ -968,57 +991,58 @@ const AppStats = (defaultprops) => {
|
|||||||
<LineChartWrapper keys={appRunCosts} height={300} width={"100%"} inputname={"Apprun cost - Cost per day"}/>
|
<LineChartWrapper keys={appRunCosts} height={300} width={"100%"} inputname={"Apprun cost - Cost per day"}/>
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
|
{syncStats === true ? null :
|
||||||
|
<div style={{height: 150+resultRows.length * 25, padding: "10px 0px 10px 0px", }}>
|
||||||
|
{resultLoading ?
|
||||||
|
<div style={{margin: "auto", alignItems: "center", width: 350, height: "100%", }}>
|
||||||
|
<Typography variant="body2" color="textSecondary" component="p" style={{textAlign: "center", marginTop: 50, marginBottom: 15, }}>
|
||||||
|
Loading usage for selected period (may take a while)
|
||||||
|
|
||||||
|
<CircularProgress style={{marginTop: 15, }} />
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<DataGrid
|
||||||
|
rows={resultRows}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={100}
|
||||||
|
rowsPerPageOptions={[10, 20, 50, 100]}
|
||||||
|
checkboxSelection
|
||||||
|
disableSelectionOnClick
|
||||||
|
onPageSizeChange={(newPageSize) => {
|
||||||
|
//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]
|
||||||
|
// })
|
||||||
|
|
||||||
<div style={{height: 150+resultRows.length * 25, padding: "10px 0px 10px 0px", }}>
|
// if (selected === undefined || selected === null) {
|
||||||
{resultLoading ?
|
// continue
|
||||||
<div style={{margin: "auto", alignItems: "center", width: 350, height: "100%", }}>
|
// }
|
||||||
<Typography variant="body2" color="textSecondary" component="p" style={{textAlign: "center", marginTop: 50, marginBottom: 15, }}>
|
|
||||||
Loading usage for selected period (may take a while)
|
|
||||||
|
|
||||||
<CircularProgress style={{marginTop: 15, }} />
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
:
|
|
||||||
<DataGrid
|
|
||||||
rows={resultRows}
|
|
||||||
columns={columns}
|
|
||||||
pageSize={100}
|
|
||||||
rowsPerPageOptions={[10, 20, 50, 100]}
|
|
||||||
checkboxSelection
|
|
||||||
disableSelectionOnClick
|
|
||||||
onPageSizeChange={(newPageSize) => {
|
|
||||||
//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) {
|
// found.push(selected)
|
||||||
// continue
|
//}
|
||||||
// }
|
|
||||||
|
|
||||||
// found.push(selected)
|
//setSelectedWorkflowExecutions(found)
|
||||||
//}
|
}}
|
||||||
|
// Track which items are selected
|
||||||
//setSelectedWorkflowExecutions(found)
|
/>
|
||||||
}}
|
}
|
||||||
// Track which items are selected
|
</div>
|
||||||
/>
|
}
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -483,7 +483,7 @@ const CloudSyncTab = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
toast("Cloud Syncronization successfully set up!");
|
toast("Cloud Syncronization successfully set up!");
|
||||||
setOrgSyncResponse(
|
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
|
Cloud syncronization
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16, fontWeight: 400, }}>
|
<Typography variant="body2" style={{ color: theme.palette.text.secondary, fontSize: 16, fontWeight: 400, }}>
|
||||||
What does <a href="/docs/organizations#cloud_sync" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.linkColor, fontSize: 16, textDecoration: 'none', }}>cloud sync</a> 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 <a href="/docs/organizations#cloud_sync" target="_blank" rel="noopener noreferrer" style={{ color: theme.palette.linkColor, fontSize: 16, textDecoration: 'none', }}>cloud sync</a> 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.
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isCloud ? (
|
{isCloud ? (
|
||||||
@@ -714,7 +714,7 @@ const CloudSyncTab = (props) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Typography variant="h5" style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
|
<Typography variant="h5" style={{ marginLeft: 5, marginTop: 40, marginBottom: 5 }}>
|
||||||
Features
|
{isCloud ? "Cloud" : "Hybrid"} Features
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: theme.palette.text.secondary }}>
|
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 24, fontSize: 16, fontWeight: 400, marginLeft: 5, color: theme.palette.text.secondary }}>
|
||||||
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. </Typography>
|
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. </Typography>
|
||||||
|
|||||||
@@ -1122,6 +1122,7 @@ const Apps2 = (props) => {
|
|||||||
const [defaultSearch, setDefaultSearch] = useState("");
|
const [defaultSearch, setDefaultSearch] = useState("");
|
||||||
|
|
||||||
const [apps, setApps] = useState([]);
|
const [apps, setApps] = useState([]);
|
||||||
|
const [backupApps, setBackupApps] = useState([]);
|
||||||
const [filteredApps, setFilteredApps] = useState([]);
|
const [filteredApps, setFilteredApps] = useState([]);
|
||||||
const [appSearchLoading, setAppSearchLoading] = useState(false);
|
const [appSearchLoading, setAppSearchLoading] = useState(false);
|
||||||
const [creatorProfile, setCreatorProfile] = useState({});
|
const [creatorProfile, setCreatorProfile] = useState({});
|
||||||
@@ -1198,57 +1199,9 @@ const Apps2 = (props) => {
|
|||||||
getFramework();
|
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(() => {
|
useEffect(() => {
|
||||||
|
getApps()
|
||||||
// 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])
|
|
||||||
|
|
||||||
|
|
||||||
// Find top categories and tags based on the current tab
|
// Find top categories and tags based on the current tab
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1293,11 +1246,13 @@ const Apps2 = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (serverside) {
|
if (serverside) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [serverside]);
|
}, [serverside]);
|
||||||
|
*/
|
||||||
|
|
||||||
const getApps = () => {
|
const getApps = () => {
|
||||||
// Get apps from localstorage
|
// Get apps from localstorage
|
||||||
@@ -1308,7 +1263,7 @@ const Apps2 = (props) => {
|
|||||||
if (storageApps === null || storageApps === undefined || storageApps.length === 0) {
|
if (storageApps === null || storageApps === undefined || storageApps.length === 0) {
|
||||||
storageApps = []
|
storageApps = []
|
||||||
} else {
|
} else {
|
||||||
setAppsToShow(storageApps)
|
//setAppsToShow(storageApps)
|
||||||
setOrgApps(storageApps)
|
setOrgApps(storageApps)
|
||||||
setApps(storageApps)
|
setApps(storageApps)
|
||||||
// setFilteredApps(storageApps)
|
// setFilteredApps(storageApps)
|
||||||
@@ -1344,18 +1299,25 @@ const Apps2 = (props) => {
|
|||||||
var privateapps = [];
|
var privateapps = [];
|
||||||
var valid = [];
|
var valid = [];
|
||||||
var invalid = [];
|
var invalid = [];
|
||||||
|
|
||||||
|
var backups = []
|
||||||
for (var key in responseJson) {
|
for (var key in responseJson) {
|
||||||
const app = responseJson[key];
|
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"]
|
app.categories = ["EDR"]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.is_valid && !(!app.activated && app.generated)) {
|
if (app?.is_valid && !(!app?.activated && app?.generated)) {
|
||||||
privateapps.push(app);
|
privateapps.push(app);
|
||||||
} else if (
|
} else if (
|
||||||
app.private_id !== undefined &&
|
app?.private_id !== undefined &&
|
||||||
app.private_id.length > 0
|
app?.private_id.length > 0
|
||||||
) {
|
) {
|
||||||
valid.push(app);
|
valid.push(app);
|
||||||
} else {
|
} else {
|
||||||
@@ -1363,6 +1325,11 @@ const Apps2 = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("BACKUPAPPS: ", backups)
|
||||||
|
if (backups.length > 0) {
|
||||||
|
setBackupApps(backups)
|
||||||
|
}
|
||||||
|
|
||||||
privateapps.push(...valid);
|
privateapps.push(...valid);
|
||||||
privateapps.push(...invalid);
|
privateapps.push(...invalid);
|
||||||
console.log("privateapps: setting apps ", privateapps)
|
console.log("privateapps: setting apps ", privateapps)
|
||||||
@@ -1372,39 +1339,22 @@ const Apps2 = (props) => {
|
|||||||
|
|
||||||
// setFilteredApps(privateapps);
|
// setFilteredApps(privateapps);
|
||||||
if (privateapps.length > 0) {
|
if (privateapps.length > 0) {
|
||||||
if (selectedApp.id === undefined || selectedApp.id === null) {
|
if (selectedApp?.id === undefined || selectedApp?.id === null) {
|
||||||
if (privateapps[0].owner !== undefined && privateapps[0].owner !== null) {
|
if (privateapps[0]?.owner !== undefined && privateapps[0]?.owner !== null) {
|
||||||
getUserProfile(privateapps[0].owner);
|
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 {
|
try {
|
||||||
localStorage.setItem("apps", JSON.stringify(privateapps))
|
localStorage.setItem("apps", JSON.stringify(privateapps))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed to set apps in localstorage: ", e)
|
console.log("Failed to set apps in localstorage: ", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//setTimeout(() => {
|
|
||||||
// setFirstLoad(false)
|
|
||||||
//}, 5000)
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
console.log("Failed to get apps: ", error.toString());
|
||||||
toast(error.toString());
|
toast(error.toString());
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
@@ -1780,7 +1730,6 @@ const Apps2 = (props) => {
|
|||||||
// setOpenModal(true);
|
// setOpenModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const apps = currTab === 1 ? userApps : orgApps;
|
const apps = currTab === 1 ? userApps : orgApps;
|
||||||
const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel);
|
const filteredUserAppdata = filterApps(apps, searchQuery, selectedCategory, selectedLabel);
|
||||||
@@ -1798,33 +1747,38 @@ const Apps2 = (props) => {
|
|||||||
} else if (newTab === 1) {
|
} else if (newTab === 1) {
|
||||||
const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel);
|
const filteredUserApps = filterApps(userApps, searchQuery, selectedCategory, selectedLabel);
|
||||||
setAppsToShow(filteredUserApps);
|
setAppsToShow(filteredUserApps);
|
||||||
}
|
} else if (newTab === 3) {
|
||||||
|
const filteredUserApps = filterApps(backupApps, searchQuery, selectedCategory, selectedLabel);
|
||||||
|
setAppsToShow(filteredUserApps);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Update URL query params based on tab index
|
// Update URL query params based on tab index
|
||||||
const tabMapping = {
|
const tabMapping = {
|
||||||
0: 'org_apps',
|
0: 'org_apps',
|
||||||
1: 'my_apps',
|
1: 'my_apps',
|
||||||
2: 'all_apps'
|
2: 'all_apps',
|
||||||
|
3: 'backup_apps',
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
queryParams.set('tab', tabMapping[newTab]);
|
queryParams.set('tab', tabMapping[newTab]);
|
||||||
|
|
||||||
// Maintain search query in URL regardless of tab
|
// Maintain search query in URL regardless of tab
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
queryParams.set('q', searchQuery);
|
queryParams.set('q', searchQuery);
|
||||||
} else {
|
} else {
|
||||||
queryParams.delete('q');
|
queryParams.delete('q');
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate(`${location.pathname}?${queryParams.toString()}`);
|
navigate(`${location.pathname}?${queryParams.toString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update useEffect for filtering without URL manipulation
|
// Update useEffect for filtering without URL manipulation
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currTab === 2) return; // Skip for "Discover Apps" tab as it uses Algolia
|
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);
|
const filteredApps = filterApps(apps, searchQuery, selectedCategory, selectedLabel);
|
||||||
setAppsToShow(filteredApps);
|
setAppsToShow(filteredApps);
|
||||||
}, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]);
|
}, [searchQuery, selectedCategory, selectedLabel, currTab, userApps, orgApps]);
|
||||||
@@ -1914,7 +1868,7 @@ const Apps2 = (props) => {
|
|||||||
<div style={boxStyle}>
|
<div style={boxStyle}>
|
||||||
<div style={{ display: "flex", flexDirection: "row", width: "100%", justifyContent: "space-between" }}>
|
<div style={{ display: "flex", flexDirection: "row", width: "100%", justifyContent: "space-between" }}>
|
||||||
<Typography variant="h4" style={{ marginBottom: 20, paddingLeft: 15, textTransform: 'none', fontFamily: theme?.typography?.fontFamily }}>
|
<Typography variant="h4" style={{ marginBottom: 20, paddingLeft: 15, textTransform: 'none', fontFamily: theme?.typography?.fontFamily }}>
|
||||||
{currTab === 0 ? "Org" : currTab === 1 ? "Your" : "Discover"} Apps
|
{currTab === 0 ? "Org" : currTab === 1 ? "Your" : currTab === 3 ? "Backup" : "Discover"} Apps
|
||||||
</Typography>
|
</Typography>
|
||||||
{isCloud ? null : (
|
{isCloud ? null : (
|
||||||
<span style={{ display: "flex", gap: 15 }}>
|
<span style={{ display: "flex", gap: 15 }}>
|
||||||
@@ -2027,6 +1981,7 @@ const Apps2 = (props) => {
|
|||||||
...(currTab === 1 ? tabActive : {})
|
...(currTab === 1 ? tabActive : {})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
label="Discover Public Apps"
|
label="Discover Public Apps"
|
||||||
style={{
|
style={{
|
||||||
@@ -2035,6 +1990,17 @@ const Apps2 = (props) => {
|
|||||||
...(currTab === 2 ? tabActive : {})
|
...(currTab === 2 ? tabActive : {})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{backupApps.length > 0 &&
|
||||||
|
<Tab
|
||||||
|
label={`Onprem Backup (${backupApps.length})`}
|
||||||
|
style={{
|
||||||
|
...tabStyle,
|
||||||
|
marginLeft: 25,
|
||||||
|
...(currTab === 3 ? tabActive : {})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: "flex", justifyContent: "space-between", gap: 10, marginBottom: 20, height: 45, paddingRight: 25 }}>
|
<div style={{ display: "flex", justifyContent: "space-between", gap: 10, marginBottom: 20, height: 45, paddingRight: 25 }}>
|
||||||
@@ -2043,7 +2009,7 @@ const Apps2 = (props) => {
|
|||||||
minWidth: "25%",
|
minWidth: "25%",
|
||||||
maxWidth: "25%"
|
maxWidth: "25%"
|
||||||
}}>
|
}}>
|
||||||
{(currTab === 0 || currTab === 1) ? (
|
{(currTab === 0 || currTab === 1 || currTab === 3) ? (
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -2253,7 +2219,7 @@ const Apps2 = (props) => {
|
|||||||
<div>
|
<div>
|
||||||
|
|
||||||
{
|
{
|
||||||
currTab === 0 && (
|
currTab === 0 || currTab === 3 && (
|
||||||
<div style={{ minHeight: 570 }}>
|
<div style={{ minHeight: 570 }}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<LoadingGrid />
|
<LoadingGrid />
|
||||||
@@ -2285,7 +2251,7 @@ const Apps2 = (props) => {
|
|||||||
handleAppClick={handleAppClick}
|
handleAppClick={handleAppClick}
|
||||||
leftSideBarOpenByClick={leftSideBarOpenByClick}
|
leftSideBarOpenByClick={leftSideBarOpenByClick}
|
||||||
userdata={userdata}
|
userdata={userdata}
|
||||||
fetchApps={fetchApps}
|
fetchApps={getApps}
|
||||||
|
|
||||||
setUserApps={setUserApps}
|
setUserApps={setUserApps}
|
||||||
appsToShow={appsToShow}
|
appsToShow={appsToShow}
|
||||||
@@ -2338,7 +2304,7 @@ const Apps2 = (props) => {
|
|||||||
{appsToShow.map((data, index) => (
|
{appsToShow.map((data, index) => (
|
||||||
<AppCard key={index} data={data} index={index} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} globalUrl={globalUrl} deactivatedIndexes={deactivatedIndexes} currTab={currTab} userdata={userdata}
|
<AppCard key={index} data={data} index={index} mouseHoverIndex={mouseHoverIndex} setMouseHoverIndex={setMouseHoverIndex} globalUrl={globalUrl} deactivatedIndexes={deactivatedIndexes} currTab={currTab} userdata={userdata}
|
||||||
handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick}
|
handleAppClick={handleAppClick} leftSideBarOpenByClick={leftSideBarOpenByClick}
|
||||||
fetchApps={fetchApps}
|
fetchApps={getApps}
|
||||||
setUserApps={setUserApps}
|
setUserApps={setUserApps}
|
||||||
appsToShow={appsToShow}
|
appsToShow={appsToShow}
|
||||||
setAppsToShow={setAppsToShow}
|
setAppsToShow={setAppsToShow}
|
||||||
|
|||||||
@@ -678,6 +678,7 @@ const Workflows2 = (props) => {
|
|||||||
var upload = "";
|
var upload = "";
|
||||||
|
|
||||||
const [workflows, setWorkflows] = React.useState([]);
|
const [workflows, setWorkflows] = React.useState([]);
|
||||||
|
const [backupWorkflows, setBackupWorkflows] = React.useState([]);
|
||||||
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
|
const [_, setUpdate] = React.useState(""); // Used for rendering, don't remove
|
||||||
const [selectedUsecases, setSelectedUsecases] = React.useState([]);
|
const [selectedUsecases, setSelectedUsecases] = React.useState([]);
|
||||||
const [filteredWorkflows, setFilteredWorkflows] = React.useState([]);
|
const [filteredWorkflows, setFilteredWorkflows] = React.useState([]);
|
||||||
@@ -756,7 +757,8 @@ const Workflows2 = (props) => {
|
|||||||
const tabMapping = {
|
const tabMapping = {
|
||||||
0: 'org_workflows',
|
0: 'org_workflows',
|
||||||
1: 'my_workflows',
|
1: 'my_workflows',
|
||||||
2: 'all_workflows'
|
2: 'all_workflows',
|
||||||
|
3: 'backup_apps',
|
||||||
};
|
};
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = new URLSearchParams(location.search);
|
||||||
queryParams.set('tab', tabMapping[newValue]);
|
queryParams.set('tab', tabMapping[newValue]);
|
||||||
@@ -1357,15 +1359,25 @@ const Workflows2 = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newarray = []
|
var newarray = []
|
||||||
|
var backupWf = []
|
||||||
for (var wfkey in responseJson) {
|
for (var wfkey in responseJson) {
|
||||||
const wf = responseJson[wfkey]
|
const wf = responseJson[wfkey]
|
||||||
if (wf.public === true || wf.hidden === true) {
|
if (wf.public === true || wf.hidden === true) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wf?.backup_config?.onprem_backup === true) {
|
||||||
|
backupWf.push(wf)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
newarray.push(wf)
|
newarray.push(wf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (backupWf.length > 0) {
|
||||||
|
setBackupWorkflows(backupWf)
|
||||||
|
}
|
||||||
|
|
||||||
var setProdFilter = false
|
var setProdFilter = false
|
||||||
|
|
||||||
var actionnamelist = [];
|
var actionnamelist = [];
|
||||||
@@ -4332,6 +4344,17 @@ const Workflows2 = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{backupWorkflows.length > 0 &&
|
||||||
|
<Tab
|
||||||
|
label={`Onprem Backup (${backupWorkflows.length})`}
|
||||||
|
style={{
|
||||||
|
...tabStyle,
|
||||||
|
marginLeft: 25,
|
||||||
|
...(currTab === 3 ? tabActive : {})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
label="Org Forms"
|
label="Org Forms"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -4341,7 +4364,7 @@ const Workflows2 = (props) => {
|
|||||||
...tabStyle,
|
...tabStyle,
|
||||||
marginRight: 0,
|
marginRight: 0,
|
||||||
marginLeft: 25,
|
marginLeft: 25,
|
||||||
...(currTab === 3 ? tabActive : {})
|
...(currTab === 4 ? tabActive : {})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -4676,8 +4699,6 @@ const Workflows2 = (props) => {
|
|||||||
paddingBottom: 40
|
paddingBottom: 40
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{currTab === 0 && orgWorkflows.map((data, index) => {
|
{currTab === 0 && orgWorkflows.map((data, index) => {
|
||||||
// Shouldn't be a part of this list
|
// Shouldn't be a part of this list
|
||||||
if (data.public === true) {
|
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 <WorkflowPaper key={index} data={data} />
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span key={index}>
|
||||||
|
{/*<Zoom key={index} in={true} style={{ transitionDelay: `${workflowDelay}ms` }}>*/}
|
||||||
|
<WorkflowPaper data={data} />
|
||||||
|
{/*</Zoom>*/}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
{
|
{
|
||||||
currTab === 1 && myWorkflows.map((data, index) => {
|
currTab === 1 && myWorkflows.map((data, index) => {
|
||||||
if (data.public === true) {
|
if (data.public === true) {
|
||||||
|
|||||||
Reference in New Issue
Block a user