Skip to content

Commit f11447d

Browse files
jog1tNathanFlurry
authored andcommitted
fix: vercel connect
1 parent c6d3d19 commit f11447d

28 files changed

+2917
-1472
lines changed

frontend/packages/icons/manifest.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

frontend/packages/icons/scripts/postinstall.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ if (process.env.FONTAWESOME_PACKAGE_TOKEN) {
3434
private: true,
3535
sideEffects: false,
3636
dependencies: {
37-
"@awesome.me/kit-63db24046b": "^1.0.15",
37+
"@awesome.me/kit-63db24046b": "^1.0.18",
3838
"@fortawesome/pro-regular-svg-icons": "6.6.0",
3939
"@fortawesome/pro-solid-svg-icons": "6.6.0",
4040
},

frontend/src/app/billing/plan-card.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,13 @@ function PlanCard({
6363
className="w-full mt-4"
6464
children="Current Plan"
6565
{...buttonProps}
66-
>
67-
</Button>
66+
></Button>
6867
) : (
69-
<Button className="w-full mt-4" children={<>{custom ? "Contact Us" : "Upgrade"}</>} {...buttonProps}/>
68+
<Button
69+
className="w-full mt-4"
70+
children={<>{custom ? "Contact Us" : "Upgrade"}</>}
71+
{...buttonProps}
72+
/>
7073
)}
7174
</div>
7275
);

frontend/src/app/data-providers/engine-data-provider.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,28 @@ export const createNamespaceContext = ({
388388
},
389389
};
390390
},
391-
} satisfies DefaultDataProvider;
391+
runnerHealthCheckQueryOptions(opts: { runnerUrl: string }) {
392+
return queryOptions({
393+
queryKey: ["runner", "healthcheck", opts.runnerUrl],
394+
enabled: !!opts.runnerUrl,
395+
queryFn: async ({ signal: abortSignal }) => {
396+
const res =
397+
await client.runnerConfigs.serverlessHealthCheck(
398+
{
399+
url: opts.runnerUrl,
400+
},
401+
{ abortSignal },
402+
);
403+
404+
if ("success" in res) {
405+
return res.success;
406+
}
407+
408+
throw res.failure;
409+
},
410+
});
411+
},
412+
};
392413

393414
return {
394415
engineNamespace: namespace,
@@ -514,11 +535,11 @@ export const createNamespaceContext = ({
514535
config,
515536
}: {
516537
name: string;
517-
config: Rivet.RunnerConfig;
538+
config: Record<string, Rivet.RunnerConfig>;
518539
}) => {
519540
const response = await client.runnerConfigs.upsert(name, {
520541
namespace,
521-
...config,
542+
datacenters: config,
522543
});
523544
return response;
524545
},
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import {
2+
faAws,
3+
faCheck,
4+
faServer,
5+
faSpinnerThird,
6+
Icon,
7+
} from "@rivet-gg/icons";
8+
import {
9+
useInfiniteQuery,
10+
useMutation,
11+
usePrefetchInfiniteQuery,
12+
useSuspenseInfiniteQuery,
13+
} from "@tanstack/react-query";
14+
import confetti from "canvas-confetti";
15+
import { useEffect } from "react";
16+
import { useController, useFormContext } from "react-hook-form";
17+
import z from "zod";
18+
import * as ConnectVercelForm from "@/app/forms/connect-vercel-form";
19+
import { cn, type DialogContentProps, Frame } from "@/components";
20+
import { type Region, useEngineCompatDataProvider } from "@/components/actors";
21+
import { defineStepper } from "@/components/ui/stepper";
22+
import { StepperForm } from "../forms/stepper-form";
23+
import { EnvVariablesStep } from "./connect-railway-frame";
24+
25+
const stepper = defineStepper(
26+
{
27+
id: "step-1",
28+
title: "Configure",
29+
assist: false,
30+
next: "Next",
31+
schema: z.object({
32+
runnerName: z.string().min(1, "Runner name is required"),
33+
datacenters: z
34+
.record(z.boolean())
35+
.refine(
36+
(data) => Object.values(data).some(Boolean),
37+
"At least one datacenter must be selected",
38+
),
39+
headers: z.array(z.tuple([z.string(), z.string()])).default([]),
40+
slotsPerRunner: z.coerce.number().min(1, "Must be at least 1"),
41+
maxRunners: z.coerce.number().min(1, "Must be at least 1"),
42+
runnerMargin: z.coerce.number().min(0, "Must be 0 or greater"),
43+
}),
44+
},
45+
{
46+
id: "step-2",
47+
title: "Deploy",
48+
assist: false,
49+
schema: z.object({}),
50+
next: "Next",
51+
},
52+
{
53+
id: "step-3",
54+
title: "Confirm Connection",
55+
assist: false,
56+
schema: z.object({
57+
success: z.boolean().refine((v) => v === true, {
58+
message: "Runner must be connected to proceed",
59+
}),
60+
}),
61+
next: "Add",
62+
},
63+
);
64+
65+
interface ConnectAwsFrameContentProps extends DialogContentProps {}
66+
67+
export default function ConnectAwsFrameContent({
68+
onClose,
69+
}: ConnectAwsFrameContentProps) {
70+
usePrefetchInfiniteQuery({
71+
...useEngineCompatDataProvider().regionsQueryOptions(),
72+
pages: Infinity,
73+
});
74+
75+
const { data: datacenters } = useSuspenseInfiniteQuery(
76+
useEngineCompatDataProvider().regionsQueryOptions(),
77+
);
78+
79+
return (
80+
<>
81+
<Frame.Header>
82+
<Frame.Title className="gap-2 flex items-center">
83+
<div>
84+
Add <Icon icon={faAws} className="ml-0.5" /> AWS ECS
85+
</div>
86+
</Frame.Title>
87+
</Frame.Header>
88+
<Frame.Content>
89+
<FormStepper onClose={onClose} datacenters={datacenters} />
90+
</Frame.Content>
91+
</>
92+
);
93+
}
94+
95+
function FormStepper({
96+
onClose,
97+
datacenters,
98+
}: {
99+
onClose?: () => void;
100+
datacenters: Region[];
101+
}) {
102+
const { mutateAsync } = useMutation({
103+
...useEngineCompatDataProvider().createRunnerConfigMutationOptions(),
104+
onSuccess: () => {
105+
confetti({
106+
angle: 60,
107+
spread: 55,
108+
origin: { x: 0 },
109+
});
110+
confetti({
111+
angle: 120,
112+
spread: 55,
113+
origin: { x: 1 },
114+
});
115+
onClose?.();
116+
},
117+
});
118+
return (
119+
<StepperForm
120+
{...stepper}
121+
onSubmit={async ({ values }) => {
122+
const selectedDatacenters = Object.entries(values.datacenters)
123+
.filter(([, selected]) => selected)
124+
.map(([id]) => id);
125+
126+
const config = Object.fromEntries(
127+
selectedDatacenters.map((dc) => [
128+
dc,
129+
{
130+
normal: {},
131+
metadata: { provider: "aws" },
132+
},
133+
]),
134+
);
135+
136+
await mutateAsync({
137+
name: values.runnerName,
138+
config,
139+
});
140+
}}
141+
defaultValues={{
142+
runnerName: "default",
143+
slotsPerRunner: 25,
144+
maxRunners: 1000,
145+
runnerMargin: 0,
146+
headers: [],
147+
success: false,
148+
datacenters: Object.fromEntries(
149+
datacenters.map((dc) => [dc.id, true]),
150+
),
151+
}}
152+
content={{
153+
"step-1": () => <Step1 />,
154+
"step-2": () => <Step2 />,
155+
"step-3": () => <Step3 />,
156+
}}
157+
/>
158+
);
159+
}
160+
161+
function Step1() {
162+
return (
163+
<div className="space-y-4">
164+
<ConnectVercelForm.RunnerName />
165+
<ConnectVercelForm.Datacenters />
166+
<ConnectVercelForm.Headers />
167+
<ConnectVercelForm.SlotsPerRunner />
168+
<ConnectVercelForm.MaxRunners />
169+
<ConnectVercelForm.RunnerMargin />
170+
</div>
171+
);
172+
}
173+
174+
function Step2() {
175+
return (
176+
<>
177+
<p>Set the following environment variables.</p>
178+
<EnvVariablesStep />
179+
</>
180+
);
181+
}
182+
183+
function Step3({ provider = "aws" }: { provider?: string }) {
184+
usePrefetchInfiniteQuery({
185+
...useEngineCompatDataProvider().runnersQueryOptions(),
186+
pages: Infinity,
187+
});
188+
189+
const { data: queryData } = useInfiniteQuery({
190+
...useEngineCompatDataProvider().runnersQueryOptions(),
191+
refetchInterval: 1000,
192+
maxPages: Infinity,
193+
});
194+
195+
const { watch } = useFormContext();
196+
197+
const datacenters: Record<string, boolean> = watch("datacenters");
198+
const chosenDatacenters = Object.entries(datacenters)
199+
.filter(([, enabled]) => enabled)
200+
.map(([dc]) => dc);
201+
202+
const runnerName: string = watch("runnerName");
203+
204+
const success = chosenDatacenters
205+
.map((dc) =>
206+
queryData?.find(
207+
(runner) =>
208+
runner.datacenter === dc && runner.name === runnerName,
209+
),
210+
)
211+
.every((v) => v);
212+
213+
const {
214+
field: { onChange },
215+
} = useController({ name: "success" });
216+
217+
useEffect(() => {
218+
onChange(success);
219+
}, [success]);
220+
221+
return (
222+
<div
223+
className={cn(
224+
"text-center h-24 text-muted-foreground text-sm overflow-hidden flex items-center justify-center",
225+
success && "text-primary-foreground",
226+
)}
227+
>
228+
{success ? (
229+
<>
230+
<Icon icon={faCheck} className="mr-1.5 text-primary" />{" "}
231+
Runner successfully connected
232+
</>
233+
) : (
234+
<div className="flex flex-col items-center gap-2">
235+
<div className="flex items-center">
236+
<Icon
237+
icon={faSpinnerThird}
238+
className="mr-1.5 animate-spin"
239+
/>{" "}
240+
Waiting for Runner to connect...
241+
</div>
242+
</div>
243+
)}
244+
</div>
245+
);
246+
}

0 commit comments

Comments
 (0)