diff --git a/messages/en-US.json b/messages/en-US.json index 5c0ca434b..8414f0a2f 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -3141,5 +3141,6 @@ "idpUnassociateMenu": "Unassociate", "idpDeleteAllOrgsMenu": "Delete", "publicIpEndpoint": "Endpoint", - "lastTriggeredAt": "Last Trigger" + "lastTriggeredAt": "Last Trigger", + "reject": "Reject" } diff --git a/server/apiServer.ts b/server/apiServer.ts index dafec1b85..9a91d473e 100644 --- a/server/apiServer.ts +++ b/server/apiServer.ts @@ -121,7 +121,7 @@ export function createApiServer() { const httpServer = apiServer.listen(externalPort, (err?: any) => { if (err) throw err; logger.info( - `API server is running on http://localhost:${externalPort}` + `Dashboard API server is running on http://localhost:${externalPort}` ); }); diff --git a/server/internalServer.ts b/server/internalServer.ts index 7ba046e4b..83872e7f9 100644 --- a/server/internalServer.ts +++ b/server/internalServer.ts @@ -36,7 +36,7 @@ export function createInternalServer() { internalServer.listen(internalPort, (err?: any) => { if (err) throw err; logger.info( - `Internal server is running on http://localhost:${internalPort}` + `Internal API server is running on http://localhost:${internalPort}` ); }); diff --git a/server/lib/telemetry.ts b/server/lib/telemetry.ts index fda59f394..8d341bf1d 100644 --- a/server/lib/telemetry.ts +++ b/server/lib/telemetry.ts @@ -72,7 +72,7 @@ class TelemetryClient { logger.debug("Successfully sent analytics data"); }); }, - 48 * 60 * 60 * 1000 + 336 * 60 * 60 * 1000 ); this.collectAndSendAnalytics().catch((err) => { diff --git a/server/nextServer.ts b/server/nextServer.ts index deb74d309..18b14a397 100644 --- a/server/nextServer.ts +++ b/server/nextServer.ts @@ -29,7 +29,7 @@ export async function createNextServer() { nextServer.listen(nextPort, (err?: any) => { if (err) throw err; logger.info( - `Next.js server is running on http://localhost:${nextPort}` + `Dashboard Web UI server is running on http://localhost:${nextPort}` ); }); diff --git a/server/private/lib/acmeCertSync.ts b/server/private/lib/acmeCertSync.ts index faa45b08e..62a18b805 100644 --- a/server/private/lib/acmeCertSync.ts +++ b/server/private/lib/acmeCertSync.ts @@ -89,7 +89,7 @@ async function pushCertUpdateToAffectedNewts( return; } - logger.info( + logger.debug( `acmeCertSync: pushing cert update to ${affectedResources.length} affected site resource(s) for domain "${domain}"` ); @@ -187,7 +187,7 @@ async function pushCertUpdateToAffectedNewts( newt.version ); - logger.info( + logger.debug( `acmeCertSync: pushed cert update to newt for site ${siteId}, resource ${resource.siteResourceId}` ); } @@ -400,7 +400,7 @@ async function syncAcmeCerts( }) .where(eq(certificates.domain, domain)); - logger.info( + logger.debug( `acmeCertSync: updated certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` ); @@ -423,7 +423,7 @@ async function syncAcmeCerts( wildcard }); - logger.info( + logger.debug( `acmeCertSync: inserted new certificate for ${domain} (expires ${expiresAt ? new Date(expiresAt * 1000).toISOString() : "unknown"})` ); @@ -461,7 +461,7 @@ export function initAcmeCertSync(): void { const resolver = privateConfigData.acme?.resolver ?? "letsencrypt"; const intervalMs = privateConfigData.acme?.sync_interval_ms ?? 5000; - logger.info( + logger.debug( `acmeCertSync: starting ACME cert sync from "${acmeJsonPath}" using resolver "${resolver}" every ${intervalMs}ms` ); diff --git a/server/routers/newt/pingAccumulator.ts b/server/routers/newt/pingAccumulator.ts index b63bf97d3..9b2f04c8e 100644 --- a/server/routers/newt/pingAccumulator.ts +++ b/server/routers/newt/pingAccumulator.ts @@ -381,7 +381,7 @@ export function startPingAccumulator(): void { // Don't prevent the process from exiting flushTimer.unref(); - logger.info( + logger.debug( `Ping accumulator started (flush interval: ${FLUSH_INTERVAL_MS}ms)` ); } diff --git a/src/app/[orgId]/settings/access/users/create/page.tsx b/src/app/[orgId]/settings/access/users/create/page.tsx index 2c3292f9e..cec399a32 100644 --- a/src/app/[orgId]/settings/access/users/create/page.tsx +++ b/src/app/[orgId]/settings/access/users/create/page.tsx @@ -50,6 +50,7 @@ import IdpTypeIcon from "@app/components/IdpTypeIcon"; import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { tierMatrix } from "@server/lib/billing/tierMatrix"; import OrgRolesTagField from "@app/components/OrgRolesTagField"; +import CopyToClipboard from "@app/components/CopyToClipboard"; type UserType = "internal" | "oidc"; @@ -670,9 +671,8 @@ export default function Page() { days: expiresInDays })}

- diff --git a/src/components/LayoutMobileMenu.tsx b/src/components/LayoutMobileMenu.tsx index 854cad6db..13efdd564 100644 --- a/src/components/LayoutMobileMenu.tsx +++ b/src/components/LayoutMobileMenu.tsx @@ -101,7 +101,6 @@ export function LayoutMobileMenu({ "serverAdmin" )} - )} diff --git a/src/components/LayoutSidebar.tsx b/src/components/LayoutSidebar.tsx index a66a8300b..19a095419 100644 --- a/src/components/LayoutSidebar.tsx +++ b/src/components/LayoutSidebar.tsx @@ -189,7 +189,6 @@ export function LayoutSidebar({ {t("serverAdmin")} - )} diff --git a/src/components/PendingSitesTable.tsx b/src/components/PendingSitesTable.tsx index a6625037d..d32aee95e 100644 --- a/src/components/PendingSitesTable.tsx +++ b/src/components/PendingSitesTable.tsx @@ -1,5 +1,6 @@ "use client"; +import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { Badge } from "@app/components/ui/badge"; import { Button } from "@app/components/ui/button"; import { @@ -24,7 +25,8 @@ import { ArrowUpRight, Check, ChevronsUpDownIcon, - MoreHorizontal + MoreHorizontal, + X } from "lucide-react"; import { useTranslations } from "next-intl"; import Link from "next/link"; @@ -62,6 +64,9 @@ export default function PendingSitesTable({ const [isRefreshing, startTransition] = useTransition(); const [approvingIds, setApprovingIds] = useState>(new Set()); + const [rejectingIds, setRejectingIds] = useState>(new Set()); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [selectedSite, setSelectedSite] = useState(null); const api = createApiClient(useEnvContext()); const t = useTranslations(); @@ -128,6 +133,33 @@ export default function PendingSitesTable({ } } + async function rejectSite(siteId: number) { + setRejectingIds((prev) => new Set(prev).add(siteId)); + try { + await api.delete(`/site/${siteId}`); + toast({ + title: t("success"), + description: t("siteDeleted"), + variant: "default" + }); + setIsDeleteModalOpen(false); + setSelectedSite(null); + router.refresh(); + } catch (e) { + toast({ + variant: "destructive", + title: t("siteErrorDelete"), + description: formatAxiosError(e, t("siteErrorDelete")) + }); + } finally { + setRejectingIds((prev) => { + const next = new Set(prev); + next.delete(siteId); + return next; + }); + } + } + const columns: ExtendedColumnDef[] = [ { accessorKey: "name", @@ -387,6 +419,7 @@ export default function PendingSitesTable({ cell: ({ row }) => { const siteRow = row.original; const isApproving = approvingIds.has(siteRow.id); + const isRejecting = rejectingIds.has(siteRow.id); return (
@@ -409,7 +442,18 @@ export default function PendingSitesTable({ + + + + {addActions.map((action, i) => ( + action.onSelect()}> + {action.label} + + ))} + + + ) : onAdd && addButtonText ? ( + + ) : null; + return (
@@ -367,51 +400,15 @@ export function ControlledDataTable({
)} - {addActions && - addActions.length > 0 && - addButtonText ? ( -
- - - - - - {addActions.map((action, i) => ( - - action.onSelect() - } - > - {action.label} - - ))} - - -
- ) : ( - onAdd && - addButtonText && ( -
- -
- ) + {addAction && ( + <> +
{addAction}
+ {!showAddActionInEmptyState && ( +
+ {addAction} +
+ )} + )}
@@ -606,14 +603,18 @@ export function ControlledDataTable({ )) ) : ( - - - No results found. - - + + {addAction} + + ) + : undefined + } + /> )} diff --git a/src/components/ui/data-table-empty-state.tsx b/src/components/ui/data-table-empty-state.tsx new file mode 100644 index 000000000..793c360f4 --- /dev/null +++ b/src/components/ui/data-table-empty-state.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { TableCell, TableRow } from "@/components/ui/table"; +import { useTranslations } from "next-intl"; +import { type ReactNode } from "react"; + +const PLACEHOLDER_ROW_COUNT = 5; + +type DataTableEmptyStateProps = { + colSpan: number; + action?: ReactNode; +}; + +export function DataTableEmptyState({ + colSpan, + action +}: DataTableEmptyStateProps) { + const t = useTranslations(); + return ( + + +
+
+ {Array.from({ length: PLACEHOLDER_ROW_COUNT }).map( + (_, i) => ( +
+ ) + )} +
+
+

+ {t("noResults")} +

+ {action} +
+
+ + + ); +} diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx index 2c0e5e48c..82aafe1f4 100644 --- a/src/components/ui/data-table.tsx +++ b/src/components/ui/data-table.tsx @@ -29,6 +29,7 @@ import { TableHeader, TableRow } from "@/components/ui/table"; +import { DataTableEmptyState } from "@/components/ui/data-table-empty-state"; import { Button } from "@app/components/ui/button"; import { useEffect, useMemo, useRef, useState } from "react"; import { Input } from "@app/components/ui/input"; @@ -515,6 +516,36 @@ export function DataTable({ return ""; }; + const tableRows = table.getRowModel().rows; + const hasRows = tableRows.length > 0; + const hasAddAction = Boolean( + addButtonText && ((addActions && addActions.length > 0) || onAdd) + ); + const showAddActionInEmptyState = !hasRows && hasAddAction; + const addAction = addActions && addActions.length > 0 && addButtonText ? ( + + + + + + {addActions.map((action, i) => ( + action.onSelect()}> + {action.label} + + ))} + + + ) : onAdd && addButtonText ? ( + + ) : null; + return (
@@ -651,45 +682,15 @@ export function DataTable({
)} - {addActions && addActions.length > 0 && addButtonText ? ( -
- - - - - - {addActions.map((action, i) => ( - - action.onSelect() - } - > - {action.label} - - ))} - - -
- ) : ( - onAdd && - addButtonText && ( -
- -
- ) + {addAction && ( + <> +
{addAction}
+ {!showAddActionInEmptyState && ( +
+ {addAction} +
+ )} + )}
@@ -884,14 +885,18 @@ export function DataTable({
)) ) : ( - - - No results found. - - + + {addAction} + + ) + : undefined + } + /> )}