Fixed a lot of billing-ui problems, and added rate limiting alerts
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
|
||||||
|
|||||||
@@ -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