From 9de91e2f81dd1347d36903ed8f8376c32b394ac7 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 22 Sep 2026 09:39:17 -0400 Subject: [PATCH] Display the quantities again --- messages/en-US.json | 8 + src/app/admin/license/page.tsx | 210 ++++++++++++++++-------- src/components/LicenseKeysDataTable.tsx | 48 ++++++ 3 files changed, 201 insertions(+), 65 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 6be431209..37d1390e6 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -590,6 +590,14 @@ "licensePurchaseSites": "Purchase Additional Sites", "licenseSitesUsedMax": "{usedSites} of {maxSites} sites used", "licenseSitesUsed": "{count, plural, =0 {# sites} one {# site} other {# sites}} in system.", + "licenseUsage": "License Usage", + "licenseUsageDescription": "View the number of users and sites licensed for this host.", + "licenseUsageSites": "Sites", + "licenseUsageUsers": "Users", + "licenseNoUserLimit": "There is no limit on the number of users using an unlicensed host.", + "licenseUsersUsedMax": "{usedUsers} of {maxUsers} users used", + "licenseUsersUsed": "{count, plural, =0 {# users} one {# user} other {# users}} in system.", + "licenseUnlimited": "Unlimited", "licensePurchaseDescription": "Choose how many sites you want to {selectedMode, select, license {purchase a license for. You can always add more sites later.} other {add to your existing license.}}", "licenseFee": "License fee", "licensePriceSite": "Price per site", diff --git a/src/app/admin/license/page.tsx b/src/app/admin/license/page.tsx index e3051664d..33cf86d96 100644 --- a/src/app/admin/license/page.tsx +++ b/src/app/admin/license/page.tsx @@ -41,6 +41,7 @@ import { SettingsSectionHeader, SettingsSectionFooter } from "@app/components/Settings"; +import { Progress } from "@app/components/ui/progress"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { ArrowRight, @@ -64,13 +65,6 @@ const ENTERPRISE_DOCS_URL = "https://docs.pangolin.net/self-host/enterprise-edition"; const ENTERPRISE_PRICING_URL = "https://pangolin.net/pricing#Self-Hosted"; -function obfuscateLicenseKey(key: string): string { - if (key.length <= 8) return key; - const firstPart = key.substring(0, 4); - const lastPart = key.substring(key.length - 4); - return `${firstPart}••••••••••••••••••••${lastPart}`; -} - export default function LicensePage() { const api = createApiClient(useEnvContext()); const [rows, setRows] = useState([]); @@ -80,7 +74,6 @@ export default function LicensePage() { useState(null); const { licenseStatus, updateLicenseStatus } = useLicenseStatusContext(); - const [hostLicense, setHostLicense] = useState(null); const [isPurchaseModalOpen, setIsPurchaseModalOpen] = useState(false); const [purchaseMode, setPurchaseMode] = useState<"license">("license"); @@ -128,12 +121,6 @@ export default function LicensePage() { ); const keys = response.data.data; setRows(keys); - const hostKey = keys.find((key) => key.type === "host"); - if (hostKey) { - setHostLicense(hostKey.licenseKey); - } else { - setHostLicense(null); - } } catch (e) { toast({ title: t("licenseErrorKeyLoad"), @@ -399,62 +386,155 @@ export default function LicensePage() { {/* */} - - - {t("licenseHost")} - - {t("licenseHostDescription")} - - -
-
- {licenseStatus?.isLicenseValid ? ( -
-
- - {t("licensed") + - `${licenseStatus?.tier === "personal" ? ` (${t("personalUseOnly")})` : ""}`} -
+ + + + {t("licenseUsage")} + + {t("licenseUsageDescription")} + + +
+
+
+ {t("licenseUsageSites")}
- ) : (
- {t("unlicensed")} + {t("licenseSitesUsed", { + count: licenseStatus?.usedSites || 0 + })} +
+ {licenseStatus?.maxSites ? ( +
+
+ + {t("licenseSitesUsedMax", { + usedSites: + licenseStatus.usedSites || + 0, + maxSites: + licenseStatus.maxSites + })} + + + {Math.round( + ((licenseStatus.usedSites || + 0) / + licenseStatus.maxSites) * + 100 + )} + % + +
+ +
+ ) : ( +
+ {t("licenseNoSiteLimit")} +
+ )} +
+
+
+ {t("licenseUsageUsers")} +
+
+ {t("licenseUsersUsed", { + count: licenseStatus?.usedUsers || 0 + })} +
+ {licenseStatus?.maxUsers ? ( +
+
+ + {t("licenseUsersUsedMax", { + usedUsers: + licenseStatus.usedUsers || + 0, + maxUsers: + licenseStatus.maxUsers + })} + + + {Math.round( + ((licenseStatus.usedUsers || + 0) / + licenseStatus.maxUsers) * + 100 + )} + % + +
+ +
+ ) : ( +
+ {t("licenseNoUserLimit")} +
+ )} +
+
+
+ + + {t("licenseHost")} + + {t("licenseHostDescription")} + + +
+
+ {licenseStatus?.isLicenseValid ? ( +
+
+ + {t("licensed") + + `${licenseStatus?.tier === "personal" ? ` (${t("personalUseOnly")})` : ""}`} +
+
+ ) : ( +
+ {t("unlicensed")} +
+ )} +
+ {licenseStatus?.hostId && ( +
+
+ {t("hostId")} +
+
)}
- {licenseStatus?.hostId && ( -
-
- {t("hostId")} -
- -
- )} - {hostLicense && ( -
-
- {t("licenseKey")} -
- -
- )} -
- - - - + + + + + { diff --git a/src/components/LicenseKeysDataTable.tsx b/src/components/LicenseKeysDataTable.tsx index 4b63a7b28..1e4f47a82 100644 --- a/src/components/LicenseKeysDataTable.tsx +++ b/src/components/LicenseKeysDataTable.tsx @@ -112,6 +112,54 @@ export function LicenseKeysDataTable({ } } }, + { + accessorKey: "quantity", + friendlyName: t("users"), + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const quantity = row.original.quantity; + if (quantity === undefined) { + return "-"; + } + return quantity < 0 ? t("licenseUnlimited") : quantity; + } + }, + { + accessorKey: "quantity_2", + friendlyName: t("sites"), + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const quantity = row.original.quantity_2; + if (quantity === undefined) { + return "-"; + } + return quantity < 0 ? t("licenseUnlimited") : quantity; + } + }, { accessorKey: "terminateAt", friendlyName: t("licenseTableValidUntil"),