Impvove communication

This commit is contained in:
Owen
2026-04-01 09:53:49 -07:00
parent 08e4afaef0
commit 363c13c387
7 changed files with 24 additions and 6 deletions

View File

@@ -60,7 +60,7 @@ Pangolin is an open-source, identity-based remote access platform built on WireG
| <img width=500 /> | Description | | <img width=500 /> | Description |
|-----------------|--------------| |-----------------|--------------|
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/understanding-nodes) and connect to our control plane. | | **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing - no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/understanding-nodes) and connect to our control plane. |
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. | | **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. | | **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |

View File

@@ -371,10 +371,10 @@
"provisioningKeysUpdated": "Provisioning key updated", "provisioningKeysUpdated": "Provisioning key updated",
"provisioningKeysUpdatedDescription": "Your changes have been saved.", "provisioningKeysUpdatedDescription": "Your changes have been saved.",
"provisioningKeysBannerTitle": "Site Provisioning Keys", "provisioningKeysBannerTitle": "Site Provisioning Keys",
"provisioningKeysBannerDescription": "Generate a provisioning key and use it with the Newt connector to automatically create sites on first startup no need to set up separate credentials for each site.", "provisioningKeysBannerDescription": "Generate a provisioning key and use it with the Newt connector to automatically create sites on first startup - no need to set up separate credentials for each site.",
"provisioningKeysBannerButtonText": "Learn More", "provisioningKeysBannerButtonText": "Learn More",
"pendingSitesBannerTitle": "Pending Sites", "pendingSitesBannerTitle": "Pending Sites",
"pendingSitesBannerDescription": "Sites that connect using a provisioning key appear here for review. Approve each site before it becomes active and gains access to your resources.", "pendingSitesBannerDescription": "Sites that connect using a provisioning key appear here for review.",
"pendingSitesBannerButtonText": "Learn More", "pendingSitesBannerButtonText": "Learn More",
"apiKeysSettings": "{apiKeyName} Settings", "apiKeysSettings": "{apiKeyName} Settings",
"userTitle": "Manage All Users", "userTitle": "Manage All Users",
@@ -2346,7 +2346,7 @@
"description": "Enterprise features, 50 users, 50 sites, and priority support." "description": "Enterprise features, 50 users, 50 sites, and priority support."
} }
}, },
"personalUseOnly": "Personal use only (free license no checkout)", "personalUseOnly": "Personal use only (free license - no checkout)",
"buttons": { "buttons": {
"continueToCheckout": "Continue to Checkout" "continueToCheckout": "Continue to Checkout"
}, },

View File

@@ -9,6 +9,8 @@ import DismissableBanner from "@app/components/DismissableBanner";
import Link from "next/link"; import Link from "next/link";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { ArrowRight, Plug } from "lucide-react"; import { ArrowRight, Plug } from "lucide-react";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
type PendingSitesPageProps = { type PendingSitesPageProps = {
params: Promise<{ orgId: string }>; params: Promise<{ orgId: string }>;
@@ -96,6 +98,10 @@ export default async function PendingSitesPage(props: PendingSitesPageProps) {
</Button> </Button>
</Link> </Link>
</DismissableBanner> </DismissableBanner>
<PaidFeaturesAlert
tiers={tierMatrix[TierFeature.SiteProvisioningKeys]}
/>
<PendingSitesTable <PendingSitesTable
sites={siteRows} sites={siteRows}
orgId={params.orgId} orgId={params.orgId}

View File

@@ -15,6 +15,8 @@ import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { build } from "@server/build"; import { build } from "@server/build";
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { type PaginationState } from "@tanstack/react-table"; import { type PaginationState } from "@tanstack/react-table";
import { import {
ArrowDown01Icon, ArrowDown01Icon,
@@ -63,6 +65,10 @@ export default function PendingSitesTable({
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const t = useTranslations(); const t = useTranslations();
const { isPaidUser } = usePaidStatus();
const canUseSiteProvisioning =
isPaidUser(tierMatrix[TierFeature.SiteProvisioningKeys]) &&
build !== "oss";
const booleanSearchFilterSchema = z const booleanSearchFilterSchema = z
.enum(["true", "false"]) .enum(["true", "false"])
@@ -450,6 +456,7 @@ export default function PendingSitesTable({
onSearch={handleSearchChange} onSearch={handleSearchChange}
onRefresh={refreshData} onRefresh={refreshData}
isRefreshing={isRefreshing || isFiltering} isRefreshing={isRefreshing || isFiltering}
refreshButtonDisabled={!canUseSiteProvisioning}
rowCount={rowCount} rowCount={rowCount}
columnVisibility={{ columnVisibility={{
niceId: false, niceId: false,

View File

@@ -311,6 +311,7 @@ export default function SiteProvisioningKeysTable({
addButtonDisabled={!canUseSiteProvisioning} addButtonDisabled={!canUseSiteProvisioning}
onRefresh={refreshData} onRefresh={refreshData}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
refreshButtonDisabled={!canUseSiteProvisioning}
addButtonText={t("provisioningKeysAdd")} addButtonText={t("provisioningKeysAdd")}
enableColumnVisibility={true} enableColumnVisibility={true}
stickyLeftColumn="name" stickyLeftColumn="name"

View File

@@ -69,6 +69,7 @@ type ControlledDataTableProps<TData, TValue> = {
onAdd?: () => void; onAdd?: () => void;
onRefresh?: () => void; onRefresh?: () => void;
isRefreshing?: boolean; isRefreshing?: boolean;
refreshButtonDisabled?: boolean;
isNavigatingToAddPage?: boolean; isNavigatingToAddPage?: boolean;
searchPlaceholder?: string; searchPlaceholder?: string;
filters?: DataTableFilter[]; filters?: DataTableFilter[];
@@ -91,6 +92,7 @@ export function ControlledDataTable<TData, TValue>({
onAdd, onAdd,
onRefresh, onRefresh,
isRefreshing, isRefreshing,
refreshButtonDisabled = false,
searchPlaceholder = "Search...", searchPlaceholder = "Search...",
filters, filters,
filterDisplayMode = "label", filterDisplayMode = "label",
@@ -335,7 +337,7 @@ export function ControlledDataTable<TData, TValue>({
<Button <Button
variant="outline" variant="outline"
onClick={onRefresh} onClick={onRefresh}
disabled={isRefreshing} disabled={isRefreshing || refreshButtonDisabled}
> >
<RefreshCw <RefreshCw
className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}

View File

@@ -174,6 +174,7 @@ type DataTableProps<TData, TValue> = {
addButtonDisabled?: boolean; addButtonDisabled?: boolean;
onRefresh?: () => void; onRefresh?: () => void;
isRefreshing?: boolean; isRefreshing?: boolean;
refreshButtonDisabled?: boolean;
searchPlaceholder?: string; searchPlaceholder?: string;
searchColumn?: string; searchColumn?: string;
defaultSort?: { defaultSort?: {
@@ -207,6 +208,7 @@ export function DataTable<TData, TValue>({
addButtonDisabled = false, addButtonDisabled = false,
onRefresh, onRefresh,
isRefreshing, isRefreshing,
refreshButtonDisabled = false,
searchPlaceholder = "Search...", searchPlaceholder = "Search...",
searchColumn = "name", searchColumn = "name",
defaultSort, defaultSort,
@@ -624,7 +626,7 @@ export function DataTable<TData, TValue>({
<Button <Button
variant="outline" variant="outline"
onClick={onRefresh} onClick={onRefresh}
disabled={isRefreshing} disabled={isRefreshing || refreshButtonDisabled}
> >
<RefreshCw <RefreshCw
className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} className={`mr-0 sm:mr-2 h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`}