diff --git a/messages/en-US.json b/messages/en-US.json index f3aca3e5d..0fefade48 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -167,7 +167,7 @@ "proxyResourceTitle": "Manage Public Resources", "proxyResourceDescription": "Create and manage resources that are publicly accessible through a web browser", "proxyResourcesBannerTitle": "Web-based Public Access", - "proxyResourcesBannerDescription": "Public resources are HTTPS or TCP/UDP proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", + "proxyResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "clientResourceTitle": "Manage Private Resources", "clientResourceDescription": "Create and manage resources that are only accessible through a connected client", "privateResourcesBannerTitle": "Zero-Trust Private Access", @@ -384,7 +384,7 @@ "userTitle": "Manage All Users", "userDescription": "View and manage all users in the system", "userAbount": "About User Management", - "userAbountDescription": "This table displays all root user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their root user object - they will remain in the system. To completely remove a user from the system, you must delete their root user object using the delete action in this table.", + "userAbountDescription": "This table displays all base user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their base user object. They will remain in the system. To completely remove a user from the system, you must delete their base user object using the delete action in this table.", "userServer": "Server Users", "userSearch": "Search server users...", "userErrorDelete": "Error deleting user", @@ -527,7 +527,7 @@ "userSettings": "User Information", "userSettingsDescription": "Enter the details for the new user", "inviteEmailSent": "Send invite email to user", - "inviteValid": "Valid For", + "inviteValid": "Invite Valid For (days)", "selectDuration": "Select duration", "selectResource": "Select Resource", "filterByResource": "Filter By Resource", @@ -2565,7 +2565,7 @@ "action": "Action", "actor": "Actor", "timestamp": "Timestamp", - "accessLogs": "Access Logs", + "accessLogs": "Authentication Logs", "exportCsv": "Export CSV", "exportError": "Unknown error when exporting CSV", "exportCsvTooltip": "Within Time Range", @@ -2586,25 +2586,25 @@ "noMoreAuthMethods": "No Valid Auth", "ip": "IP", "reason": "Reason", - "requestLogs": "Request Logs", + "requestLogs": "HTTPS Request Logs", "requestAnalytics": "Request Analytics", "host": "Host", "location": "Location", - "actionLogs": "Action Logs", - "sidebarLogsRequest": "Request Logs", - "sidebarLogsAccess": "Access Logs", - "sidebarLogsAction": "Action Logs", + "actionLogs": "Admin Action Logs", + "sidebarLogsRequest": "HTTPS Request Logs", + "sidebarLogsAccess": "Authentication Logs", + "sidebarLogsAction": "Admin Action Logs", "logRetention": "Log Retention", "logRetentionDescription": "Manage how long different types of logs are retained for this organization or disable them", - "requestLogsDescription": "View detailed request logs for resources in this organization", + "requestLogsDescription": "View detailed request logs for HTTPS resources in this organization", "requestAnalyticsDescription": "View detailed request analytics for resources in this organization", - "logRetentionRequestLabel": "Request Log Retention", + "logRetentionRequestLabel": "HTTPS Request Log Retention", "logRetentionRequestDescription": "How long to retain request logs", - "logRetentionAccessLabel": "Access Log Retention", + "logRetentionAccessLabel": "Authentication Log Retention", "logRetentionAccessDescription": "How long to retain access logs", - "logRetentionActionLabel": "Action Log Retention", + "logRetentionActionLabel": "Admin Action Log Retention", "logRetentionActionDescription": "How long to retain action logs", - "logRetentionConnectionLabel": "Connection Log Retention", + "logRetentionConnectionLabel": "Network Log Retention", "logRetentionConnectionDescription": "How long to retain connection logs", "logRetentionDisabled": "Disabled", "logRetention3Days": "3 days", @@ -2616,10 +2616,10 @@ "logRetentionEndOfFollowingYear": "End of following year", "actionLogsDescription": "View a history of actions performed in this organization", "accessLogsDescription": "View access auth requests for resources in this organization", - "connectionLogs": "Connection Logs", - "connectionLogsDescription": "View connection logs for tunnels in this organization", - "sidebarLogsConnection": "Connection Logs", - "sidebarLogsStreaming": "Streaming", + "connectionLogs": "Network Logs", + "connectionLogsDescription": "View network session logs handled by sites in this organization", + "sidebarLogsConnection": "Network Logs", + "sidebarLogsStreaming": "Event Streaming", "sourceAddress": "Source Address", "destinationAddress": "Destination Address", "duration": "Duration", @@ -3051,13 +3051,13 @@ "httpDestFormatSingleDescription": "Sends a separate HTTP POST for each individual event. Use only for endpoints that cannot handle batches.", "httpDestLogTypesTitle": "Log Types", "httpDestLogTypesDescription": "Choose which log types are forwarded to this destination. Only enabled log types will be streamed.", - "httpDestAccessLogsTitle": "Access Logs", + "httpDestAccessLogsTitle": "Authentication Logs", "httpDestAccessLogsDescription": "Resource access attempts, including authenticated and denied requests.", - "httpDestActionLogsTitle": "Action Logs", + "httpDestActionLogsTitle": "Admin Action Logs", "httpDestActionLogsDescription": "Administrative actions performed by users within the organization.", - "httpDestConnectionLogsTitle": "Connection Logs", + "httpDestConnectionLogsTitle": "Network Logs", "httpDestConnectionLogsDescription": "Site and tunnel connection events, including connects and disconnects.", - "httpDestRequestLogsTitle": "Request Logs", + "httpDestRequestLogsTitle": "HTTPS Request Logs", "httpDestRequestLogsDescription": "HTTP request logs for proxied resources, including method, path, and response code.", "httpDestSaveChanges": "Save Changes", "httpDestCreateDestination": "Create Destination", diff --git a/server/routers/domain/listDomains.ts b/server/routers/domain/listDomains.ts index 085acf0c6..94dddb1cf 100644 --- a/server/routers/domain/listDomains.ts +++ b/server/routers/domain/listDomains.ts @@ -103,7 +103,8 @@ export async function listDomains( const [{ count }] = await db .select({ count: sql`count(*)` }) - .from(domains); + .from(orgDomains) + .where(eq(orgDomains.orgId, orgId)); return response(res, { data: { diff --git a/src/app/[orgId]/settings/access/users/create/page.tsx b/src/app/[orgId]/settings/access/users/create/page.tsx index 858ac8da8..2c3292f9e 100644 --- a/src/app/[orgId]/settings/access/users/create/page.tsx +++ b/src/app/[orgId]/settings/access/users/create/page.tsx @@ -467,7 +467,7 @@ export default function Page() {
- {!inviteLink ? ( + {!inviteLink && userOptions.length > 1 ? ( @@ -490,7 +490,7 @@ export default function Page() { genericOidcForm.reset(); } }} - cols={2} + cols={3} /> diff --git a/src/app/[orgId]/settings/resources/proxy/create/page.tsx b/src/app/[orgId]/settings/resources/proxy/create/page.tsx index 535b597f2..1c9e5b1bb 100644 --- a/src/app/[orgId]/settings/resources/proxy/create/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/create/page.tsx @@ -776,9 +776,15 @@ export default function Page() { pathMatchType: row.original.pathMatchType }} onChange={(config) => - updateTarget(row.original.targetId, - config.path === null && config.pathMatchType === null - ? { ...config, rewritePath: null, rewritePathType: null } + updateTarget( + row.original.targetId, + config.path === null && + config.pathMatchType === null + ? { + ...config, + rewritePath: null, + rewritePathType: null + } : config ) } @@ -804,9 +810,15 @@ export default function Page() { pathMatchType: row.original.pathMatchType }} onChange={(config) => - updateTarget(row.original.targetId, - config.path === null && config.pathMatchType === null - ? { ...config, rewritePath: null, rewritePathType: null } + updateTarget( + row.original.targetId, + config.path === null && + config.pathMatchType === null + ? { + ...config, + rewritePath: null, + rewritePathType: null + } : config ) } @@ -1061,7 +1073,7 @@ export default function Page() { : null ); }} - cols={2} + cols={3} /> )} @@ -1118,28 +1130,30 @@ export default function Page() { - = 1 - } - onDomainChange={(res) => { - if (!res) return; + + = 1 + } + onDomainChange={(res) => { + if (!res) return; - httpForm.setValue( - "subdomain", - res.subdomain - ); - httpForm.setValue( - "domainId", - res.domainId - ); - console.log( - "Domain changed:", - res - ); - }} - /> + httpForm.setValue( + "subdomain", + res.subdomain + ); + httpForm.setValue( + "domainId", + res.domainId + ); + console.log( + "Domain changed:", + res + ); + }} + /> + ) : ( @@ -1155,98 +1169,101 @@ export default function Page() { -
- { - if (e.key === "Enter") { - e.preventDefault(); // block default enter refresh - } - }} - className="space-y-4 grid gap-4 grid-cols-1 md:grid-cols-2 items-start" - id="tcp-udp-settings-form" - > - ( - - - {t("protocol")} - - - - - )} - /> + + + { + if (e.key === "Enter") { + e.preventDefault(); // block default enter refresh + } + }} + className="space-y-4 grid gap-4 grid-cols-1 md:grid-cols-2 items-start" + id="tcp-udp-settings-form" + > + ( + + + {t( + "protocol" + )} + + + + + )} + /> - ( - - - {t( - "resourcePortNumber" - )} - - - - field.onChange( + ( + + + {t( + "resourcePortNumber" + )} + + + - - - - {t( - "resourcePortNumberDescription" - )} - - - )} - /> - - + ) => + field.onChange( + e + .target + .value + ? parseInt( + e + .target + .value + ) + : undefined + ) + } + /> + + + + )} + /> + + +
)} diff --git a/src/app/globals.css b/src/app/globals.css index bbb165c28..aa98b1d49 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -6,7 +6,7 @@ :root { --radius: 0.75rem; - --background: oklch(0.985 0 0); + --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); --card-foreground: oklch(0.141 0.005 285.823); @@ -22,30 +22,30 @@ --accent-foreground: oklch(0.21 0.006 285.885); --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); - --border: oklch(0.91 0.004 286.32); - --input: oklch(0.92 0.004 286.32); + --border: oklch(0.88 0.004 286.32); + --input: oklch(0.88 0.004 286.32); --ring: oklch(0.705 0.213 47.604); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); + --sidebar: #fafafa; --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.705 0.213 47.604); --sidebar-primary-foreground: oklch(0.98 0.016 73.684); - --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent: #eaeaea; --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.705 0.213 47.604); } .dark { - --background: oklch(0.19 0.006 285.885); + --background: #0d0d0f; --foreground: oklch(0.985 0 0); - --card: oklch(0.21 0.006 285.885); + --card: #0d0d0f; --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.21 0.006 285.885); + --popover: #0d0d0f; --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.6717 0.1946 41.93); --primary-foreground: oklch(0.98 0.016 73.684); @@ -57,7 +57,7 @@ --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.5382 0.1949 22.216); --destructive-foreground: oklch(0.985 0 0); - --border: oklch(1 0 0 / 13%); + --border: oklch(1 0 0 / 15%); --input: oklch(1 0 0 / 18%); --ring: oklch(0.646 0.222 41.116); --chart-1: oklch(0.488 0.243 264.376); @@ -65,11 +65,11 @@ --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.21 0.006 285.885); + --sidebar: #040404; --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.646 0.222 41.116); --sidebar-primary-foreground: oklch(0.98 0.016 73.684); - --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent: #131317; --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.646 0.222 41.116); @@ -110,6 +110,15 @@ --color-chart-4: var(--chart-4); --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + --radius-lg: var(--radius); --radius-md: calc(var(--radius) - 2px); --radius-sm: calc(var(--radius) - 4px); @@ -166,7 +175,9 @@ p { } @keyframes dot-pulse { - 0%, 80%, 100% { + 0%, + 80%, + 100% { opacity: 0.3; transform: scale(0.8); } @@ -189,7 +200,10 @@ p { /* Only apply custom viewport height on mobile */ @media (max-width: 767px) { .h-screen-safe { - height: var(--vh, 100vh); /* Use CSS variable set by ViewportHeightFix on mobile */ + height: var( + --vh, + 100vh + ); /* Use CSS variable set by ViewportHeightFix on mobile */ } } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 0db1b49bf..9cf66dd28 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -23,7 +23,7 @@ import { TanstackQueryProvider } from "@app/components/TanstackQueryProvider"; import { TailwindIndicator } from "@app/components/TailwindIndicator"; import { ViewportHeightFix } from "@app/components/ViewportHeightFix"; import StoreInternalRedirect from "@app/components/StoreInternalRedirect"; -import { Inter } from "next/font/google"; +import { Inter, Mona_Sans } from "next/font/google"; export const metadata: Metadata = { title: `Dashboard - ${process.env.BRANDING_APP_NAME || "Pangolin"}`, @@ -36,7 +36,11 @@ const inter = Inter({ subsets: ["latin"] }); -const fontClassName = inter.className; +const monaSans = Mona_Sans({ + subsets: ["latin"] +}); + +const fontClassName = monaSans.className; export default async function RootLayout({ children diff --git a/src/components/IdpGlobalModeBanner.tsx b/src/components/IdpGlobalModeBanner.tsx index 9f864b36d..5e2709e6f 100644 --- a/src/components/IdpGlobalModeBanner.tsx +++ b/src/components/IdpGlobalModeBanner.tsx @@ -8,23 +8,25 @@ import { useEnvContext } from "@app/hooks/useEnvContext"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { build } from "@server/build"; +import type { Env } from "@app/lib/types/env"; + +export function isIdpGlobalModeBannerVisible(env: Env): boolean { + if (build === "saas") { + return false; + } + return env.app.identityProviderMode === undefined; +} export function IdpGlobalModeBanner() { const t = useTranslations(); const { env } = useEnvContext(); const { isPaidUser, hasEnterpriseLicense } = usePaidStatus(); - const identityProviderModeUndefined = - env.app.identityProviderMode === undefined; const paidUserForOrgOidc = isPaidUser(tierMatrix.orgOidc); const enterpriseUnlicensed = build === "enterprise" && !hasEnterpriseLicense; - if (build === "saas") { - return null; - } - - if (!identityProviderModeUndefined) { + if (!isIdpGlobalModeBannerVisible(env)) { return null; } diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx index e8574b29e..fce96894c 100644 --- a/src/components/InternalResourceForm.tsx +++ b/src/components/InternalResourceForm.tsx @@ -1542,7 +1542,7 @@ export function InternalResourceForm({ ) )} - + {t( "accessClientSelect" )} diff --git a/src/components/LayoutHeader.tsx b/src/components/LayoutHeader.tsx index bef016853..29850f115 100644 --- a/src/components/LayoutHeader.tsx +++ b/src/components/LayoutHeader.tsx @@ -49,7 +49,7 @@ export function LayoutHeader({ showTopBar }: LayoutHeaderProps) { return (
-
+
diff --git a/src/components/LayoutSidebar.tsx b/src/components/LayoutSidebar.tsx index 27d8c2cd8..bde9e37a8 100644 --- a/src/components/LayoutSidebar.tsx +++ b/src/components/LayoutSidebar.tsx @@ -125,7 +125,7 @@ export function LayoutSidebar({ return (
@@ -154,7 +154,7 @@ export function LayoutSidebar({
{/* Fade gradient at bottom to indicate scrollable content */} -
+
{isSidebarCollapsed && ( @@ -206,7 +206,7 @@ export function LayoutSidebar({ setHasManualToggle(true); setSidebarStateCookie(false); }} - className="rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 transition-colors" + className="rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50 transition-colors" aria-label={t("sidebarExpand")} > @@ -220,7 +220,12 @@ export function LayoutSidebar({
)} -
+
{canShowProductUpdates ? (
diff --git a/src/components/LocaleSwitcherSelect.tsx b/src/components/LocaleSwitcherSelect.tsx index 5d7ece74e..b6f65aa7c 100644 --- a/src/components/LocaleSwitcherSelect.tsx +++ b/src/components/LocaleSwitcherSelect.tsx @@ -53,7 +53,7 @@ export default function LocaleSwitcherSelect({ )} aria-label={label} > - + {selected?.label ?? label} diff --git a/src/components/OrgIdpDataTable.tsx b/src/components/OrgIdpDataTable.tsx index 7e3f7ab65..fe15b6cc9 100644 --- a/src/components/OrgIdpDataTable.tsx +++ b/src/components/OrgIdpDataTable.tsx @@ -12,13 +12,15 @@ interface DataTableProps { data: TData[]; onAdd?: () => void; addActions?: DataTableAddAction[]; + addButtonDisabled?: boolean; } export function IdpDataTable({ columns, data, onAdd, - addActions + addActions, + addButtonDisabled }: DataTableProps) { const t = useTranslations(); @@ -33,6 +35,7 @@ export function IdpDataTable({ addButtonText={t("idpAdd")} onAdd={onAdd} addActions={addActions} + addButtonDisabled={addButtonDisabled} enableColumnVisibility={true} stickyRightColumn="actions" /> diff --git a/src/components/OrgIdpTable.tsx b/src/components/OrgIdpTable.tsx index 0e3a83dc2..bdbaafa27 100644 --- a/src/components/OrgIdpTable.tsx +++ b/src/components/OrgIdpTable.tsx @@ -52,6 +52,7 @@ import type { ListUserAdminOrgIdpsResponse } from "@server/routers/orgIdp/types" import { cn } from "@app/lib/cn"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; +import { isIdpGlobalModeBannerVisible } from "@app/components/IdpGlobalModeBanner"; export type IdpRow = { idpId: number; @@ -85,13 +86,15 @@ export default function IdpTable({ idps, orgId }: Props) { const [importSubmitting, setImportSubmitting] = useState(false); const [debouncedImportSearch] = useDebounce(importSearchQuery, 150); - const api = createApiClient(useEnvContext()); + const envContext = useEnvContext(); + const api = createApiClient(envContext); const { user } = useUserContext(); const { isPaidUser } = usePaidStatus(); const router = useRouter(); const t = useTranslations(); const canImportOrgOidcIdp = isPaidUser(tierMatrix.orgOidc); + const addIdpDisabled = isIdpGlobalModeBannerVisible(envContext.env); const { data: adminIdpsRaw = [] } = useQuery({ queryKey: ["admin-org-idps", user.userId], @@ -184,23 +187,6 @@ export default function IdpTable({ idps, orgId }: Props) { }; const columns: ExtendedColumnDef[] = [ - { - accessorKey: "idpId", - friendlyName: "ID", - header: ({ column }) => { - return ( - - ); - } - }, { accessorKey: "name", friendlyName: t("name"), @@ -427,6 +413,7 @@ export default function IdpTable({ idps, orgId }: Props) { {isCollapsed ? ( @@ -172,7 +172,7 @@ export function OrgSelector({
) : ( - - setActiveTab(value as "available" | "archived") - } - className="w-full" + !d.archived).length})`, + href: "#available" + }, + { + title: `${t("archived") || "Archived"} (${devices.filter((d) => d.archived).length})`, + href: "#archived" + } + ]} > - - - {t("available") || "Available"} ( - { - devices.filter( - (d) => !d.archived - ).length - } - ) - - - {t("archived") || "Archived"} ( - { - devices.filter( - (d) => d.archived - ).length - } - ) - - - +
{devices.filter((d) => !d.archived) .length === 0 ? (
@@ -271,8 +257,8 @@ export default function ViewDevicesDialog({
)} - - +
+
{devices.filter((d) => d.archived) .length === 0 ? (
@@ -336,8 +322,8 @@ export default function ViewDevicesDialog({
)} - - +
+
)}