diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 544f53bfa8..65425fc339 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -19,6 +19,7 @@ import { Toaster, TooltipProvider, } from "@/components"; +import { NotFoundCard } from "./app/not-found-card"; import { RouteLayout } from "./app/route-layout"; import { RootLayout } from "./components/layout"; import { clerk } from "./lib/auth"; @@ -60,27 +61,7 @@ export const router = createRouter({ }, defaultNotFoundComponent: () => ( -
-
-
- - - - 404 - - - The page was not found - - - - - - -
-
-
+
), }); diff --git a/frontend/src/app/data-providers/cloud-data-provider.tsx b/frontend/src/app/data-providers/cloud-data-provider.tsx index bda157e16b..2068702ffd 100644 --- a/frontend/src/app/data-providers/cloud-data-provider.tsx +++ b/frontend/src/app/data-providers/cloud-data-provider.tsx @@ -381,5 +381,29 @@ export const createNamespaceContext = ({ }, }); }, + publishableTokenQueryOptions() { + return queryOptions({ + staleTime: 5 * 60 * 1000, // 5 minutes + gcTime: 5 * 60 * 1000, // 5 minutes + queryKey: [ + { + namespace, + project: parent.project, + organization: parent.organization, + }, + "tokens", + "publishable", + ], + queryFn: async () => { + const f = parent.client.namespaces.createPublishableToken( + parent.project, + namespace, + { org: parent.organization }, + ); + const t = await f; + return t.token; + }, + }); + }, }; }; diff --git a/frontend/src/app/data-providers/default-data-provider.tsx b/frontend/src/app/data-providers/default-data-provider.tsx index 893de148cf..678ae98dab 100644 --- a/frontend/src/app/data-providers/default-data-provider.tsx +++ b/frontend/src/app/data-providers/default-data-provider.tsx @@ -209,6 +209,7 @@ const defaultContext = { : null, sleepingAt: data.sleepingAt ? new Date(data.sleepingAt) : null, region: data.region, + runner: data.runner, crashPolicy: data.crashPolicy, }), }); diff --git a/frontend/src/app/dialogs/tokens-frame.tsx b/frontend/src/app/dialogs/tokens-frame.tsx new file mode 100644 index 0000000000..ac432aa2cf --- /dev/null +++ b/frontend/src/app/dialogs/tokens-frame.tsx @@ -0,0 +1,103 @@ +import { faQuestionCircle, faRailway, Icon } from "@rivet-gg/icons"; +import { useQuery } from "@tanstack/react-query"; +import { useRouteContext } from "@tanstack/react-router"; +import * as ConnectRailwayForm from "@/app/forms/connect-railway-form"; +import { HelpDropdown } from "@/app/help-dropdown"; +import { + Button, + type DialogContentProps, + DiscreteInput, + Frame, + Label, + Skeleton, +} from "@/components"; +import { + useCloudDataProvider, + useEngineCompatDataProvider, +} from "@/components/actors"; +import { defineStepper } from "@/components/ui/stepper"; +import { cloudEnv, engineEnv } from "@/lib/env"; + +interface TokensFrameContentProps extends DialogContentProps {} + +export default function TokensFrameContent({ + onClose, +}: TokensFrameContentProps) { + return ( + <> + + +
Namespace Tokens
+ + + +
+ + These tokens are used to authenticate requests to the Rivet + Engine API. Keep them secret! + +
+ +
+ + +
+
+ + + + + ); +} + +function SecretToken() { + const dataProvider = useRouteContext({ + from: "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace", + select: (c) => c.dataProvider, + }); + const { data, isLoading } = useQuery( + dataProvider.engineAdminTokenQueryOptions(), + ); + return ( +
+ + {isLoading ? ( + + ) : ( + + )} +

+ Only use in secure server environments. Grants full access to + your namespace. Used to connect your Runners to your namespace. +

+
+ ); +} + +function PublishableToken() { + const dataProvider = useRouteContext({ + from: "/_context/_cloud/orgs/$organization/projects/$project/ns/$namespace", + select: (c) => c.dataProvider, + }); + const { data, isLoading } = useQuery( + dataProvider.publishableTokenQueryOptions(), + ); + return ( +
+ + {isLoading ? ( + + ) : ( + + )} +

+ Safe to use in public contexts like client-side code. Allows + your frontend to interact with Rivet services. +

+
+ ); +} diff --git a/frontend/src/app/not-found-card.tsx b/frontend/src/app/not-found-card.tsx new file mode 100644 index 0000000000..62e3049e6e --- /dev/null +++ b/frontend/src/app/not-found-card.tsx @@ -0,0 +1,35 @@ +import { Link } from "@tanstack/react-router"; +import { + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components"; + +export function NotFoundCard() { + return ( +
+
+
+ + + + 404 + + + The page was not found + + + + + + +
+
+
+ ); +} diff --git a/frontend/src/app/use-dialog.tsx b/frontend/src/app/use-dialog.tsx index c9ba831fb4..9e6a720ccd 100644 --- a/frontend/src/app/use-dialog.tsx +++ b/frontend/src/app/use-dialog.tsx @@ -18,4 +18,5 @@ export const useDialog = { ProvideEngineCredentials: createDialogHook( () => import("@/app/dialogs/provide-engine-credentials-frame"), ), + Tokens: createDialogHook(() => import("@/app/dialogs/tokens-frame")), }; diff --git a/frontend/src/app/user-dropdown.tsx b/frontend/src/app/user-dropdown.tsx index bfa4a03f84..8ead4b57c8 100644 --- a/frontend/src/app/user-dropdown.tsx +++ b/frontend/src/app/user-dropdown.tsx @@ -59,6 +59,16 @@ export function UserDropdown() { > Profile + { + navigate({ + to: ".", + search: { ...params, modal: "tokens" }, + }); + }} + > + Tokens + {clerk.organization ? ( { diff --git a/frontend/src/components/actors/actor-general.tsx b/frontend/src/components/actors/actor-general.tsx index 1779c623b7..cee0203071 100644 --- a/frontend/src/components/actors/actor-general.tsx +++ b/frontend/src/components/actors/actor-general.tsx @@ -21,6 +21,7 @@ export function ActorGeneral({ actorId }: ActorGeneralProps) { pendingAllocationAt, sleepingAt, crashPolicy, + runner, } = {}, } = useQuery(useDataProvider().actorGeneralQueryOptions(actorId)); @@ -61,6 +62,8 @@ export function ActorGeneral({ actorId }: ActorGeneralProps) { /> +
Runner
+
{runner}
Crash Policy
{crashPolicy}
Created
diff --git a/frontend/src/routes/_context/_cloud.tsx b/frontend/src/routes/_context/_cloud.tsx index 95e6bb173b..0216536a8b 100644 --- a/frontend/src/routes/_context/_cloud.tsx +++ b/frontend/src/routes/_context/_cloud.tsx @@ -39,6 +39,7 @@ function CloudModals() { const CreateNamespaceDialog = useDialog.CreateNamespace.Dialog; const ConnectVercelDialog = useDialog.ConnectVercel.Dialog; const ConnectRailwayDialog = useDialog.ConnectRailway.Dialog; + const TokensDialog = useDialog.Tokens.Dialog; return ( <> @@ -116,6 +117,23 @@ function CloudModals() { }, }} /> + { + if (!value) { + navigate({ + to: ".", + search: (old) => ({ + ...old, + modal: undefined, + }), + }); + } + }, + }} + /> ); } diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project.tsx index 4868883e80..8931705fc0 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project.tsx @@ -1,6 +1,7 @@ import { createFileRoute, Outlet } from "@tanstack/react-router"; import { match } from "ts-pattern"; import { createProjectContext } from "@/app/data-providers/cloud-data-provider"; +import { NotFoundCard } from "@/app/not-found-card"; import { RouteError } from "@/app/route-error"; import { useDialog } from "@/app/use-dialog"; diff --git a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx index af58b8a0c6..eec565c29e 100644 --- a/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx +++ b/frontend/src/routes/_context/_cloud/orgs.$organization/projects.$project/ns.$namespace.tsx @@ -1,5 +1,6 @@ import { createFileRoute } from "@tanstack/react-router"; import { createNamespaceContext } from "@/app/data-providers/cloud-data-provider"; +import { NotFoundCard } from "@/app/not-found-card"; import { RouteLayout } from "@/app/route-layout"; export const Route = createFileRoute( @@ -28,6 +29,7 @@ export const Route = createFileRoute( }, }; }, + notFoundComponent: () => , }); function RouteComponent() { diff --git a/frontend/src/routes/_context/_engine/ns.$namespace.tsx b/frontend/src/routes/_context/_engine/ns.$namespace.tsx index c272104963..9b9a2d7e54 100644 --- a/frontend/src/routes/_context/_engine/ns.$namespace.tsx +++ b/frontend/src/routes/_context/_engine/ns.$namespace.tsx @@ -1,6 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; import { match } from "ts-pattern"; import { createNamespaceContext } from "@/app/data-providers/engine-data-provider"; +import { NotFoundCard } from "@/app/not-found-card"; import { RouteLayout } from "@/app/route-layout"; export const Route = createFileRoute("/_context/_engine/ns/$namespace")({ @@ -20,6 +21,7 @@ export const Route = createFileRoute("/_context/_engine/ns/$namespace")({ }); }, component: RouteComponent, + notFoundComponent: () => , }); function RouteComponent() {