{subscription.features !== undefined && subscription.features !== null ?
subscription.features.map((feature, index) => {
var parsedFeature = feature
if (feature.includes("Documentation: ")) {
parsedFeature =
Documentation to get started
}
if (feature.includes("Worker License: ")) {
const fieldId = "webhook_uri_field_" + index
parsedFeature =
Use the {feature.split("Worker License: ")[0]} Worker
{ }}
InputProps={{
endAdornment:
{
var copyText = document.getElementById(fieldId);
if (copyText !== undefined && copyText !== null) {
console.log("NAVIGATOR: ", navigator);
const clipboard = navigator.clipboard;
if (clipboard === undefined) {
toast("Can only copy over HTTPS (port 3443)");
return;
}
navigator.clipboard.writeText(copyText.value);
copyText.select();
copyText.setSelectionRange(
0,
99999
); /* For mobile devices */
/* Copy the text inside the text field */
document.execCommand("copy");
toast("Copied Webhook URL");
} else {
console.log("Couldn't find webhook URI field: ", copyText);
}
}}
edge="end"
>
}}
fullWidth
/>
}
return (
{parsedFeature}
)
})
: null}
{isCloud && (highlight === true && (subscription.name === "Pay as you go" && subscription.limit <= 10000) || subscription.name === "Open Source") ?
{subscription.name.includes("Scale") ?
""
:
userdata.has_card_available === true ?
"While you have a card attached to your account, Shuffle will no longer prevent workflows from running. Billing will occur at the start of each month."
:
isCloud ?
`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, open source plan. This plan has no enforced limits, but scale issues may occur due to CPU congestion.`
}
Build custom apps, integrations, and worklows for your specific use cases or applications
Help solve / debug / update / add features and capabilities of the platform
)
}
const TrainingService = () => {
const [hovered, setHovered] = React.useState(false)
const [openPrivateTraining, setOpenPrivateTraining] = React.useState(false)
const [PrivateTrainingMember, setPrivateTrainingMember] = React.useState(5)
const [message, setMessage] = React.useState("");
const handlePrivateTraining = () => {
toast("Submitting your request for private training. Please wait...")
const data = {
org_id: selectedOrganization.id,
trainingMembers: String(PrivateTrainingMember),
message: message
}
const url = `${globalUrl}/api/v1/orgs/${selectedOrganization.id}/privateTraining`
fetch(url, {
body: JSON.stringify(data),
mode: "cors",
method: "POST",
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
}).then((response) => {
if (response.status !== 200) {
console.log("Error in response");
}
return response.json();
}).then((responseJson) => {
if (responseJson.success === true) {
toast.success("Your request for private training has been submitted successfully. We will get back to you soon.")
setOpenPrivateTraining(false)
} else {
toast.error("Failed sending request for private training. Please try again later or contact support@shuffler.io for help.")
}
})
}
return (
setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
Training
Become a Shuffle Expert
Public Training
Public course on Automation for Security Professionals
Covers Shuffle Platform, Apps, Workflows, Usecases, JSON, Liquid Formatting, and more.
Private Training
Everything from Public Training
Customized for your team’s usecases, date and time, location, and more.
)
}
const addDealModal = (
);
const submitDeal = (dealName, dealAddress, dealCountry, dealValue) => {
if (dealerror.length > 0) {
setDealerror("");
}
const orgId = selectedOrganization.id;
const data = {
reseller_org: orgId,
name: dealName,
address: dealAddress,
country: dealCountry,
value: dealValue,
};
const url = `${globalUrl}/api/v1/orgs/${orgId}/deals`;
fetch(url, {
mode: "cors",
method: "POST",
body: JSON.stringify(data),
credentials: "include",
crossDomain: true,
withCredentials: true,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
})
.then(function (response) {
if (response.status !== 200) {
console.log("Error in response");
}
return response.json();
})
.then(function (responseJson) {
if (responseJson.success === true) {
setSelectedDealModalOpen(false);
toast(
"Added new deal! We will be in touch shortly with an update."
);
setDealName("");
setDealAddress("");
setDealValue("");
setDealCountry("United States");
setDealType("MSSP");
} else {
setDealerror(responseJson.reason);
}
})
.catch(function (error) {
//console.log("Error: ", error);
setDealerror(error.toString());
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);
toast.info("Alert Threshold deleted successfully. Don't forget to save your changes.");
};
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,
name: selectedOrganization?.name,
description: selectedOrganization?.description,
image: selectedOrganization?.image,
defaults: selectedOrganization?.defaults,
sso_config: selectedOrganization?.sso_config,
mfa_required: selectedOrganization?.mfa_required,
billing: {
email: BillingEmail,
AlertThreshold: alertThresholds.map(threshold => ({
...threshold,
percentage: parseInt(threshold.percentage, 10),
count: parseInt(threshold.count, 10),
})),
Consultation: selectedOrganization?.billing?.Consultation,
},
};
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
return (
{addDealModal}
{clickedFromOrgTab ?
Billing & Licensing :
Billing & Licensing
}
{clickedFromOrgTab ?
{isCloud ?
"Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below."
:
"Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update."
} :
{isCloud ?
"Get more out of Shuffle by adding your credit card, such as no App Run limitations, and priority support from our team. We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below."
:
"Shuffle is an Open Source automation platform, and no license is required. We do however offer a Scale license with HA guarantees, along with support hours. By buying a license on https://shuffler.io, you can get access to the license immediately, and if Cloud Syncronisation is enabled, the UI in your local instance will also update."
}
}
{userdata.support === true ?
Reseller dashboard
{dealList.length === 0 ? (
No deals registered yet. Click "Add deal" to register one
) : (
dealList.map((deal, index) => {
var bgColor = "#27292d";
if (index % 2 === 0) {
bgColor = "#1f2023";
}
return (
);
})
)}
) : null*/}
{!isChildOrg && isCloud && (
Professional Services
We offer priority support through consultations and training to help you make the most of our product. If you have any questions, please reach out to us at support@shuffler.io.
Manage Billing
Manage your billing and licensing information below. When you reach the certain thresholds of your subscription limit, you will be notified by email.
Current Usage:
You have used {currentAppRunsInPercentage}% of total app execution limit or {userdata.app_execution_usage} app runs out of {userdata.app_execution_limit} app runs.
Set email alert thresholds for app runs
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.
Please note: Once your app runs reach the set alert threshold, all admins in the organization will receive an email notification.