Fixed the merge issue
This commit is contained in:
@@ -24,9 +24,12 @@ import {
|
|||||||
Chip,
|
Chip,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
DialogContentText,
|
||||||
|
DialogActions,
|
||||||
|
LinearProgress
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link, json } from "react-router-dom";
|
||||||
import { Autocomplete } from "@mui/material";
|
import { Autocomplete } from "@mui/material";
|
||||||
import { toast } from "react-toastify"
|
import { toast } from "react-toastify"
|
||||||
|
|
||||||
@@ -35,12 +38,15 @@ import {
|
|||||||
ContentCopy as ContentCopyIcon,
|
ContentCopy as ContentCopyIcon,
|
||||||
Draw as DrawIcon,
|
Draw as DrawIcon,
|
||||||
Close as CloseIcon,
|
Close as CloseIcon,
|
||||||
|
Delete,
|
||||||
|
RestaurantRounded,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
//import { useAlert
|
//import { useAlert
|
||||||
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
|
import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx";
|
||||||
import BillingStats from "../components/BillingStats.jsx";
|
import BillingStats from "../components/BillingStats.jsx";
|
||||||
import { handlePayasyougo } from "../views/HandlePaymentNew.jsx"
|
import { handlePayasyougo } from "../views/HandlePaymentNew.jsx"
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
|
||||||
const Billing = (props) => {
|
const Billing = (props) => {
|
||||||
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props;
|
const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, clickedFromOrgTab } = props;
|
||||||
@@ -58,6 +64,56 @@ const Billing = (props) => {
|
|||||||
const [dealValue, setDealValue] = React.useState("");
|
const [dealValue, setDealValue] = React.useState("");
|
||||||
const [dealDiscount, setDealDiscount] = React.useState("");
|
const [dealDiscount, setDealDiscount] = React.useState("");
|
||||||
const [dealerror, setDealerror] = React.useState("");
|
const [dealerror, setDealerror] = React.useState("");
|
||||||
|
const [openChangeEmailBox, setOpenChangeEmailBox] = useState(false);
|
||||||
|
const [isMouseOverOnChangeEmail, setIsMouseOverOnChangeEmail] = useState(false);
|
||||||
|
const [currentAppRunsInPercentage, setCurrentAppRunsInPercentage] = useState(0);
|
||||||
|
const [currentAppRunsInNumber, setCurrentAppRunsInNumber] = useState(0);
|
||||||
|
const [alertThresholds, setAlertThresholds] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.AlertThreshold !== undefined && selectedOrganization.Billing.AlertThreshold !== null ? selectedOrganization.Billing.AlertThreshold : [{ percentage: '', count: '', Email_send: false }]);
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userdata.app_execution_limit !== undefined && userdata.app_execution_usage !== undefined) {
|
||||||
|
const percentage = (userdata.app_execution_usage / userdata.app_execution_limit) * 100;
|
||||||
|
setCurrentAppRunsInPercentage(Math.round(percentage));
|
||||||
|
setCurrentAppRunsInNumber(userdata.app_execution_limit - userdata.app_execution_usage);
|
||||||
|
}
|
||||||
|
}, [userdata]);
|
||||||
|
|
||||||
|
|
||||||
|
const [BillingEmail, setBillingEmail] = useState(selectedOrganization.Billing !== undefined && selectedOrganization.Billing.Email !== undefined && selectedOrganization.Billing.Email != null && selectedOrganization.Billing.Email.length > 0 ? selectedOrganization.Billing.Email : selectedOrganization.org);
|
||||||
|
|
||||||
|
useState(() => {
|
||||||
|
// Set the billing email
|
||||||
|
setBillingEmail(
|
||||||
|
selectedOrganization.Billing !== undefined &&
|
||||||
|
selectedOrganization.Billing.Email !== undefined &&
|
||||||
|
selectedOrganization.Billing.Email.length > 0
|
||||||
|
? selectedOrganization.Billing.Email
|
||||||
|
: selectedOrganization.org
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set and sort the alert thresholds
|
||||||
|
const alertThresholds = selectedOrganization.Billing !== undefined &&
|
||||||
|
selectedOrganization.Billing.AlertThreshold !== undefined &&
|
||||||
|
selectedOrganization.Billing.AlertThreshold !== null
|
||||||
|
? selectedOrganization.Billing.AlertThreshold
|
||||||
|
: [{ percentage: '', count: '', Email_send: false }];
|
||||||
|
|
||||||
|
const sortedAlertThresholds = alertThresholds.sort((a, b) => {
|
||||||
|
const countA = parseFloat(a.count);
|
||||||
|
const countB = parseFloat(b.count);
|
||||||
|
if (isNaN(countA)) return 1;
|
||||||
|
if (isNaN(countB)) return -1;
|
||||||
|
|
||||||
|
return countA - countB;
|
||||||
|
});
|
||||||
|
|
||||||
|
setAlertThresholds(sortedAlertThresholds);
|
||||||
|
|
||||||
|
const findCurrentIndex = sortedAlertThresholds.some(threshold => threshold.Email_send === false);
|
||||||
|
setCurrentIndex(findCurrentIndex ? sortedAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1);
|
||||||
|
|
||||||
|
}, [selectedOrganization]);
|
||||||
|
|
||||||
const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : ""
|
const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : ""
|
||||||
const products = [
|
const products = [
|
||||||
@@ -238,6 +294,7 @@ const Billing = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const sendSignatureRequest = (subscription) => {
|
const sendSignatureRequest = (subscription) => {
|
||||||
const url = `${globalUrl}/api/v1/orgs/${selectedOrganization.id}`;
|
const url = `${globalUrl}/api/v1/orgs/${selectedOrganization.id}`;
|
||||||
|
|
||||||
@@ -275,6 +332,7 @@ const Billing = (props) => {
|
|||||||
const [signatureOpen, setSignatureOpen] = React.useState(false);
|
const [signatureOpen, setSignatureOpen] = React.useState(false);
|
||||||
const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed)
|
const [tosChecked, setTosChecked] = React.useState(subscription.eula_signed)
|
||||||
const [hovered, setHovered] = React.useState(false)
|
const [hovered, setHovered] = React.useState(false)
|
||||||
|
const [newBillingEmail, setNewBillingEmail] = useState('');
|
||||||
|
|
||||||
var top_text = "Base Cloud Access"
|
var top_text = "Base Cloud Access"
|
||||||
if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) {
|
if (subscription.limit === undefined && subscription.level === undefined || subscription.level === null || subscription.level === 0) {
|
||||||
@@ -327,6 +385,78 @@ const Billing = (props) => {
|
|||||||
newPaperstyle.backgroundColor = theme.palette.surfaceColor
|
newPaperstyle.backgroundColor = theme.palette.surfaceColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickOpen = () => {
|
||||||
|
setOpenChangeEmailBox(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseChangeEmailBox = () => {
|
||||||
|
setOpenChangeEmailBox(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getCircularReplacer = () => {
|
||||||
|
const seen = new WeakSet();
|
||||||
|
return (key, value) => {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if (seen.has(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seen.add(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const HandleChangeBillingEmail = (orgId) => {
|
||||||
|
const email = newBillingEmail;
|
||||||
|
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
|
||||||
|
console.log("Pattern matches: ", emailPattern.test(email));
|
||||||
|
if (!emailPattern.test(email)) {
|
||||||
|
toast("Please enter a valid email address");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
setNewBillingEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("Updating billing email. Please Wait")
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
org_id: orgId,
|
||||||
|
email: newBillingEmail,
|
||||||
|
billing: {
|
||||||
|
email: newBillingEmail,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/orgs/${orgId}/billing`;
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Bad status code in get org:", response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}).then((responseJson) => {
|
||||||
|
console.log("Got org:", responseJson);
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
toast.success("Successfully updated billing email");
|
||||||
|
setBillingEmail(newBillingEmail);
|
||||||
|
setOpenChangeEmailBox(false);
|
||||||
|
} else {
|
||||||
|
toast.error("Failed to update billing email. Please try again.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error getting org:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
style={newPaperstyle}
|
style={newPaperstyle}
|
||||||
@@ -587,7 +717,79 @@ const Billing = (props) => {
|
|||||||
`You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.`
|
`You are not subscribed to any plan and are using the free plan with max 10,000 app runs per month. Upgrade to deactivate this limit.`
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
Billing email: {selectedOrganization.org}
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
<Typography variant="body2" style={{}}>
|
||||||
|
Billing email: {BillingEmail}
|
||||||
|
</Typography>
|
||||||
|
{userdata.has_card_available === true && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleClickOpen}
|
||||||
|
onMouseOver={() => setIsMouseOverOnChangeEmail(true)}
|
||||||
|
onMouseOut={() => setIsMouseOverOnChangeEmail(false)}
|
||||||
|
style={{
|
||||||
|
width: 'auto',
|
||||||
|
height: 20,
|
||||||
|
textTransform: 'none',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
color: '#f86743',
|
||||||
|
boxShadow: 'none',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.3s, color 0.3s'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Dialog
|
||||||
|
open={openChangeEmailBox}
|
||||||
|
onClose={handleCloseChangeEmailBox}
|
||||||
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
maxWidth: '500px',
|
||||||
|
width: '100%'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>Change Billing Email</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText style={{ marginBottom: '20px' }}>
|
||||||
|
Enter the new billing email address.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="email"
|
||||||
|
label="New Billing Email"
|
||||||
|
type="email"
|
||||||
|
fullWidth
|
||||||
|
value={newBillingEmail}
|
||||||
|
onKeyPress={(event) => { if (event.key === 'Enter') HandleChangeBillingEmail(selectedOrganization.id) }}
|
||||||
|
onChange={(e) => setNewBillingEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button
|
||||||
|
onClick={handleCloseChangeEmailBox}
|
||||||
|
color="primary"
|
||||||
|
style={{ textTransform: 'none', marginRight: '10px' }}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
style={{ textTransform: 'none' }}
|
||||||
|
onClick={(event) => HandleChangeBillingEmail(selectedOrganization.id)}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
{/*isCloud ?
|
{/*isCloud ?
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -617,7 +819,7 @@ const Billing = (props) => {
|
|||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isCloud) {
|
if (isCloud) {
|
||||||
handlePayasyougo(userdata)
|
handlePayasyougo(userdata, selectedOrganization, BillingEmail)
|
||||||
//navigate("/pricing?tab=cloud&highlight=true")
|
//navigate("/pricing?tab=cloud&highlight=true")
|
||||||
} else {
|
} else {
|
||||||
//window.open("https://shuffler.io/pricing?tab=onprem&highlight=true", "_blank")
|
//window.open("https://shuffler.io/pricing?tab=onprem&highlight=true", "_blank")
|
||||||
@@ -967,6 +1169,130 @@ const Billing = (props) => {
|
|||||||
toast("Failed adding deal reg: ", error);
|
toast("Failed adding deal reg: ", error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const addAlertThreshold = () => {
|
||||||
|
setAlertThresholds([...alertThresholds, { percentage: '', count: '', Email_send: false }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAlertThreshold = (index, field, value) => {
|
||||||
|
|
||||||
|
if (field === 'percentage') {
|
||||||
|
if (value > 100 || value < 0) {
|
||||||
|
value = 0
|
||||||
|
toast("The percentage value should be between 0 and 100")
|
||||||
|
}
|
||||||
|
} else if (field === 'count') {
|
||||||
|
if (value < 0 || value >= userdata.app_execution_limit) {
|
||||||
|
value = 0
|
||||||
|
toast("The count value should be greater than 0 and less than the total app execution limit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const totalValue = userdata.app_execution_limit;
|
||||||
|
const newAlertThresholds = alertThresholds.map((threshold, i) => {
|
||||||
|
if (i === index) {
|
||||||
|
const newValue = parseFloat(value);
|
||||||
|
if (field === 'percentage') {
|
||||||
|
const newCount = (newValue / 100) * totalValue;
|
||||||
|
return {
|
||||||
|
...threshold,
|
||||||
|
percentage: isNaN(newValue) ? '' : Math.round(newValue),
|
||||||
|
count: isNaN(newCount) ? '' : Math.round(newCount),
|
||||||
|
Email_send: false
|
||||||
|
};
|
||||||
|
} else if (field === 'count') {
|
||||||
|
const newPercentage = (newValue / totalValue) * 100;
|
||||||
|
return {
|
||||||
|
...threshold,
|
||||||
|
count: newValue,
|
||||||
|
percentage: isNaN(newPercentage) ? '' : Math.round(newPercentage),
|
||||||
|
Email_send: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return threshold;
|
||||||
|
});
|
||||||
|
setAlertThresholds(newAlertThresholds);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleDeleteAlertThreshold = (index) => {
|
||||||
|
const newAlertThresholds = alertThresholds.filter((_, i) => i !== index);
|
||||||
|
setAlertThresholds(newAlertThresholds);
|
||||||
|
|
||||||
|
// Update currentIndex based on remaining elements
|
||||||
|
const findCurrentIndex = newAlertThresholds.some(threshold => threshold.Email_send === false);
|
||||||
|
setCurrentIndex(findCurrentIndex ? newAlertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HandleEditOrgForAlertThreshold = (orgId) => {
|
||||||
|
|
||||||
|
// Use the `some` method to check for invalid counts
|
||||||
|
const invalidCount = alertThresholds.some((threshold) => {
|
||||||
|
if (threshold.count === '') {
|
||||||
|
toast("Please enter a valid Count or Percentage value");
|
||||||
|
return true; // Stop checking further and return true if invalid
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If any invalid count is found, return early
|
||||||
|
if (invalidCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toast("Updating Email Alert Threshold. Please wait...");
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
org_id: orgId,
|
||||||
|
billing: {
|
||||||
|
email: BillingEmail,
|
||||||
|
AlertThreshold: alertThresholds.map(threshold => ({
|
||||||
|
...threshold,
|
||||||
|
percentage: parseInt(threshold.percentage, 10),
|
||||||
|
count: parseInt(threshold.count, 10),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const url = `${globalUrl}/api/v1/orgs/${orgId}`;
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log("Bad status code in get org:", response.status);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}).then((responseJson) => {
|
||||||
|
console.log("Got org:", responseJson);
|
||||||
|
if (responseJson.success === true) {
|
||||||
|
toast.success("Successfully updated Email Alert Thresholds");
|
||||||
|
const findCurrentIndex = alertThresholds.some(threshold => threshold.Email_send === false);
|
||||||
|
setCurrentIndex(findCurrentIndex ? alertThresholds.findIndex(threshold => threshold.Email_send === false) : - 1);
|
||||||
|
} else {
|
||||||
|
toast.error("Failed to update Email Alert Thresholds. Please try again.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("Error getting org:", error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSafeValue = (value) => {
|
||||||
|
|
||||||
|
if (value === undefined || value === null || isNaN(value)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null
|
const isChildOrg = userdata.active_org.creator_org !== "" && userdata.active_org.creator_org !== undefined && userdata.active_org.creator_org !== null
|
||||||
return (
|
return (
|
||||||
@@ -1009,8 +1335,6 @@ const Billing = (props) => {
|
|||||||
<a href={"https://github.com/Shuffle/Shuffle-docs/tree/master/handbook/Sales"} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
<a href={"https://github.com/Shuffle/Shuffle-docs/tree/master/handbook/Sales"} target="_blank" rel="noopener noreferrer" style={{ textDecoration: "none", color: "#f85a3e" }}>
|
||||||
Sales Process
|
Sales Process
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
null
|
null
|
||||||
@@ -1333,9 +1657,144 @@ const Billing = (props) => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
) : null*/}
|
) : null*/}
|
||||||
<div style={{ marginTop: 100, }}>
|
<div style={{ marginTop: 50, marginLeft: 10 }}>
|
||||||
<Typography
|
<Typography
|
||||||
style={{ marginTop: 40, marginLeft: 10, marginBottom: 5 }}
|
style={{ marginTop: 40, marginBottom: 5 }}
|
||||||
|
variant="h4"
|
||||||
|
>
|
||||||
|
Manage Billing
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 0, marginBottom: 10 }}>
|
||||||
|
Manage your billing and licensing information below. When you reach the certain thresholds of your subscription limit, you will be notified by email.
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">Current Usage:</Typography>
|
||||||
|
<LinearProgress
|
||||||
|
variant="determinate"
|
||||||
|
value={currentAppRunsInPercentage}
|
||||||
|
style={{
|
||||||
|
width: "50%",
|
||||||
|
height: 15,
|
||||||
|
borderRadius: 10,
|
||||||
|
margin: '10px 0',
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="body1" color="textSecondary">
|
||||||
|
You have used <strong>{currentAppRunsInPercentage}%</strong> of total app execution limit or <strong>{userdata.app_execution_usage}</strong> app runs out of <strong>{userdata.app_execution_limit}</strong> app runs.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Typography variant="body1" style={{ marginTop: 20 }}>
|
||||||
|
Set email alert thresholds for app runs
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="textSecondary" style={{ marginTop: 10 }}>
|
||||||
|
You will be notified by email when you reach the
|
||||||
|
{currentIndex !== -1
|
||||||
|
? " " + getSafeValue(alertThresholds[currentIndex].percentage) + '%' + " "
|
||||||
|
: " " + '0%' + " "
|
||||||
|
}
|
||||||
|
of your total app execution limit or
|
||||||
|
{currentIndex !== -1
|
||||||
|
? " " + getSafeValue(alertThresholds[currentIndex].count) + " "
|
||||||
|
: " " + 0 + " "}
|
||||||
|
app runs.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 15 }}>
|
||||||
|
{alertThresholds.map((threshold, index) => (
|
||||||
|
<div key={index} style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<TextField
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
width: 250,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: 50,
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
endAdornment: '%',
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
label="Alert threshold (%)"
|
||||||
|
type="number"
|
||||||
|
value={threshold.percentage}
|
||||||
|
onChange={(e) => updateAlertThreshold(index, 'percentage', e.target.value)}
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
inputProps={{
|
||||||
|
max: 100,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
backgroundColor: theme.palette.inputColor,
|
||||||
|
width: 250,
|
||||||
|
marginLeft: 15,
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
style: {
|
||||||
|
height: 50,
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
label="Alert threshold (count)"
|
||||||
|
type="number"
|
||||||
|
value={threshold.count}
|
||||||
|
onChange={(e) => updateAlertThreshold(index, 'count', e.target.value)}
|
||||||
|
margin="normal"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
alertThresholds.length > 1 &&
|
||||||
|
(
|
||||||
|
<Button
|
||||||
|
disableRipple
|
||||||
|
disableElevation
|
||||||
|
sx={{
|
||||||
|
padding: 0,
|
||||||
|
color: 'red',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
onClick={() => { handleDeleteAlertThreshold(index) }}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
style={{ marginTop: 15, textTransform: 'none' }}
|
||||||
|
onClick={addAlertThreshold}
|
||||||
|
>
|
||||||
|
Add Threshold
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
style={{ marginTop: 15, textTransform: 'none', marginLeft: 20 }}
|
||||||
|
onClick={() => { HandleEditOrgForAlertThreshold(selectedOrganization.id) }}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 40, display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Typography
|
||||||
|
style={{ marginTop: 10, marginLeft: 10, marginBottom: 5 }}
|
||||||
variant="h4"
|
variant="h4"
|
||||||
>
|
>
|
||||||
Utilization & Stats
|
Utilization & Stats
|
||||||
|
|||||||
@@ -217,6 +217,17 @@ const ParsedAction = (props) => {
|
|||||||
selectedAction, selectedApp,setNewSelectedAction, workflow,
|
selectedAction, selectedApp,setNewSelectedAction, workflow,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setParamValues(selectedAction.parameters.map((param) => {
|
||||||
|
return {
|
||||||
|
name: param.name,
|
||||||
|
value: param.value,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
},[
|
||||||
|
selectedAction, selectedApp,setNewSelectedAction, workflow,
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedAction.parameters === null || selectedAction.parameters === undefined) {
|
if (selectedAction.parameters === null || selectedAction.parameters === undefined) {
|
||||||
return
|
return
|
||||||
@@ -972,10 +983,12 @@ const ParsedAction = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
selectedActionParameters[count].autocompleted = false
|
selectedActionParameters[count].autocompleted = false
|
||||||
selectedAction.parameters[count].autocompleted = false
|
selectedAction.parameters[count].autocompleted = false
|
||||||
selectedActionParameters[count].value = data
|
selectedActionParameters[count].value = data
|
||||||
selectedAction.parameters[count].value = data
|
selectedAction.parameters[count].value = data
|
||||||
|
}, 100);
|
||||||
setSelectedAction(selectedAction)
|
setSelectedAction(selectedAction)
|
||||||
//setUpdate(Math.random())
|
//setUpdate(Math.random())
|
||||||
//setUpdate(event.target.value)
|
//setUpdate(event.target.value)
|
||||||
@@ -1143,7 +1156,6 @@ const ParsedAction = (props) => {
|
|||||||
|
|
||||||
// FIXME: Issue #40 - selectedActionParameters not reset
|
// FIXME: Issue #40 - selectedActionParameters not reset
|
||||||
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
|
if (Object.getOwnPropertyNames(selectedAction).length > 0 && selectedActionParameters.length > 0) {
|
||||||
|
|
||||||
var wrapperapp = {
|
var wrapperapp = {
|
||||||
"id": "",
|
"id": "",
|
||||||
"name": "noapp",
|
"name": "noapp",
|
||||||
|
|||||||
@@ -3804,15 +3804,9 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
if (connected.length > 0 && connected !== undefined) {
|
if (connected.length > 0 && connected !== undefined) {
|
||||||
for (let connectkey in connected) {
|
for (let connectkey in connected) {
|
||||||
const edge = connected[connectkey]
|
const edge = connected[connectkey]
|
||||||
if (edge.data.decorator && edge.data.label === releaseToConnectLabel) {
|
//console.log("EDGE:", edge)
|
||||||
// Transform to normal edge
|
|
||||||
const currentedge = cy.getElementById(edge.data.id)
|
//const edge = edgeBase.json()
|
||||||
if (currentedge !== undefined && currentedge !== null) {
|
|
||||||
currentedge.data("decorator", false)
|
|
||||||
currentedge.data("label", "")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourcenode = cy.getElementById(edge.data.source)
|
const sourcenode = cy.getElementById(edge.data.source)
|
||||||
const destinationnode = cy.getElementById(edge.data.target)
|
const destinationnode = cy.getElementById(edge.data.target)
|
||||||
@@ -4027,87 +4021,19 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nodedata.id === selectedAction.id) {
|
if (nodedata.id === selectedAction.id) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ((nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT" || nodedata.type === "ACTION") && !nodedata.isStartNode) {
|
|
||||||
// Check if it already has any non-decorator branches attached to it
|
|
||||||
const branches = cy.elements('edge').jsons()
|
|
||||||
var branchFound = false
|
|
||||||
var decoratorIds = []
|
|
||||||
for (var branchkey in branches) {
|
|
||||||
if (branches[branchkey].data.source === nodedata.id || branches[branchkey].data.target === nodedata.id) {
|
|
||||||
|
|
||||||
if (branches[branchkey].data.decorator === true) {
|
/*
|
||||||
|
// Tried looking for the closest node by position. aStar path not working entirely.
|
||||||
// Add the source/destination
|
console.log("NODE: ", event.target)
|
||||||
if (branches[branchkey].data.source === nodedata.id) {
|
const closestNode = cy.elements().aStar({
|
||||||
decoratorIds.push(branches[branchkey].data.target)
|
root: nodedata.id,
|
||||||
} else {
|
goal: 'node',
|
||||||
decoratorIds.push(branches[branchkey].data.source)
|
directed: false,
|
||||||
}
|
})
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
branchFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!branchFound) {
|
|
||||||
//console.log("Found action during drag. Checking closest nodes as it doesn't have a valid branch")
|
|
||||||
var closestNode = null
|
|
||||||
var minDistance = 300
|
|
||||||
|
|
||||||
const draggedNode = event.target
|
|
||||||
const allnodes = cy.nodes().jsons()
|
|
||||||
for (var nodekey in allnodes) {
|
|
||||||
const node = allnodes[nodekey]
|
|
||||||
if (node.data.id === nodedata.id) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decorators
|
|
||||||
if (node.data.attachedTo !== undefined) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.position === undefined || node.position === null || node.position.x === undefined || node.position.y === undefined) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.data.type !== "ACTION" && node.data.type !== "TRIGGER") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const distance = Math.sqrt(
|
|
||||||
Math.pow(draggedNode.position('x') - node.position.x, 2) +
|
|
||||||
Math.pow(draggedNode.position('y') - node.position.y, 2)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (decoratorIds.includes(node.data.id)) {
|
|
||||||
//console.log("Found existing decorator for: ", node.data.app_name, "Distance: ", distance)
|
|
||||||
|
|
||||||
if (distance > 300) {
|
|
||||||
// Remove the branch
|
|
||||||
const edgeToRemove = cy.getElementById(branches[branchkey].data.id)
|
|
||||||
if (edgeToRemove !== null && edgeToRemove !== undefined) {
|
|
||||||
//console.log("Removing edge: ", edgeToRemove)
|
|
||||||
edgeToRemove.remove()
|
|
||||||
//decoratorIds.splice(decoratorIds.indexOf(node.data.id), 1)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (distance < minDistance) {
|
|
||||||
minDistance = distance
|
|
||||||
closestNode = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closestNode !== null && closestNode !== undefined) {
|
if (closestNode !== null && closestNode !== undefined) {
|
||||||
//console.log("Closest node app: ", closestNode.data.app_name, "Distance: ", minDistance)
|
//console.log("Closest node app: ", closestNode.data.app_name, "Distance: ", minDistance)
|
||||||
@@ -4220,11 +4146,11 @@ const releaseToConnectLabel = "Release to Connect"
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure it only happens once
|
// Ensure it only happens once
|
||||||
document.removeEventListener("mousemove", onMouseUpdate, false)
|
document.removeEventListener("mousemove", onMouseUpdate, false);
|
||||||
}
|
};
|
||||||
|
|
||||||
document.addEventListener("mousemove", onMouseUpdate, false)
|
document.addEventListener("mousemove", onMouseUpdate, false);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
useBeforeunload(() => {
|
useBeforeunload(() => {
|
||||||
|
|||||||
@@ -2153,7 +2153,7 @@ const Workflows = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{width: "100%", minWidth: 321, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? "1px solid #40E0D0" : "inherit", borderRadius: theme.palette.borderRadius, }}>
|
<div style={{width: "100%", minWidth: 320, position: "relative", border: highlightIds.includes(data.id) ? "2px solid #f85a3e" : isDistributed || hasSuborgs ? "1px solid #40E0D0" : "inherit", borderRadius: theme.palette.borderRadius, }}>
|
||||||
<Paper square style={paperAppStyle}>
|
<Paper square style={paperAppStyle}>
|
||||||
{selectedCategory !== "" ?
|
{selectedCategory !== "" ?
|
||||||
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
|
<Tooltip title={`Usecase Category: ${selectedCategory}`} placement="bottom">
|
||||||
|
|||||||
Reference in New Issue
Block a user