mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-16 15:50:09 +02:00
Add license tiers
This commit is contained in:
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "No sites found.",
|
"sitesNotFound": "No sites found.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolin",
|
"pangolinServerAdmin": "Server Admin - Pangolin",
|
||||||
"licenseTierProfessional": "Professional License",
|
"licenseTierProfessional": "Professional License",
|
||||||
"licenseTierEnterprise": "Enterprise License",
|
"licenseTierEnterprise": "Enterprise",
|
||||||
"licenseTierPersonal": "Personal License",
|
"licenseTierPersonal": "Personal",
|
||||||
|
"licenseTierTier1": "Starter",
|
||||||
|
"licenseTierTier2": "Scale",
|
||||||
"licensed": "Licensed",
|
"licensed": "Licensed",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export enum LicenseId {
|
export enum LicenseId {
|
||||||
SMALL_LICENSE = "small_license",
|
TIER1 = "tier1",
|
||||||
BIG_LICENSE = "big_license"
|
TIER2 = "tier2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicensePriceSet = {
|
export type LicensePriceSet = {
|
||||||
@@ -9,15 +9,15 @@ export type LicensePriceSet = {
|
|||||||
|
|
||||||
export const licensePriceSet: LicensePriceSet = {
|
export const licensePriceSet: LicensePriceSet = {
|
||||||
// Free license matches the freeLimitSet
|
// Free license matches the freeLimitSet
|
||||||
[LicenseId.SMALL_LICENSE]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
|
[LicenseId.TIER1]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
|
||||||
[LicenseId.BIG_LICENSE]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
|
[LicenseId.TIER2]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const licensePriceSetSandbox: LicensePriceSet = {
|
export const licensePriceSetSandbox: LicensePriceSet = {
|
||||||
// Free license matches the freeLimitSet
|
// Free license matches the freeLimitSet
|
||||||
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
|
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
|
||||||
[LicenseId.SMALL_LICENSE]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
|
[LicenseId.TIER1]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
|
||||||
[LicenseId.BIG_LICENSE]: "price_1SxDy0DCpkOb237BWJxrxYkl"
|
[LicenseId.TIER2]: "price_1SxDy0DCpkOb237BWJxrxYkl"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLicensePriceSet(
|
export function getLicensePriceSet(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { getTraefikConfig } from "#dynamic/lib/traefik";
|
|||||||
import { getValidCertificatesForDomains } from "@server/lib/certificates";
|
import { getValidCertificatesForDomains } from "@server/lib/certificates";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import license from "#dynamic/license/license";
|
||||||
|
|
||||||
export class TraefikConfigManager {
|
export class TraefikConfigManager {
|
||||||
private intervalId: NodeJS.Timeout | null = null;
|
private intervalId: NodeJS.Timeout | null = null;
|
||||||
@@ -357,7 +358,11 @@ export class TraefikConfigManager {
|
|||||||
this.lastActiveDomains = new Set(domains);
|
this.lastActiveDomains = new Set(domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.CERT_MODE === "pangolin" && build != "oss") {
|
if (
|
||||||
|
process.env.CERT_MODE === "pangolin" &&
|
||||||
|
build != "oss" &&
|
||||||
|
(await license.hasTier(["personal", "tier2", "enterprise"]))
|
||||||
|
) {
|
||||||
// Scan current local certificate state
|
// Scan current local certificate state
|
||||||
this.lastLocalCertificateState =
|
this.lastLocalCertificateState =
|
||||||
await this.scanLocalCertificateState();
|
await this.scanLocalCertificateState();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { setHostMeta } from "@server/lib/hostMeta";
|
|||||||
const keyTypes = ["host"] as const;
|
const keyTypes = ["host"] as const;
|
||||||
export type LicenseKeyType = (typeof keyTypes)[number];
|
export type LicenseKeyType = (typeof keyTypes)[number];
|
||||||
|
|
||||||
const keyTiers = ["personal", "enterprise"] as const;
|
const keyTiers = ["personal", "enterprise", "tier1", "tier2"] as const;
|
||||||
export type LicenseKeyTier = (typeof keyTiers)[number];
|
export type LicenseKeyTier = (typeof keyTiers)[number];
|
||||||
|
|
||||||
export type LicenseStatus = {
|
export type LicenseStatus = {
|
||||||
@@ -50,6 +50,10 @@ export class License {
|
|||||||
public async isUnlocked() {
|
public async isUnlocked() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async hasTier(tier: LicenseKeyTier[]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await setHostMeta();
|
await setHostMeta();
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ export class JobScheduler {
|
|||||||
label: string
|
label: string
|
||||||
): () => Promise<void> {
|
): () => Promise<void> {
|
||||||
return async () => {
|
return async () => {
|
||||||
if (!(await license.isUnlocked())) {
|
if (!(await license.hasTier(["personal", "tier2", "enterprise"]))) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Skipping ${label} tick - license is not subscribed`
|
`Skipping ${label} tick - requires a tier2 license`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ export class AuthoritativeDNSServer {
|
|||||||
private allDomains: Set<string> = new Set();
|
private allDomains: Set<string> = new Set();
|
||||||
private domainRefreshInterval: NodeJS.Timeout | null = null;
|
private domainRefreshInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
// Cached license/subscription status. license.isUnlocked() does a DB
|
// Cached license/plan status - only a tier2 license unlocks the DNS
|
||||||
// round-trip on every call, so it can't be checked per-query on a UDP
|
// server. license.hasPlan() does a DB round-trip on every call, so it
|
||||||
// server that may see very high query volume - instead it's polled on
|
// can't be checked per-query on a UDP server that may see very high
|
||||||
// the same cadence as the domain set refresh and read from memory here.
|
// query volume - instead it's polled on the same cadence as the domain
|
||||||
|
// set refresh and read from memory here.
|
||||||
private isLicensed: boolean = false;
|
private isLicensed: boolean = false;
|
||||||
private licenseRefreshInterval: NodeJS.Timeout | null = null;
|
private licenseRefreshInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ export class AuthoritativeDNSServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isLicensed) {
|
if (!this.isLicensed) {
|
||||||
logger.debug("Refusing DNS query - license is not subscribed");
|
logger.debug("Refusing DNS query - requires a tier2 license");
|
||||||
// REFUSED (rcode=5) indicates a policy refusal by this nameserver.
|
// REFUSED (rcode=5) indicates a policy refusal by this nameserver.
|
||||||
this.sendResponse(packet, [], rinfo, false, 5, []);
|
this.sendResponse(packet, [], rinfo, false, 5, []);
|
||||||
return;
|
return;
|
||||||
@@ -1042,7 +1043,11 @@ export class AuthoritativeDNSServer {
|
|||||||
|
|
||||||
private async refreshLicenseStatus(): Promise<void> {
|
private async refreshLicenseStatus(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.isLicensed = await license.isUnlocked();
|
this.isLicensed = await license.hasTier([
|
||||||
|
"personal",
|
||||||
|
"tier2",
|
||||||
|
"enterprise"
|
||||||
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to refresh license status:", error);
|
logger.error("Failed to refresh license status:", error);
|
||||||
this.isLicensed = false;
|
this.isLicensed = false;
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import {
|
|||||||
getValidCertificatesForDomains
|
getValidCertificatesForDomains
|
||||||
} from "@server/lib/certificates";
|
} from "@server/lib/certificates";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import license from "#private/license/license";
|
||||||
import regionalCache from "#private/lib/cache";
|
import regionalCache from "#private/lib/cache";
|
||||||
import { TargetWithSite } from "@server/lib/traefik/types";
|
import { TargetWithSite } from "@server/lib/traefik/types";
|
||||||
import { buildWildcardTls } from "@server/lib/traefik/certResolver";
|
import { buildWildcardTls } from "@server/lib/traefik/certResolver";
|
||||||
@@ -395,8 +396,15 @@ export async function getTraefikConfig(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Pangolin-managed DNS-01/ACME cert mode requires either a tier1
|
||||||
|
// license (self-hosted) or a saas build - otherwise fall back to
|
||||||
|
// Traefik's own cert resolvers (buildWildcardTls) throughout.
|
||||||
|
const pangolinCertModeEnabled =
|
||||||
|
privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin" &&
|
||||||
|
(await license.hasTier(["personal", "tier2", "enterprise"]));
|
||||||
|
|
||||||
let validCerts: CertificateResult[] = [];
|
let validCerts: CertificateResult[] = [];
|
||||||
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
|
if (pangolinCertModeEnabled) {
|
||||||
// create a list of all domains to get certs for
|
// create a list of all domains to get certs for
|
||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
for (const resource of resourcesMap.values()) {
|
for (const resource of resourcesMap.values()) {
|
||||||
@@ -522,10 +530,7 @@ export async function getTraefikConfig(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tls = {};
|
let tls = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
tls = buildWildcardTls({
|
tls = buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain: !!resource.subdomain,
|
hasSubdomain: !!resource.subdomain,
|
||||||
@@ -790,10 +795,7 @@ export async function getTraefikConfig(
|
|||||||
domainCertResolver,
|
domainCertResolver,
|
||||||
preferWildcardCert
|
preferWildcardCert
|
||||||
}) => {
|
}) => {
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
return buildWildcardTls({
|
return buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain,
|
hasSubdomain,
|
||||||
@@ -834,10 +836,7 @@ export async function getTraefikConfig(
|
|||||||
maintenancePageUiUrl,
|
maintenancePageUiUrl,
|
||||||
redirectHttpsMiddlewareName,
|
redirectHttpsMiddlewareName,
|
||||||
resolveTls: (fullDomain) => {
|
resolveTls: (fullDomain) => {
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// siteResource aliases don't have a per-domain cert
|
// siteResource aliases don't have a per-domain cert
|
||||||
// resolver stored, so always fall back to the global
|
// resolver stored, so always fall back to the global
|
||||||
// defaults.
|
// defaults.
|
||||||
@@ -928,10 +927,7 @@ export async function getTraefikConfig(
|
|||||||
const rule = buildHostRule(fullDomain, ir.wildcard);
|
const rule = buildHostRule(fullDomain, ir.wildcard);
|
||||||
|
|
||||||
let tls: any = {};
|
let tls: any = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
tls = buildWildcardTls({
|
tls = buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain: !!ir.subdomain,
|
hasSubdomain: !!ir.subdomain,
|
||||||
@@ -1011,10 +1007,7 @@ export async function getTraefikConfig(
|
|||||||
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
|
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
|
||||||
|
|
||||||
let tls: any = {};
|
let tls: any = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// siteResource aliases don't have a per-domain cert
|
// siteResource aliases don't have a per-domain cert
|
||||||
// resolver stored, so always fall back to the global
|
// resolver stored, so always fall back to the global
|
||||||
// defaults.
|
// defaults.
|
||||||
@@ -1088,7 +1081,7 @@ export async function getTraefikConfig(
|
|||||||
.where(eq(exitNodes.exitNodeId, exitNodeId));
|
.where(eq(exitNodes.exitNodeId, exitNodeId));
|
||||||
|
|
||||||
let validCertsLoginPages: CertificateResult[] = [];
|
let validCertsLoginPages: CertificateResult[] = [];
|
||||||
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
|
if (pangolinCertModeEnabled) {
|
||||||
// create a list of all domains to get certs for
|
// create a list of all domains to get certs for
|
||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
for (const lp of exitNodeLoginPages) {
|
for (const lp of exitNodeLoginPages) {
|
||||||
@@ -1133,10 +1126,7 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tls = {};
|
const tls = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// TODO: we need to add the wildcard logic here too
|
// TODO: we need to add the wildcard logic here too
|
||||||
} else {
|
} else {
|
||||||
// find a cert that matches the full domain, if not continue
|
// find a cert that matches the full domain, if not continue
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
LicenseStatus
|
LicenseStatus
|
||||||
} from "@server/license/license";
|
} from "@server/license/license";
|
||||||
import { setHostMeta } from "@server/lib/hostMeta";
|
import { setHostMeta } from "@server/lib/hostMeta";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
type ActivateLicenseKeyAPIResponse = {
|
type ActivateLicenseKeyAPIResponse = {
|
||||||
data: {
|
data: {
|
||||||
@@ -119,6 +120,9 @@ LQIDAQAB
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async isUnlocked(): Promise<boolean> {
|
public async isUnlocked(): Promise<boolean> {
|
||||||
|
if (build == "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const status = await this.check();
|
const status = await this.check();
|
||||||
if (status.isHostLicensed) {
|
if (status.isHostLicensed) {
|
||||||
if (status.isLicenseValid) {
|
if (status.isLicenseValid) {
|
||||||
@@ -128,6 +132,20 @@ LQIDAQAB
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async hasTier(tier: LicenseKeyTier[]): Promise<boolean> {
|
||||||
|
if (build == "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const status = await this.check();
|
||||||
|
if (status.isHostLicensed && status.isLicenseValid) {
|
||||||
|
return (
|
||||||
|
status.tier !== undefined &&
|
||||||
|
tier.includes(status.tier as LicenseKeyTier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async check(): Promise<LicenseStatus> {
|
public async check(): Promise<LicenseStatus> {
|
||||||
// If a check is already in progress, return the last known status
|
// If a check is already in progress, return the last known status
|
||||||
if (this.checkInProgress) {
|
if (this.checkInProgress) {
|
||||||
@@ -135,8 +153,7 @@ LQIDAQAB
|
|||||||
"License check already in progress, returning last known status"
|
"License check already in progress, returning last known status"
|
||||||
);
|
);
|
||||||
const lastStatus = this.statusCache.get(this.statusKey) as
|
const lastStatus = this.statusCache.get(this.statusKey) as
|
||||||
| LicenseStatus
|
LicenseStatus | undefined;
|
||||||
| undefined;
|
|
||||||
if (lastStatus) {
|
if (lastStatus) {
|
||||||
return lastStatus;
|
return lastStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,15 +222,16 @@ export async function handleSubscriptionCreated(
|
|||||||
|
|
||||||
let numUsers: number;
|
let numUsers: number;
|
||||||
let numSites: number;
|
let numSites: number;
|
||||||
|
let tier = "enterprise";
|
||||||
|
|
||||||
if (subscriptionPriceId === priceSet[LicenseId.SMALL_LICENSE]) {
|
if (subscriptionPriceId === priceSet[LicenseId.TIER1]) {
|
||||||
numUsers = 25;
|
numUsers = 25;
|
||||||
numSites = 25;
|
numSites = 25;
|
||||||
} else if (
|
tier = "tier1";
|
||||||
subscriptionPriceId === priceSet[LicenseId.BIG_LICENSE]
|
} else if (subscriptionPriceId === priceSet[LicenseId.TIER2]) {
|
||||||
) {
|
|
||||||
numUsers = 50;
|
numUsers = 50;
|
||||||
numSites = 100;
|
numSites = 100;
|
||||||
|
tier = "tier2";
|
||||||
} else {
|
} else {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
|
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
|
||||||
@@ -256,7 +257,8 @@ export async function handleSubscriptionCreated(
|
|||||||
licenseId: parseInt(licenseId),
|
licenseId: parseInt(licenseId),
|
||||||
paidFor: true,
|
paidFor: true,
|
||||||
users: numUsers,
|
users: numUsers,
|
||||||
sites: numSites
|
sites: numSites,
|
||||||
|
tier: tier
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -64,14 +64,11 @@ export async function generateNewEnterpriseLicense(
|
|||||||
|
|
||||||
const licenseData = req.body;
|
const licenseData = req.body;
|
||||||
|
|
||||||
if (
|
if (licenseData.tier != "tier2" && licenseData.tier != "tier1") {
|
||||||
licenseData.tier != "big_license" &&
|
|
||||||
licenseData.tier != "small_license"
|
|
||||||
) {
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
"Invalid tier specified. Must be either 'big_license' or 'small_license'."
|
"Invalid tier specified. Must be either 'tier2' or 'tier1'."
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -118,9 +115,7 @@ export async function generateNewEnterpriseLicense(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tier =
|
const tier =
|
||||||
licenseData.tier === "big_license"
|
licenseData.tier === "tier2" ? LicenseId.TIER2 : LicenseId.TIER1;
|
||||||
? LicenseId.BIG_LICENSE
|
|
||||||
: LicenseId.SMALL_LICENSE;
|
|
||||||
const tierPrice = getLicensePriceSet()[tier];
|
const tierPrice = getLicensePriceSet()[tier];
|
||||||
|
|
||||||
const session = await stripe!.checkout.sessions.create({
|
const session = await stripe!.checkout.sessions.create({
|
||||||
|
|||||||
@@ -252,9 +252,10 @@ export default function GenerateLicenseKeyForm({
|
|||||||
try {
|
try {
|
||||||
// Check if this is a business/enterprise license request
|
// Check if this is a business/enterprise license request
|
||||||
if (payload.useCaseType === "business") {
|
if (payload.useCaseType === "business") {
|
||||||
const response = await api.put<
|
const response = await api.put<AxiosResponse<string>>(
|
||||||
AxiosResponse<string>
|
`/org/${orgId}/license/enterprise`,
|
||||||
>(`/org/${orgId}/license/enterprise`, { ...payload, tier: "big_license" } );
|
{ ...payload, tier: "tier2" }
|
||||||
|
);
|
||||||
|
|
||||||
console.log("Checkout session response:", response.data);
|
console.log("Checkout session response:", response.data);
|
||||||
const checkoutUrl = response.data.data;
|
const checkoutUrl = response.data.data;
|
||||||
|
|||||||
@@ -201,9 +201,16 @@ export default function GenerateLicenseKeysTable({
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const tier = row.original.tier;
|
const tier = row.original.tier;
|
||||||
return tier === "enterprise"
|
switch (tier) {
|
||||||
? t("licenseTierEnterprise")
|
case "enterprise":
|
||||||
: t("licenseTierPersonal");
|
return t("licenseTierEnterprise");
|
||||||
|
case "tier1":
|
||||||
|
return t("licenseTierTier1");
|
||||||
|
case "tier2":
|
||||||
|
return t("licenseTierTier2");
|
||||||
|
default:
|
||||||
|
return t("licenseTierPersonal");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,9 +100,16 @@ export function LicenseKeysDataTable({
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const tier = row.original.tier;
|
const tier = row.original.tier;
|
||||||
return tier === "enterprise"
|
switch (tier) {
|
||||||
? t("licenseTierEnterprise")
|
case "enterprise":
|
||||||
: t("licenseTierPersonal");
|
return t("licenseTierEnterprise");
|
||||||
|
case "tier1":
|
||||||
|
return t("licenseTierTier1");
|
||||||
|
case "tier2":
|
||||||
|
return t("licenseTierTier2");
|
||||||
|
default:
|
||||||
|
return t("licenseTierPersonal");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import { InfoIcon } from "lucide-react";
|
|||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
import { useUserContext } from "@app/hooks/useUserContext";
|
||||||
|
|
||||||
const TIER_TO_LICENSE_ID = {
|
const TIER_TO_LICENSE_ID = {
|
||||||
starter: "small_license",
|
starter: "tier1",
|
||||||
scale: "big_license"
|
scale: "tier2"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type FormProps = {
|
type FormProps = {
|
||||||
|
|||||||
Reference in New Issue
Block a user