import { ArrowRightIcon, ArrowUpCircleIcon, BookOpenIcon, ChatBubbleLeftEllipsisIcon, InformationCircleIcon, MapPinIcon, } from "@heroicons/react/20/solid"; import { Form } from "@remix-run/react"; import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { tryCatch } from "@trigger.dev/core"; import { useState } from "react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { CloudProviderIcon } from "~/assets/icons/CloudProviderIcon"; import { FlagIcon } from "~/assets/icons/RegionIcons"; import { cloudProviderTitle } from "~/components/CloudProvider"; import { Feedback } from "~/components/Feedback"; import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout"; import { Badge } from "~/components/primitives/Badge"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { ClipboardField } from "~/components/primitives/ClipboardField"; import { CopyableText } from "~/components/primitives/CopyableText"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "~/components/primitives/Dialog"; import { InfoPanel } from "~/components/primitives/InfoPanel"; import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader"; import { Paragraph } from "~/components/primitives/Paragraph"; import * as Property from "~/components/primitives/PropertyTable"; import { Table, TableBlankRow, TableBody, TableCell, TableCellMenu, TableHeader, TableHeaderCell, TableRow, } from "~/components/primitives/Table"; import { TextLink } from "~/components/primitives/TextLink"; import { InfoIconTooltip } from "~/components/primitives/Tooltip"; import { useFeatures } from "~/hooks/useFeatures"; import { useOrganization } from "~/hooks/useOrganizations"; import { useHasAdminAccess } from "~/hooks/useUser"; import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; import { findProjectBySlug } from "~/models/project.server"; import { type Region, RegionsPresenter } from "~/presenters/v3/RegionsPresenter.server"; import { requireUser } from "~/services/session.server"; import { docsPath, EnvironmentParamSchema, ProjectParamSchema, regionsPath, v3BillingPath, } from "~/utils/pathBuilder"; import { SetDefaultRegionService } from "~/v3/services/setDefaultRegion.server"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); const { projectParam } = ProjectParamSchema.parse(params); const presenter = new RegionsPresenter(); const [error, result] = await tryCatch( presenter.call({ userId: user.id, projectSlug: projectParam, isAdmin: user.admin || user.isImpersonating, }) ); if (error) { throw new Response(undefined, { status: 400, statusText: error.message, }); } return typedjson(result); }; const FormSchema = z.object({ regionId: z.string(), }); export const action = async ({ request, params }: ActionFunctionArgs) => { const user = await requireUser(request); const { organizationSlug, projectParam, envParam } = EnvironmentParamSchema.parse(params); const project = await findProjectBySlug(organizationSlug, projectParam, user.id); const redirectPath = regionsPath( { slug: organizationSlug }, { slug: projectParam }, { slug: envParam } ); if (!project) { throw await redirectWithErrorMessage(redirectPath, request, "Project not found"); } const formData = await request.formData(); const parsedFormData = FormSchema.safeParse(Object.fromEntries(formData)); if (!parsedFormData.success) { throw await redirectWithErrorMessage(redirectPath, request, "No region specified"); } const service = new SetDefaultRegionService(); const [error, result] = await tryCatch( service.call({ projectId: project.id, regionId: parsedFormData.data.regionId, isAdmin: user.admin || user.isImpersonating, }) ); if (error) { return redirectWithErrorMessage(redirectPath, request, error.message); } return redirectWithSuccessMessage(redirectPath, request, `Set ${result.name} as default`); }; export default function Page() { const { regions, isPaying: _isPaying } = useTypedLoaderData(); const organization = useOrganization(); const isAdmin = useHasAdminAccess(); const { isManagedCloud } = useFeatures(); return ( {regions.map((region) => ( {region.name} {region.id} ))}
{regions.length === 0 ? (
No regions found for this project.
) : ( <>
Region Cloud Provider Location Static IPs {isAdmin && Admin} When you trigger a run it will execute in your default region, unless you override the region when triggering. Read docs } > Default region {regions.length === 0 ? ( There are no regions for this project ) : ( regions.map((region) => { return ( {region.workloadType === "MICROVM" && ( MicroVM )} {region.cloudProvider ? ( {cloudProviderTitle(region.cloudProvider)} ) : ( "–" )} {region.location ? ( ) : null} {region.description ?? "–"} {region.staticIPs === null ? ( Unlock static IPs ) : region.staticIPs !== undefined ? ( ) : ( "Not available" )} {isAdmin && ( {region.isHidden ? "Hidden" : "Visible"} )} {region.isDefault ? ( Default ) : ( } /> )} ); }) )} Suggest a new region Suggest a region… } defaultValue="region" /> } />
{isManagedCloud && ( Trigger.dev is fully{" "} GDPR compliant . Learn more in our{" "} security portal or{" "} get in touch } defaultValue="feedback" /> . )}
)}
); } function SetDefaultDialog({ regions, newDefaultRegion, }: { regions: Region[]; newDefaultRegion: Region; }) { const [isOpen, setIsOpen] = useState(false); const { isManagedCloud } = useFeatures(); const currentDefaultRegion = regions.find((r) => r.isDefault); return ( Set as default region
Are you sure you want to set {newDefaultRegion.name} as your new default region?
Current default
{currentDefaultRegion?.name ?? "–"}
{currentDefaultRegion?.cloudProvider ? ( <> {cloudProviderTitle(currentDefaultRegion.cloudProvider)} ) : ( "–" )}
{currentDefaultRegion?.location ? ( ) : null} {currentDefaultRegion?.description ?? "–"}
{/* Middle column with arrow */}
{/* Right column */}
New default
{newDefaultRegion.name}
{newDefaultRegion.cloudProvider ? ( <> {cloudProviderTitle(newDefaultRegion.cloudProvider)} ) : ( "–" )}
{newDefaultRegion.location ? ( ) : null} {newDefaultRegion.description ?? "–"}
Runs triggered from now on will execute in "{newDefaultRegion.name}", unless you{" "} override when triggering. Region is where your runs execute, not where your data is stored. {isManagedCloud ? ( <> {" "} Trigger.dev is fully{" "} GDPR compliant . ) : null}
); }