diff --git a/frontend/src/app/connect.tsx b/frontend/src/app/connect.tsx new file mode 100644 index 0000000000..6ec11ef14c --- /dev/null +++ b/frontend/src/app/connect.tsx @@ -0,0 +1,84 @@ +import { faNodeJs, faReact, Icon } from "@rivet-gg/icons"; +import { useSearch } from "@tanstack/react-router"; +import type { ComponentProps, Ref } from "react"; +import { + Button, + Card, + CardContent, + CardHeader, + CardTitle, + DocsSheet, +} from "@/components"; +import { ConnectionForm } from "@/components/connection-form"; +import { docsLinks } from "@/content/data"; + +export function Connect({ + onSubmit, + formRef, +}: { + formRef?: Ref; + onSubmit: ComponentProps["onSubmit"]; +}) { + const search = useSearch({ from: "/_context" }); + return ( + <> + + + Getting Started + + +

Get started with one of our quick start guides:

+
+
+ + + + + + +
+
+
+
+ + + + Connect to Project + + +

+ Connect to your RivetKit project by entering the URL and + access token. +

+ + +
+
+ + ); +} diff --git a/frontend/src/components/actors/actors-actor-details.tsx b/frontend/src/components/actors/actors-actor-details.tsx index 7764e0fbf1..d5e5fedf34 100644 --- a/frontend/src/components/actors/actors-actor-details.tsx +++ b/frontend/src/components/actors/actors-actor-details.tsx @@ -22,7 +22,7 @@ import { ActorStopButton } from "./actor-stop-button"; import { ActorsSidebarToggleButton } from "./actors-sidebar-toggle-button"; import { useActorsView } from "./actors-view-context-provider"; import { ActorConsole } from "./console/actor-console"; -import { GuardConnectableInspector } from "./guard-connectable-inspector"; +import { GuardConnectableInspector, useInspectorGuard } from "./guard-connectable-inspector"; import { useManager } from "./manager-context"; import { ActorFeature, type ActorId } from "./queries"; import { ActorWorkerContextProvider } from "./worker/actor-worker-context"; @@ -48,6 +48,8 @@ export const ActorsActorDetails = memo( const supportsConsole = features.includes(ActorFeature.Console); return ( + +
{supportsConsole ? ( ) : null}
+
); }, ); @@ -121,6 +119,8 @@ export function ActorTabs({ const defaultTab = supportsState ? "state" : "logs"; const value = disabled ? undefined : tab || defaultTab; + const guardContent = useInspectorGuard(); + return ( }> - - - + {guardContent || } ) : null} @@ -239,9 +237,7 @@ export function ActorTabs({ value="connections" className="min-h-0 flex-1 mt-0" > - - - + {guardContent ||} ) : null} {supportsEvents ? ( @@ -249,9 +245,7 @@ export function ActorTabs({ value="events" className="min-h-0 flex-1 mt-0" > - - - + {guardContent || } ) : null} {supportsDatabase ? ( @@ -259,9 +253,7 @@ export function ActorTabs({ value="database" className="min-h-0 min-w-0 flex-1 mt-0 h-full" > - - - + {guardContent || } ) : null} {supportsState ? ( @@ -269,9 +261,7 @@ export function ActorTabs({ value="state" className="min-h-0 flex-1 mt-0" > - - - + {guardContent || } ) : null} {supportsMetrics ? ( @@ -279,9 +269,7 @@ export function ActorTabs({ value="metrics" className="min-h-0 flex-1 mt-0 h-full" > - - - + {guardContent || } ) : null} diff --git a/frontend/src/components/actors/console/actor-console.tsx b/frontend/src/components/actors/console/actor-console.tsx index 4def5ac856..6bdb5bd6a8 100644 --- a/frontend/src/components/actors/console/actor-console.tsx +++ b/frontend/src/components/actors/console/actor-console.tsx @@ -21,8 +21,8 @@ export function ActorConsole({ actorId }: ActorConsoleProps) { const status = useActorWorkerStatus(); const managerQueries = useManager(); const actorQueries = useActor(); - const { data: destroyedAt } = useQuery( - managerQueries.actorDestroyedAtQueryOptions(actorId), + const { data: { destroyedAt, sleepingAt } = {} } = useQuery( + managerQueries.actorWorkerQueryOptions(actorId), ); const { isSuccess, isError, isLoading } = useQuery( actorQueries.actorPingQueryOptions(actorId, { @@ -31,7 +31,8 @@ export function ActorConsole({ actorId }: ActorConsoleProps) { }), ); - const isBlocked = status.type !== "ready" || !isSuccess || !!destroyedAt; + const isBlocked = + status.type !== "ready" || !isSuccess || !!destroyedAt || !!sleepingAt; const combinedStatus = isError ? "error" diff --git a/frontend/src/components/actors/guard-connectable-inspector.tsx b/frontend/src/components/actors/guard-connectable-inspector.tsx index fb3092a968..d7dca760ab 100644 --- a/frontend/src/components/actors/guard-connectable-inspector.tsx +++ b/frontend/src/components/actors/guard-connectable-inspector.tsx @@ -1,15 +1,15 @@ import { faPowerOff, faSpinnerThird, Icon } from "@rivet-gg/icons"; import { useMutation, useQuery, useSuspenseQuery } from "@tanstack/react-query"; import { useMatch } from "@tanstack/react-router"; -import { type ReactNode, useMemo } from "react"; +import { createContext, type ReactNode, useContext, useMemo } from "react"; import { useInspectorCredentials } from "@/app/credentials-context"; -import { createEngineActorContext } from "@/queries/actor-engine"; import { createInspectorActorContext } from "@/queries/actor-inspector"; import { type NamespaceNameId, runnerByNameQueryOptions, } from "@/queries/manager-engine"; import { DiscreteCopyButton } from "../copy-area"; +import { getConfig } from "../lib/config"; import { Button } from "../ui/button"; import { useFiltersValue } from "./actor-filters-context"; import { ActorProvider } from "./actor-queries-context"; @@ -17,6 +17,10 @@ import { Info } from "./actor-state-tab"; import { useManager } from "./manager-context"; import type { ActorId } from "./queries"; +const InspectorGuardContext = createContext(null); + +export const useInspectorGuard = () => useContext(InspectorGuardContext); + interface GuardConnectableInspectorProps { actorId: ActorId; children: ReactNode; @@ -35,31 +39,56 @@ export function GuardConnectableInspector({ }); if (destroyedAt) { - return Unavailable for inactive Actors.; + return ( + Unavailable for inactive Actors.} + > + {children} + + ); } if (sleepingAt) { if (filters.wakeOnSelect?.value?.[0] === "1") { return ( - - - + + + + } + > + {children} + ); } return ( - -

Unavailable for sleeping Actors.

- -
+ +

Unavailable for sleeping Actors.

+ + + } + > + {children} +
); } if (pendingAllocationAt && !startedAt) { return ( - - Cannot start Actor, runners are out of capacity. Add more - runners to run the Actor or increase runner capacity. - + + Cannot start Actor, runners are out of capacity. Add + more runners to run the Actor or increase runner + capacity. + + } + > + {children} + ); } @@ -81,14 +110,7 @@ function ActorContextProvider(props: { ); } -function ActorInspectorProvider({ - actorId, - children, -}: { - actorId: ActorId; - children: ReactNode; -}) { - const { data } = useSuspenseQuery(useManager().actorQueryOptions(actorId)); +function ActorInspectorProvider({ children }: { children: ReactNode }) { const { credentials } = useInspectorCredentials(); if (!credentials?.url || !credentials?.token) { @@ -98,9 +120,8 @@ function ActorInspectorProvider({ const actorContext = useMemo(() => { return createInspectorActorContext({ ...credentials, - name: data.name || "", }); - }, [credentials, data.name]); + }, [credentials]); return {children}; } @@ -133,7 +154,8 @@ function useActorEngineContext({ actorId }: { actorId: ActorId }) { const { actor, runner } = useActorRunner({ actorId }); const actorContext = useMemo(() => { - return createEngineActorContext({ + return createInspectorActorContext({ + url: getConfig().apiUrl, token: (runner?.metadata?.inspectorToken as string) || "", }); }, [runner?.metadata?.inspectorToken]); @@ -152,7 +174,15 @@ function ActorEngineProvider({ if (!runner || !actor.runner) { return ( - + + } + > + {children} + ); } diff --git a/frontend/src/components/actors/manager-context.tsx b/frontend/src/components/actors/manager-context.tsx index d02ffc4efc..d00d92b009 100644 --- a/frontend/src/components/actors/manager-context.tsx +++ b/frontend/src/components/actors/manager-context.tsx @@ -296,6 +296,7 @@ const defaultContext = { destroyedAt: data.destroyedAt ? new Date(data.destroyedAt) : null, + sleepingAt: data.sleepingAt ? new Date(data.sleepingAt) : null, startedAt: data.startedAt ? new Date(data.startedAt) : null, }), }); diff --git a/frontend/src/components/actors/worker/actor-repl.worker.ts b/frontend/src/components/actors/worker/actor-repl.worker.ts index 8478653a4b..ef066cafb1 100644 --- a/frontend/src/components/actors/worker/actor-repl.worker.ts +++ b/frontend/src/components/actors/worker/actor-repl.worker.ts @@ -183,27 +183,6 @@ function respond(msg: Response) { async function callAction({ name, args }: { name: string; args: unknown[] }) { if (!init) throw new Error("Actor not initialized"); - if (__APP_TYPE__ === "inspector") { - const client = createClient(init.endpoint).getForId(init.name, init.id); - return await client.action({ name, args }); - } - - const opts = - createEngineActorContext().createActorInspectorFetchConfiguration( - init.id, - ); - - const response = await fetch(`${getConfig().apiUrl}/actions/${name}`, { - ...opts, - headers: { - ...opts.headers, - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - throw response; - } - - return await response.json(); + const client = createClient(init.endpoint).getForId(init.name, init.id); + return await client.action({ name, args }); } diff --git a/frontend/src/components/actors/worker/actor-worker-context.tsx b/frontend/src/components/actors/worker/actor-worker-context.tsx index bff0fb3fb8..0e2170f5ab 100644 --- a/frontend/src/components/actors/worker/actor-worker-context.tsx +++ b/frontend/src/components/actors/worker/actor-worker-context.tsx @@ -32,11 +32,20 @@ export const ActorWorkerContextProvider = ({ children, actorId, }: ActorWorkerContextProviderProps) => { - const { data: { features, name, endpoint, destroyedAt, startedAt } = {} } = - useQuery(useManager().actorWorkerQueryOptions(actorId)); + const { + data: { + features, + name, + endpoint, + destroyedAt, + startedAt, + sleepingAt, + } = {}, + } = useQuery(useManager().actorWorkerQueryOptions(actorId)); const enabled = (features?.includes(ActorFeature.Console) && !destroyedAt && + !sleepingAt && !!startedAt) ?? false; diff --git a/frontend/src/components/connection-form.tsx b/frontend/src/components/connection-form.tsx index 50cc23be96..4c9fb60019 100644 --- a/frontend/src/components/connection-form.tsx +++ b/frontend/src/components/connection-form.tsx @@ -34,7 +34,7 @@ export const ConnectionForm = ( Endpoint diff --git a/frontend/src/queries/actor-engine.ts b/frontend/src/queries/actor-engine.ts deleted file mode 100644 index 020bf572eb..0000000000 --- a/frontend/src/queries/actor-engine.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { getConfig } from "@/components"; -import { - type ActorContext, - createDefaultActorContext, -} from "@/components/actors"; - -export const createEngineActorContext = ({ - token, -}: { - token?: string; -} = {}) => { - const def = createDefaultActorContext(); - - return { - ...def, - createActorInspectorFetchConfiguration(actorId) { - return { - headers: { - "x-rivet-actor": actorId, - "x-rivet-target": "actor", - ...(token ? { authorization: `Bearer ${token}` } : {}), - }, - }; - }, - createActorInspectorUrl() { - return new URL( - `${getConfig().apiUrl}/inspect`, - window.location.origin, - ).href; - }, - } satisfies ActorContext; -}; diff --git a/frontend/src/queries/actor-inspector.ts b/frontend/src/queries/actor-inspector.ts index bfc70965e8..ebf8566d05 100644 --- a/frontend/src/queries/actor-inspector.ts +++ b/frontend/src/queries/actor-inspector.ts @@ -1,4 +1,3 @@ -import { createActorInspectorClient } from "@rivetkit/core/inspector"; import { type ActorContext, createDefaultActorContext, @@ -8,43 +7,28 @@ import { ensureTrailingSlash } from "@/lib/utils"; export const createInspectorActorContext = ({ url, token, - name, }: { url: string; token: string; - name: string; }) => { const def = createDefaultActorContext(); const newUrl = new URL(url); - if (!newUrl.pathname.endsWith("registry/inspect")) { - if (!newUrl.pathname.endsWith("registry")) { - newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}registry`; - } - if (!newUrl.pathname.endsWith("inspect")) { - newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}inspect`; - } + if (!newUrl.pathname.endsWith("inspect")) { + newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}inspect`; } - newUrl.pathname = newUrl.pathname.replace( - "/registry/inspect", - "/registry/actors/inspect", - ); return { ...def, createActorInspectorFetchConfiguration(actorId) { return { headers: { - "X-RivetKit-Query": JSON.stringify({ - getForId: { actorId, name }, - }), - Authorization: `Bearer ${token}`, + "x-rivet-actor": actorId, + "x-rivet-target": "actor", + ...(token ? { authorization: `Bearer ${token}` } : {}), }, }; }, - createActorInspector(actorId) { - return createActorInspectorClient( - newUrl.href, - this.createActorInspectorFetchConfiguration(actorId), - ); + createActorInspectorUrl() { + return new URL(`${url}/inspect`, window.location.origin).href; }, } satisfies ActorContext; }; diff --git a/frontend/src/queries/manager-inspector.ts b/frontend/src/queries/manager-inspector.ts index 3bb8512d3a..7822205c20 100644 --- a/frontend/src/queries/manager-inspector.ts +++ b/frontend/src/queries/manager-inspector.ts @@ -6,23 +6,6 @@ import { infiniteQueryOptions } from "@tanstack/react-query"; import type { Actor, ActorId, ManagerContext } from "@/components/actors"; import { createDefaultManagerContext } from "@/components/actors/manager-context"; import { ensureTrailingSlash } from "@/lib/utils"; -import { queryClient } from "./global"; - -export const createClient = (url: string, token: string) => { - const newUrl = new URL(url); - if (!newUrl.pathname.endsWith("registry/inspect")) { - if (!newUrl.pathname.endsWith("registry")) { - newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}registry`; - } - if (!newUrl.pathname.endsWith("inspect")) { - newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}inspect`; - } - } - - return createManagerInspectorClient(newUrl.href, { - headers: { Authorization: `Bearer ${token}` }, - }); -}; export const createInspectorManagerContext = ({ url, @@ -31,7 +14,7 @@ export const createInspectorManagerContext = ({ url: string; token: string; }) => { - const client = createClient(url, token); + const client = createClient({ url, token }); const def = createDefaultManagerContext(); @@ -183,3 +166,14 @@ function transformActor(a: InspectorActor): Actor { features: a.features, }; } + +export function createClient({ url, token }: { url: string; token: string }) { + const newUrl = new URL(url); + if (!newUrl.pathname.endsWith("inspect")) { + newUrl.pathname = `${ensureTrailingSlash(newUrl.pathname)}inspect`; + } + + return createManagerInspectorClient(newUrl.href, { + headers: { Authorization: `Bearer ${token}` }, + }); +} diff --git a/frontend/src/routes/_layout.tsx b/frontend/src/routes/_layout.tsx index 0e60d80fa5..e2fefa900c 100644 --- a/frontend/src/routes/_layout.tsx +++ b/frontend/src/routes/_layout.tsx @@ -236,6 +236,7 @@ function Modals() { function InspectorContent({ content }: { content: ReactNode }) { const search = Route.useSearch(); + const navigate = Route.useNavigate(); const [credentials, setCredentials] = useState { try { - const client = createClient(values.username, values.token); + const client = createClient({ + url: values.username, + token: values.token, + }); const resp = await client.ping.$get(); if (!resp.ok) { throw resp; } + navigate({ + to: ".", + search: (old) => ({ + ...old, + u: values.username, + t: values.token, + }), + }); setCredentials({ url: values.username, token: values.token, @@ -364,7 +376,7 @@ function Connect({