mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-07 14:44:52 +02:00
Add new limits to the billing page
This commit is contained in:
@@ -1915,6 +1915,9 @@
|
|||||||
"billingDomains": "Domains",
|
"billingDomains": "Domains",
|
||||||
"billingOrganizations": "Orgs",
|
"billingOrganizations": "Orgs",
|
||||||
"billingRemoteExitNodes": "Remote Nodes",
|
"billingRemoteExitNodes": "Remote Nodes",
|
||||||
|
"billingPublicResources": "Public Resources",
|
||||||
|
"billingPrivateResources": "Private Resources",
|
||||||
|
"billingMachineClients": "Machine Clients",
|
||||||
"billingNoLimitConfigured": "No limit configured",
|
"billingNoLimitConfigured": "No limit configured",
|
||||||
"billingEstimatedPeriod": "Estimated Billing Period",
|
"billingEstimatedPeriod": "Estimated Billing Period",
|
||||||
"billingIncludedUsage": "Included Usage",
|
"billingIncludedUsage": "Included Usage",
|
||||||
@@ -1943,6 +1946,9 @@
|
|||||||
"billingUsersInfo": "How many users you can use",
|
"billingUsersInfo": "How many users you can use",
|
||||||
"billingDomainInfo": "How many domains you can use",
|
"billingDomainInfo": "How many domains you can use",
|
||||||
"billingRemoteExitNodesInfo": "How many remote nodes you can use",
|
"billingRemoteExitNodesInfo": "How many remote nodes you can use",
|
||||||
|
"billingPublicResourcesInfo": "How many public resources you can use",
|
||||||
|
"billingPrivateResourcesInfo": "How many private resources you can use",
|
||||||
|
"billingMachineClientsInfo": "How many machine clients you can use",
|
||||||
"billingLicenseKeys": "License Keys",
|
"billingLicenseKeys": "License Keys",
|
||||||
"billingLicenseKeysDescription": "Manage your license key subscriptions",
|
"billingLicenseKeysDescription": "Manage your license key subscriptions",
|
||||||
"billingLicenseSubscription": "License Subscription",
|
"billingLicenseSubscription": "License Subscription",
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ export async function getFeatureDisplayName(
|
|||||||
return "Remote Exit Nodes";
|
return "Remote Exit Nodes";
|
||||||
case LimitId.ORGANIZATIONS:
|
case LimitId.ORGANIZATIONS:
|
||||||
return "Organizations";
|
return "Organizations";
|
||||||
|
case LimitId.PUBLIC_RESOURCES:
|
||||||
|
return "Public Resources";
|
||||||
|
case LimitId.PRIVATE_RESOURCES:
|
||||||
|
return "Private Resources";
|
||||||
|
case LimitId.MACHINE_CLIENTS:
|
||||||
|
return "Machine Clients";
|
||||||
case LimitId.TIER1:
|
case LimitId.TIER1:
|
||||||
return "Home Lab";
|
return "Home Lab";
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -104,6 +104,18 @@ export async function getOrgUsage(
|
|||||||
orgId,
|
orgId,
|
||||||
LimitId.ORGANIZATIONS
|
LimitId.ORGANIZATIONS
|
||||||
);
|
);
|
||||||
|
const publicResources = await usageService.getUsage(
|
||||||
|
orgId,
|
||||||
|
LimitId.PUBLIC_RESOURCES
|
||||||
|
);
|
||||||
|
const privateResources = await usageService.getUsage(
|
||||||
|
orgId,
|
||||||
|
LimitId.PRIVATE_RESOURCES
|
||||||
|
);
|
||||||
|
const machineClients = await usageService.getUsage(
|
||||||
|
orgId,
|
||||||
|
LimitId.MACHINE_CLIENTS
|
||||||
|
);
|
||||||
// const egressData = await usageService.getUsage(
|
// const egressData = await usageService.getUsage(
|
||||||
// orgId,
|
// orgId,
|
||||||
// FeatureId.EGRESS_DATA_MB
|
// FeatureId.EGRESS_DATA_MB
|
||||||
@@ -127,6 +139,15 @@ export async function getOrgUsage(
|
|||||||
if (organizations) {
|
if (organizations) {
|
||||||
usageData.push(organizations);
|
usageData.push(organizations);
|
||||||
}
|
}
|
||||||
|
if (publicResources) {
|
||||||
|
usageData.push(publicResources);
|
||||||
|
}
|
||||||
|
if (privateResources) {
|
||||||
|
usageData.push(privateResources);
|
||||||
|
}
|
||||||
|
if (machineClients) {
|
||||||
|
usageData.push(machineClients);
|
||||||
|
}
|
||||||
|
|
||||||
const orgLimits = await db
|
const orgLimits = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -158,6 +158,9 @@ const tierLimits: Record<
|
|||||||
domains: number;
|
domains: number;
|
||||||
remoteNodes: number;
|
remoteNodes: number;
|
||||||
organizations: number;
|
organizations: number;
|
||||||
|
publicResources: number;
|
||||||
|
privateResources: number;
|
||||||
|
machineClients: number;
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
basic: {
|
basic: {
|
||||||
@@ -165,35 +168,50 @@ const tierLimits: Record<
|
|||||||
sites: freeLimitSet[LimitId.SITES]?.value ?? 0,
|
sites: freeLimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: freeLimitSet[LimitId.DOMAINS]?.value ?? 0,
|
domains: freeLimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: freeLimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: freeLimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: freeLimitSet[LimitId.ORGANIZATIONS]?.value ?? 0
|
organizations: freeLimitSet[LimitId.ORGANIZATIONS]?.value ?? 0,
|
||||||
|
publicResources: freeLimitSet[LimitId.PUBLIC_RESOURCES]?.value ?? 0,
|
||||||
|
privateResources: freeLimitSet[LimitId.PRIVATE_RESOURCES]?.value ?? 0,
|
||||||
|
machineClients: freeLimitSet[LimitId.MACHINE_CLIENTS]?.value ?? 0
|
||||||
},
|
},
|
||||||
tier1: {
|
tier1: {
|
||||||
users: tier1LimitSet[LimitId.USERS]?.value ?? 0,
|
users: tier1LimitSet[LimitId.USERS]?.value ?? 0,
|
||||||
sites: tier1LimitSet[LimitId.SITES]?.value ?? 0,
|
sites: tier1LimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: tier1LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
domains: tier1LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: tier1LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: tier1LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: tier1LimitSet[LimitId.ORGANIZATIONS]?.value ?? 0
|
organizations: tier1LimitSet[LimitId.ORGANIZATIONS]?.value ?? 0,
|
||||||
|
publicResources: tier1LimitSet[LimitId.PUBLIC_RESOURCES]?.value ?? 0,
|
||||||
|
privateResources: tier1LimitSet[LimitId.PRIVATE_RESOURCES]?.value ?? 0,
|
||||||
|
machineClients: tier1LimitSet[LimitId.MACHINE_CLIENTS]?.value ?? 0
|
||||||
},
|
},
|
||||||
tier2: {
|
tier2: {
|
||||||
users: tier2LimitSet[LimitId.USERS]?.value ?? 0,
|
users: tier2LimitSet[LimitId.USERS]?.value ?? 0,
|
||||||
sites: tier2LimitSet[LimitId.SITES]?.value ?? 0,
|
sites: tier2LimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: tier2LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
domains: tier2LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: tier2LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: tier2LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: tier2LimitSet[LimitId.ORGANIZATIONS]?.value ?? 0
|
organizations: tier2LimitSet[LimitId.ORGANIZATIONS]?.value ?? 0,
|
||||||
|
publicResources: tier2LimitSet[LimitId.PUBLIC_RESOURCES]?.value ?? 0,
|
||||||
|
privateResources: tier2LimitSet[LimitId.PRIVATE_RESOURCES]?.value ?? 0,
|
||||||
|
machineClients: tier2LimitSet[LimitId.MACHINE_CLIENTS]?.value ?? 0
|
||||||
},
|
},
|
||||||
tier3: {
|
tier3: {
|
||||||
users: tier3LimitSet[LimitId.USERS]?.value ?? 0,
|
users: tier3LimitSet[LimitId.USERS]?.value ?? 0,
|
||||||
sites: tier3LimitSet[LimitId.SITES]?.value ?? 0,
|
sites: tier3LimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: tier3LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
domains: tier3LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: tier3LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: tier3LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: tier3LimitSet[LimitId.ORGANIZATIONS]?.value ?? 0
|
organizations: tier3LimitSet[LimitId.ORGANIZATIONS]?.value ?? 0,
|
||||||
|
publicResources: tier3LimitSet[LimitId.PUBLIC_RESOURCES]?.value ?? 0,
|
||||||
|
privateResources: tier3LimitSet[LimitId.PRIVATE_RESOURCES]?.value ?? 0,
|
||||||
|
machineClients: tier3LimitSet[LimitId.MACHINE_CLIENTS]?.value ?? 0
|
||||||
},
|
},
|
||||||
enterprise: {
|
enterprise: {
|
||||||
users: 0, // Custom for enterprise
|
users: 0, // Custom for enterprise
|
||||||
sites: 0, // Custom for enterprise
|
sites: 0, // Custom for enterprise
|
||||||
domains: 0, // Custom for enterprise
|
domains: 0, // Custom for enterprise
|
||||||
remoteNodes: 0, // Custom for enterprise
|
remoteNodes: 0, // Custom for enterprise
|
||||||
organizations: 0 // Custom for enterprise
|
organizations: 0, // Custom for enterprise
|
||||||
|
publicResources: 0, // Custom for enterprise
|
||||||
|
privateResources: 0, // Custom for enterprise
|
||||||
|
machineClients: 0 // Custom for enterprise
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -234,6 +252,9 @@ export default function BillingPage() {
|
|||||||
const DOMAINS = "domains";
|
const DOMAINS = "domains";
|
||||||
const REMOTE_EXIT_NODES = "remoteExitNodes";
|
const REMOTE_EXIT_NODES = "remoteExitNodes";
|
||||||
const ORGINIZATIONS = "organizations";
|
const ORGINIZATIONS = "organizations";
|
||||||
|
const PUBLIC_RESOURCES = "publicResources";
|
||||||
|
const PRIVATE_RESOURCES = "privateResources";
|
||||||
|
const MACHINE_CLIENTS = "machineClients";
|
||||||
|
|
||||||
// Confirmation dialog state
|
// Confirmation dialog state
|
||||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||||
@@ -797,6 +818,45 @@ export default function BillingPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check public resources
|
||||||
|
const publicResourcesUsage = getUsageValue(PUBLIC_RESOURCES);
|
||||||
|
if (
|
||||||
|
limits.publicResources > 0 &&
|
||||||
|
publicResourcesUsage > limits.publicResources
|
||||||
|
) {
|
||||||
|
violations.push({
|
||||||
|
feature: "Public Resources",
|
||||||
|
currentUsage: publicResourcesUsage,
|
||||||
|
newLimit: limits.publicResources
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check private resources
|
||||||
|
const privateResourcesUsage = getUsageValue(PRIVATE_RESOURCES);
|
||||||
|
if (
|
||||||
|
limits.privateResources > 0 &&
|
||||||
|
privateResourcesUsage > limits.privateResources
|
||||||
|
) {
|
||||||
|
violations.push({
|
||||||
|
feature: "Private Resources",
|
||||||
|
currentUsage: privateResourcesUsage,
|
||||||
|
newLimit: limits.privateResources
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check machine clients
|
||||||
|
const machineClientsUsage = getUsageValue(MACHINE_CLIENTS);
|
||||||
|
if (
|
||||||
|
limits.machineClients > 0 &&
|
||||||
|
machineClientsUsage > limits.machineClients
|
||||||
|
) {
|
||||||
|
violations.push({
|
||||||
|
feature: "Machine Clients",
|
||||||
|
currentUsage: machineClientsUsage,
|
||||||
|
newLimit: limits.machineClients
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return violations;
|
return violations;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1025,7 +1085,7 @@ export default function BillingPage() {
|
|||||||
<div className="text-sm text-muted-foreground mb-3">
|
<div className="text-sm text-muted-foreground mb-3">
|
||||||
{t("billingMaximumLimits") || "Maximum Limits"}
|
{t("billingMaximumLimits") || "Maximum Limits"}
|
||||||
</div>
|
</div>
|
||||||
<InfoSections cols={5}>
|
<InfoSections cols={8}>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle className="flex items-center gap-1 text-xs">
|
<InfoSectionTitle className="flex items-center gap-1 text-xs">
|
||||||
{t("billingUsers") || "Users"}
|
{t("billingUsers") || "Users"}
|
||||||
@@ -1308,6 +1368,168 @@ export default function BillingPage() {
|
|||||||
)}
|
)}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle className="flex items-center gap-1 text-xs">
|
||||||
|
{t("billingPublicResources") ||
|
||||||
|
"Public Resources"}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent className="text-sm">
|
||||||
|
{isOverLimit(PUBLIC_RESOURCES) ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger className="flex items-center gap-1">
|
||||||
|
<AlertTriangle className="h-3 w-3 text-orange-400" />
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-orange-600 dark:text-orange-400 font-medium"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{getLimitValue(
|
||||||
|
PUBLIC_RESOURCES
|
||||||
|
) ??
|
||||||
|
t(
|
||||||
|
"billingUnlimited"
|
||||||
|
) ??
|
||||||
|
"∞"}
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
"billingUsageExceedsLimit",
|
||||||
|
{
|
||||||
|
current:
|
||||||
|
getUsageValue(
|
||||||
|
PUBLIC_RESOURCES
|
||||||
|
),
|
||||||
|
limit:
|
||||||
|
getLimitValue(
|
||||||
|
PUBLIC_RESOURCES
|
||||||
|
) ?? 0
|
||||||
|
}
|
||||||
|
) ||
|
||||||
|
`Current usage (${getUsageValue(PUBLIC_RESOURCES)}) exceeds limit (${getLimitValue(PUBLIC_RESOURCES)})`}
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{getLimitValue(
|
||||||
|
PUBLIC_RESOURCES
|
||||||
|
) ??
|
||||||
|
t("billingUnlimited") ??
|
||||||
|
"∞"}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle className="flex items-center gap-1 text-xs">
|
||||||
|
{t("billingPrivateResources") ||
|
||||||
|
"Private Resources"}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent className="text-sm">
|
||||||
|
{isOverLimit(PRIVATE_RESOURCES) ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger className="flex items-center gap-1">
|
||||||
|
<AlertTriangle className="h-3 w-3 text-orange-400" />
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-orange-600 dark:text-orange-400 font-medium"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{getLimitValue(
|
||||||
|
PRIVATE_RESOURCES
|
||||||
|
) ??
|
||||||
|
t(
|
||||||
|
"billingUnlimited"
|
||||||
|
) ??
|
||||||
|
"∞"}
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
"billingUsageExceedsLimit",
|
||||||
|
{
|
||||||
|
current:
|
||||||
|
getUsageValue(
|
||||||
|
PRIVATE_RESOURCES
|
||||||
|
),
|
||||||
|
limit:
|
||||||
|
getLimitValue(
|
||||||
|
PRIVATE_RESOURCES
|
||||||
|
) ?? 0
|
||||||
|
}
|
||||||
|
) ||
|
||||||
|
`Current usage (${getUsageValue(PRIVATE_RESOURCES)}) exceeds limit (${getLimitValue(PRIVATE_RESOURCES)})`}
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{getLimitValue(
|
||||||
|
PRIVATE_RESOURCES
|
||||||
|
) ??
|
||||||
|
t("billingUnlimited") ??
|
||||||
|
"∞"}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle className="flex items-center gap-1 text-xs">
|
||||||
|
{t("billingMachineClients") ||
|
||||||
|
"Machine Clients"}
|
||||||
|
</InfoSectionTitle>
|
||||||
|
<InfoSectionContent className="text-sm">
|
||||||
|
{isOverLimit(MACHINE_CLIENTS) ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger className="flex items-center gap-1">
|
||||||
|
<AlertTriangle className="h-3 w-3 text-orange-400" />
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-orange-600 dark:text-orange-400 font-medium"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{getLimitValue(
|
||||||
|
MACHINE_CLIENTS
|
||||||
|
) ??
|
||||||
|
t(
|
||||||
|
"billingUnlimited"
|
||||||
|
) ??
|
||||||
|
"∞"}
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>
|
||||||
|
{t(
|
||||||
|
"billingUsageExceedsLimit",
|
||||||
|
{
|
||||||
|
current:
|
||||||
|
getUsageValue(
|
||||||
|
MACHINE_CLIENTS
|
||||||
|
),
|
||||||
|
limit:
|
||||||
|
getLimitValue(
|
||||||
|
MACHINE_CLIENTS
|
||||||
|
) ?? 0
|
||||||
|
}
|
||||||
|
) ||
|
||||||
|
`Current usage (${getUsageValue(MACHINE_CLIENTS)}) exceeds limit (${getLimitValue(MACHINE_CLIENTS)})`}
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{getLimitValue(
|
||||||
|
MACHINE_CLIENTS
|
||||||
|
) ??
|
||||||
|
t("billingUnlimited") ??
|
||||||
|
"∞"}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
</InfoSections>
|
</InfoSections>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1507,6 +1729,45 @@ export default function BillingPage() {
|
|||||||
"Remote Nodes"}
|
"Remote Nodes"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/50 shrink-0" />
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
tierLimits[
|
||||||
|
pendingTier.tier
|
||||||
|
].publicResources
|
||||||
|
}{" "}
|
||||||
|
{t(
|
||||||
|
"billingPublicResources"
|
||||||
|
) || "Public Resources"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/50 shrink-0" />
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
tierLimits[
|
||||||
|
pendingTier.tier
|
||||||
|
].privateResources
|
||||||
|
}{" "}
|
||||||
|
{t(
|
||||||
|
"billingPrivateResources"
|
||||||
|
) || "Private Resources"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/50 shrink-0" />
|
||||||
|
<span>
|
||||||
|
{
|
||||||
|
tierLimits[
|
||||||
|
pendingTier.tier
|
||||||
|
].machineClients
|
||||||
|
}{" "}
|
||||||
|
{t(
|
||||||
|
"billingMachineClients"
|
||||||
|
) || "Machine Clients"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user