Sync UI files from cloud

This commit is contained in:
monilprajapati
2025-10-10 17:40:06 +05:30
parent 96f91d0e9e
commit a137368eee
4 changed files with 181 additions and 22 deletions
+3 -2
View File
@@ -2099,13 +2099,13 @@ const Billing = memo((props) => {
<Typography variant="body2" color="textSecondary" style={{ fontSize: 16 }}>{isCloud ? <Typography variant="body2" color="textSecondary" style={{ fontSize: 16 }}>{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." "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." !(selectedOrganization?.subscriptions !== undefined && selectedOrganization?.subscriptions.length > 0 && selectedOrganization?.subscriptions[0]?.name?.toLowerCase().includes("enterprise") && selectedOrganization?.subscriptions[0]?.active) ? "Shuffle is an Enterprise automation platform, and a 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." : "Here you can check your license and billing information."
}</Typography> : }</Typography> :
<Typography variant="body1" color="textSecondary" style={{ marginTop: 0, marginBottom: 10, fontSize: 16 }}> <Typography variant="body1" color="textSecondary" style={{ marginTop: 0, marginBottom: 10, fontSize: 16 }}>
{isCloud ? {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." "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." !(selectedOrganization?.subscriptions !== undefined && selectedOrganization?.subscriptions.length > 0 && selectedOrganization?.subscriptions[0]?.name?.toLowerCase().includes("enterprise") && selectedOrganization?.subscriptions[0]?.active) ? "Shuffle is an Enterprise automation platform, and a 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." : "Here you can check your license and billing information."
} }
</Typography>} </Typography>}
</> } </> }
@@ -2924,6 +2924,7 @@ const Billing = memo((props) => {
userdata={userdata} userdata={userdata}
currentTab={currentTab} currentTab={currentTab}
syncStats={true} syncStats={true}
statistics={statistics}
/> />
} }
</div> </div>
+96 -2
View File
@@ -92,6 +92,8 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
region_url: org.region_url, region_url: org.region_url,
})) || [] })) || []
); );
const [activeOrgData, setActiveOrgData] = useState(null);
const [isProdStatusOn, setIsProdStatusOn] = useState(false);
const userOrgs = React.useMemo(() => { const userOrgs = React.useMemo(() => {
return orgOptions.find((option) => option.name === selectedOrg); return orgOptions.find((option) => option.name === selectedOrg);
}, [selectedOrg, orgOptions]); }, [selectedOrg, orgOptions]);
@@ -682,7 +684,7 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
<Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}> <Typography color="textSecondary" align="center" style={{ marginTop: 5, marginBottom: 5, fontSize: 18 }}>
Version: <a href="https://github.com/Shuffle/Shuffle/releases" style={{ color: theme.palette.text.primary, textDecoration: "underline" }} target="_blank" rel="noreferrer"> Version: <a href="https://github.com/Shuffle/Shuffle/releases" style={{ color: theme.palette.text.primary, textDecoration: "underline" }} target="_blank" rel="noreferrer">
2.1.0 2.1.1
</a> </a>
</Typography> </Typography>
</Menu> </Menu>
@@ -936,6 +938,40 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
const showPartnerLogo = userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.image !== undefined && userdata?.active_org?.image !== null && userdata?.active_org?.image.length > 0 const showPartnerLogo = userdata?.org_status?.includes("integration_partner") && userdata?.active_org?.image !== undefined && userdata?.active_org?.image !== null && userdata?.active_org?.image.length > 0
useEffect(() => {
const orgId = userdata?.active_org?.id;
if (!orgId) {
return;
}
let fetched = false;
fetch(`${globalUrl}/api/v1/orgs/${orgId}`, {
method: "GET",
credentials: "include",
headers: { "Content-Type": "application/json" },
})
.then((response) => (response.ok ? response.json() : null))
.then((org) => {
if (!fetched && org) {
setActiveOrgData(org);
if (!isCloud) {
if (org?.cloud_sync && org?.subscriptions[0]?.name?.toLowerCase().includes("enterprise") && org?.subscriptions[0]?.active) {
setIsProdStatusOn(true);
} else if (org?.subscriptions[0]?.name?.toLowerCase().includes("enterprise") && org?.subscriptions[0]?.active) {
setIsProdStatusOn(true);
} else {
setIsProdStatusOn(false);
}
}
}
})
.catch(() => {});
return () => {
fetched = true;
};
}, [userdata?.active_org?.id, globalUrl]);
return ( return (
<div <div
style={{ style={{
@@ -975,6 +1011,14 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
} }
}} }}
> >
<Box sx={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 1,
}}
>
<Tooltip <Tooltip
title="Go to Home" title="Go to Home"
placement="top" placement="top"
@@ -1005,7 +1049,15 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
style={{ width: showPartnerLogo ? 30 : 24, height: showPartnerLogo ? 30 : 24 }} style={{ width: showPartnerLogo ? 30 : 24, height: showPartnerLogo ? 30 : 24 }}
/> />
</Link> </Link>
</Tooltip> </Tooltip>
{
!isCloud && expandLeftNav && (
<Typography variant="body2" style={{fontSize: 16, color: themeMode === "dark" ? lightText : darkText, transition: "opacity 0.3s ease", fontWeight: 600, marginTop: -5, marginLeft: 3}}>
{isProdStatusOn ? "Enterprise" : "Open Source"}
</Typography>
)
}
</Box>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@@ -1871,6 +1923,48 @@ const LeftSideBar = ({ userdata, serverside, globalUrl, notifications, }) => {
}} }}
> >
{!isCloud ? (
<div
style={{
display: "flex",
alignItems: "center",
gap: 20,
padding: "4px 10px",
marginLeft: "5px",
marginRight: "5px",
borderRadius: 20,
marginBottom: "14px",
background: isProdStatusOn
? "rgba(43, 192, 126, 0.1)"
: "rgba(255, 82, 82, 0.1)",
cursor: "pointer",
}}
onClick={() => {
navigate("/admin?admin_tab=prodstatus")
}}
>
<span
style={{
width: 8,
height: 8,
marginLeft: 10,
background: isProdStatusOn ? "#2BC07E" : "#FD4C62",
borderRadius: 999,
display: expandLeftNav ? "inline" : "none",
}}
/>
<Typography
style={{
fontFamily: "12px",
opacity: 0.9,
color: isProdStatusOn ? "#2BC07E" : "#FD4C62",
}}
>
{expandLeftNav ? isProdStatusOn ? "Prod. Status ON" : "Prod. Status OFF" : isProdStatusOn ? "ON" : "OFF"}
</Typography>
</div>
) : null}
{userdata?.licensed !== true && !userdata?.org_status?.includes("integration_partner") && expandLeftNav && {userdata?.licensed !== true && !userdata?.org_status?.includes("integration_partner") && expandLeftNav &&
<div style={{display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", }}> <div style={{display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", }}>
<Button <Button
+16 -6
View File
@@ -237,7 +237,7 @@ const LicencePopup = (props) => {
return formattedLimit === "Unlimited" return formattedLimit === "Unlimited"
? "Unlimited SMS per month" ? "Unlimited SMS per month"
: `${formattedLimit} SMS${ : `${formattedLimit} SMS${
parseInt(formattedLimit) > 1 ? "s" : "" parseInt(formattedLimit) > 1 ? "" : ""
} per month`; } per month`;
}, },
email_trigger: (limit) => { email_trigger: (limit) => {
@@ -296,6 +296,14 @@ const LicencePopup = (props) => {
parseInt(formattedLimit) > 1 ? "s" : "" parseInt(formattedLimit) > 1 ? "s" : ""
}`; }`;
}, },
branding: (limit) => {
const formattedLimit = formatLimit(limit);
return formattedLimit === "Unlimited"
? "Unlimited Branding"
: `${formattedLimit} Branding${
parseInt(formattedLimit) > 1 ? "s" : ""
}`;
}
}; };
try { try {
@@ -1027,7 +1035,7 @@ const LicencePopup = (props) => {
}; };
const isCancelled = localSub.cancellationdate !== 0; const isCancelled = localSub.cancellationdate !== 0;
const isPaidPlan = localSub.amount !== "0"; const isPaidPlan = localSub.amount !== "0" || (!isCloud && localSub.name.toLowerCase().includes("enterprise") && !selectedOrganization.cloud_sync);
const amountToshow = isPaidPlan const amountToshow = isPaidPlan
? String(localSub.currency || "").toLowerCase() === "usd" ? String(localSub.currency || "").toLowerCase() === "usd"
? "$" + localSub?.amount ? "$" + localSub?.amount
@@ -1214,7 +1222,7 @@ const LicencePopup = (props) => {
}} }}
> >
{/* EULA Signature Button */} {/* EULA Signature Button */}
{isCloud && localSub.eula && appRunsLimit >= 12000 && ( {false && (
<Tooltip <Tooltip
title={ title={
localSub.eula_signed localSub.eula_signed
@@ -1279,7 +1287,8 @@ const LicencePopup = (props) => {
</div> </div>
</div> </div>
<div { (!isCloud && selectedOrganization.cloud_sync) || isCloud && (
<div
style={{ style={{
display: "flex", display: "flex",
alignItems: "baseline", alignItems: "baseline",
@@ -1301,11 +1310,12 @@ const LicencePopup = (props) => {
</Typography> </Typography>
)} )}
</div> </div>
)}
{(localSub.enddate || localSub.Enddate) && localSub.active ? ( {(localSub.enddate || localSub.Enddate) && localSub.active ? (
<Typography <Typography
variant="caption" variant="caption"
color="textSecondary" color="textSecondary"
style={{ marginTop: 2 }} style={{ marginTop: (isCloud || (!isCloud && selectedOrganization.cloud_sync)) ? 2 : 8 }}
> >
{`${ {`${
isPaidPlan ? "Next billing: " : "App runs resets on " isPaidPlan ? "Next billing: " : "App runs resets on "
@@ -1494,7 +1504,7 @@ const LicencePopup = (props) => {
Manage subscription Manage subscription
</Button> </Button>
) : null} ) : null}
{subscription.amount === "0" && ( {!isPaidPlan && (
<Button <Button
fullWidth fullWidth
variant="outlined" variant="outlined"
+66 -12
View File
@@ -7,7 +7,8 @@ import EditOrgTab from '../components/EditOrgTab.jsx';
import CloudSyncTab from '../components/CloudSyncTab.jsx'; import CloudSyncTab from '../components/CloudSyncTab.jsx';
import SSOTab from "../components/ssoTab.jsx" import SSOTab from "../components/ssoTab.jsx"
import { ToastContainer, toast } from "react-toastify"; import { ToastContainer, toast } from "react-toastify";
import { Button, Tooltip } from '@mui/material'; import { Button, Tooltip, Typography } from '@mui/material';
import { CheckCircle as CheckCircleIcon, Cancel as CancelIcon } from '@mui/icons-material';
import { getTheme } from '../theme.jsx'; import { getTheme } from '../theme.jsx';
import { Context } from '../context/ContextApi.jsx'; import { Context } from '../context/ContextApi.jsx';
const OrganizationTab = (props) => { const OrganizationTab = (props) => {
@@ -36,7 +37,7 @@ const OrganizationTab = (props) => {
const [billingInfo, setBillingInfo] = useState({}); const [billingInfo, setBillingInfo] = useState({});
const [orgRequest, setOrgRequest] = React.useState(true); const [orgRequest, setOrgRequest] = React.useState(true);
const [curIndex, setCurIndex] = React.useState(0); const [curIndex, setCurIndex] = React.useState(0);
const items = ['Org Configuration', "SSO", "Notifications", 'Billing & Stats']; const items = ['Org Configuration', 'Production Status', "SSO", "Notifications", 'Billing & Stats'];
const [visibleTabs, setVisibleTabs] = useState(items); const [visibleTabs, setVisibleTabs] = useState(items);
const [unreadNotifications, setUnreadNotifications] = React.useState( const [unreadNotifications, setUnreadNotifications] = React.useState(
notifications?.filter((notification) => notification.read === false)?.length notifications?.filter((notification) => notification.read === false)?.length
@@ -56,7 +57,11 @@ const OrganizationTab = (props) => {
setVisibleTabs(items.filter((item) => item !== 'SSO')); setVisibleTabs(items.filter((item) => item !== 'SSO'));
} }
} }
},[isIntegrationPartner, isChildOrg, isGlobalUser, userdata]);
if (isCloud) {
setVisibleTabs(items.filter((item) => item !== 'Production Status'));
}
},[isIntegrationPartner, isChildOrg, isGlobalUser, userdata, isCloud]);
useEffect(() => { useEffect(() => {
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
@@ -66,30 +71,32 @@ const OrganizationTab = (props) => {
setSelectedTab(decodedTabName); setSelectedTab(decodedTabName);
if (decodedTabName === 'org_config') { if (decodedTabName === 'org_config') {
setCurIndex(0); setCurIndex(0);
} else if (decodedTabName === 'prodstatus' || decodedTabName === 'productionstatus' && !isCloud) {
setCurIndex(1);
} else if(decodedTabName === 'sso'){ } else if(decodedTabName === 'sso'){
setCurIndex(1) setCurIndex(isCloud ? 1 : 2)
}else if (decodedTabName === 'notifications' || decodedTabName === 'priorities') { }else if (decodedTabName === 'notifications' || decodedTabName === 'priorities') {
if (isIntegrationPartner && isChildOrg && !isGlobalUser) { if (isIntegrationPartner && isChildOrg && !isGlobalUser) {
setCurIndex(1); setCurIndex(isCloud ? 1 : 2);
}else { }else {
if (userdata && userdata.active_org && userdata.active_org.role === 'admin') { if (userdata && userdata.active_org && userdata.active_org.role === 'admin') {
setCurIndex(2); setCurIndex(isCloud ? 2 : 3);
}else { }else {
setCurIndex(1); setCurIndex(isCloud ? 1 : 2);
} }
} }
} else if (decodedTabName === 'billingstats' || decodedTabName === 'billing') { } else if (decodedTabName === 'billingstats' || decodedTabName === 'billing') {
if (isIntegrationPartner && isChildOrg && !isGlobalUser) { if (isIntegrationPartner && isChildOrg && !isGlobalUser) {
setCurIndex(2); setCurIndex(isCloud ? 2 : 3);
} else { } else {
if (userdata && userdata?.active_org && userdata?.active_org?.role === 'admin') { if (userdata && userdata?.active_org && userdata?.active_org?.role === 'admin') {
setCurIndex(3); setCurIndex(isCloud ? 3 : 4);
} else { } else {
setCurIndex(2); setCurIndex(isCloud ? 2 : 3);
} }
} }
} else if (decodedTabName === 'branding') { } else if (decodedTabName === 'branding') {
setCurIndex(4); setCurIndex(isCloud ? 4 : 5);
} }
// else if (decodedTabName === 'analytics') { // else if (decodedTabName === 'analytics') {
// setCurIndex(5); // setCurIndex(5);
@@ -120,6 +127,9 @@ const OrganizationTab = (props) => {
switch (selectedTab) { switch (selectedTab) {
case 'org_config': case 'org_config':
return <EditOrgTab isCloud={isCloud} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />; return <EditOrgTab isCloud={isCloud} selectedStatus={selectedStatus} setSelectedStatus={setSelectedStatus} handleStatusChange={handleStatusChange} handleEditOrg={handleEditOrg} userdata={userdata} globalUrl={globalUrl} serverside={serverside} handleGetOrg={handleGetOrg} setSelectedOrganization={setSelectedOrganization} selectedOrganization={selectedOrganization} />;
case 'prodstatus':
case 'productionstatus':
return isCloud ? null : <ProductionStatus selectedOrganization={selectedOrganization} userdata={userdata} isCloud={isCloud} theme={theme} />;
case 'sso': case 'sso':
return <SSOTab isEditOrgTab={true} globalUrl={globalUrl} isCloud={isCloud} userdata={userdata} handleEditOrg={handleEditOrg} selectedOrganization={selectedOrganization}/> return <SSOTab isEditOrgTab={true} globalUrl={globalUrl} isCloud={isCloud} userdata={userdata} handleEditOrg={handleEditOrg} selectedOrganization={selectedOrganization}/>
case `notifications`: case `notifications`:
@@ -248,7 +258,7 @@ const OrganizationTab = (props) => {
</Tooltip> </Tooltip>
))} ))}
</div> </div>
<div style={{ display: 'flex', justifyContent: 'center', width: "100%", height: "100%", boxSizing:'border-box'}}> <div style={{ display: 'flex', justifyContent: 'flex-start', width: "100%", height: "100%", boxSizing:'border-box'}}>
{renderContent()} {renderContent()}
</div> </div>
</div> </div>
@@ -256,3 +266,47 @@ const OrganizationTab = (props) => {
}; };
export default OrganizationTab; export default OrganizationTab;
const ProductionStatus = ({ selectedOrganization, userdata, isCloud, theme }) => {
var isProdStatusOn;
if (selectedOrganization !== undefined && selectedOrganization?.subscriptions !== undefined && selectedOrganization?.subscriptions[0] !== undefined) {
isProdStatusOn = selectedOrganization?.subscriptions[0]?.name?.toLowerCase()?.includes("enterprise") && selectedOrganization?.subscriptions[0]?.active;
} else {
isProdStatusOn = false;
}
const rows = [
{ label: 'Licensed', ok: isProdStatusOn },
{ label: 'High Scale', ok: isProdStatusOn },
{ label: 'High Availability', ok: isProdStatusOn },
{ label: 'Stable Configuration', ok: isProdStatusOn },
{ label: 'Robust Infrastructure', ok: isProdStatusOn },
];
return (
<div style={{ width: '100%', maxWidth: 800, padding: '24px 24px 24px 34px', height: 445 , display: 'flex', flexDirection: 'column', alignItems: 'flex-start'}}>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 8 }}>
<Typography variant="h5" style={{ fontWeight: 600, fontFamily: theme.typography.fontFamily }}>Production Status</Typography>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 14px', borderRadius: 16, background: isProdStatusOn ? 'rgba(43,192,126,0.1)' : 'rgba(253,76,98,0.1)' }}>
<span style={{ width: 8, height: 8, borderRadius: 999, background: isProdStatusOn ? '#2BC07E' : '#FD4C62' }} />
<Typography variant="caption" style={{ color: isProdStatusOn ? '#2BC07E' : '#FD4C62', fontWeight: 400, fontFamily: theme.typography.fontFamily }}>{isProdStatusOn ? "ON" : "OFF"}</Typography>
</div>
</div>
<Typography variant="body2" color="textSecondary" style={{ marginBottom: 18, fontFamily: theme.typography.fontFamily }}>
Monitor your production status to stay informed about available features.
</Typography>
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
{rows.map((row) => (
<div key={row.label} style={{ display: 'flex', alignItems: 'center', gap: 12, fontFamily: theme.typography.fontFamily }}>
{row.ok ? (
<CheckCircleIcon style={{ color: '#2BC07E' }} />
) : (
<CancelIcon style={{ color: '#FD4C62' }} />
)}
<Typography variant="body1" style={{ fontWeight: 400, fontFamily: theme.typography.fontFamily }}>{row.label}</Typography>
</div>
))}
</div>
</div>
);
};