mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-02 16:06:38 +00:00
Impvove communication
This commit is contained in:
@@ -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. |
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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" : ""}`}
|
||||||
|
|||||||
@@ -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" : ""}`}
|
||||||
|
|||||||
Reference in New Issue
Block a user