Skip to content

Commit e1dfca1

Browse files
committed
engine version change dropdown
1 parent e632d50 commit e1dfca1

File tree

3 files changed

+169
-61
lines changed

3 files changed

+169
-61
lines changed

apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,24 +190,43 @@ export function useEngineQueueMetrics(
190190
});
191191
}
192192

193-
interface GetDeploymentInput {
193+
interface GetDeploymentPublicConfigurationInput {
194194
teamId: string;
195-
deploymentId: string;
196195
}
197196

198-
export function useEngineGetDeployment(input: GetDeploymentInput) {
197+
export function useEngineGetDeploymentPublicConfiguration(
198+
input: GetDeploymentPublicConfigurationInput,
199+
) {
199200
return useQuery({
200201
queryKey: engineKeys.deployment(),
201202
queryFn: async () => {
202-
const res = await fetch(
203-
`${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/${input.deploymentId}`,
204-
{
205-
method: "GET",
203+
// DEBUG
204+
return {
205+
serverVersions: {
206+
latest: "v2.1.0",
207+
recent: [
208+
"v2.0.35",
209+
"v2.0.34",
210+
"v2.0.33",
211+
"v2.0.32",
212+
"v2.0.31",
213+
"v2.0.30",
214+
"v2.0.29",
215+
"v2.0.28",
216+
"v2.0.27",
217+
"v2.0.26",
218+
],
206219
},
220+
};
221+
222+
const res = await fetch(
223+
`${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/public-configuration`,
224+
{ method: "GET" },
207225
);
208226
if (!res.ok) {
209227
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
210228
}
229+
211230
const json = await res.json();
212231
return json.data as {
213232
serverVersions: {

apps/dashboard/src/components/engine/badges/version.tsx

Lines changed: 142 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
1-
import { Button } from "@/components/ui/button";
2-
import { ToolTipLabel } from "@/components/ui/tooltip";
3-
import {
4-
type EngineInstance,
5-
useEngineGetDeployment,
6-
useEngineSystemHealth,
7-
useEngineUpdateDeployment,
8-
} from "@3rdweb-sdk/react/hooks/useEngine";
9-
import { CircleArrowDownIcon, CloudDownloadIcon } from "lucide-react";
10-
import { useState } from "react";
11-
121
import { Spinner } from "@/components/ui/Spinner/Spinner";
2+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
3+
import { Badge } from "@/components/ui/badge";
4+
import { Button } from "@/components/ui/button";
135
import {
146
Dialog,
157
DialogContent,
@@ -18,7 +10,28 @@ import {
1810
DialogHeader,
1911
DialogTitle,
2012
} from "@/components/ui/dialog";
13+
import {
14+
Select,
15+
SelectContent,
16+
SelectGroup,
17+
SelectItem,
18+
SelectTrigger,
19+
SelectValue,
20+
} from "@/components/ui/select";
21+
import { ToolTipLabel } from "@/components/ui/tooltip";
2122
import { TrackedLinkTW } from "@/components/ui/tracked-link";
23+
import {
24+
type EngineInstance,
25+
useEngineGetDeploymentPublicConfiguration,
26+
useEngineSystemHealth,
27+
useEngineUpdateDeployment,
28+
} from "@3rdweb-sdk/react/hooks/useEngine";
29+
import {
30+
CircleArrowDownIcon,
31+
CloudDownloadIcon,
32+
TriangleAlertIcon,
33+
} from "lucide-react";
34+
import { useState } from "react";
2235
import { toast } from "sonner";
2336
import invariant from "tiny-invariant";
2437

@@ -27,28 +40,39 @@ export const EngineVersionBadge = ({
2740
}: {
2841
instance: EngineInstance;
2942
}) => {
43+
const teamId = "DEBUG - UNIMPLEMENTED";
44+
3045
const healthQuery = useEngineSystemHealth(instance.url);
31-
const latestVersionQuery = useEngineGetDeployment();
46+
const publicConfigurationQuery = useEngineGetDeploymentPublicConfiguration({
47+
teamId,
48+
});
3249
const [isModalOpen, setModalOpen] = useState(false);
3350

34-
const currentVersion = healthQuery.data?.engineVersion ?? "...";
35-
const latestVersion = latestVersionQuery.data;
36-
const isStale = latestVersion && currentVersion !== latestVersion;
51+
if (!healthQuery.data || !publicConfigurationQuery.data) {
52+
return null;
53+
}
54+
55+
const currentVersion = healthQuery.data.engineVersion ?? "N/A";
56+
const hasNewerVersion =
57+
publicConfigurationQuery.data.serverVersions.latest !== currentVersion;
3758

38-
if (!isStale) {
59+
// Hide the change version modal unless owner.
60+
if (!instance.deploymentId) {
3961
return (
40-
<ToolTipLabel label="Latest Version">
41-
<Button variant="outline" asChild className="hover:bg-transparent">
42-
<div>{currentVersion}</div>
43-
</Button>
44-
</ToolTipLabel>
62+
<Button variant="outline" asChild className="hover:bg-transparent">
63+
<div>{currentVersion}</div>
64+
</Button>
4565
);
4666
}
4767

4868
return (
4969
<>
5070
<ToolTipLabel
51-
label="New version is available"
71+
label={
72+
hasNewerVersion
73+
? "An update is available"
74+
: "You are on the latest version"
75+
}
5276
leftIcon={
5377
<CircleArrowDownIcon className="size-4 text-link-foreground" />
5478
}
@@ -60,38 +84,41 @@ export const EngineVersionBadge = ({
6084
>
6185
{currentVersion}
6286

63-
{/* Notification Dot */}
64-
<span className="-top-1 -right-1 absolute">
65-
<PulseDot />
66-
</span>
87+
{/* Notification dot if an update is available */}
88+
{hasNewerVersion && (
89+
<span className="-top-1 -right-1 absolute">
90+
<PulseDot />
91+
</span>
92+
)}
6793
</Button>
6894
</ToolTipLabel>
6995

70-
{latestVersion && (
71-
<UpdateVersionModal
72-
open={isModalOpen}
73-
onOpenChange={setModalOpen}
74-
latestVersion={latestVersion}
75-
instance={instance}
76-
/>
77-
)}
96+
<ChangeVersionModal
97+
open={isModalOpen}
98+
onOpenChange={setModalOpen}
99+
instance={instance}
100+
currentVersion={currentVersion}
101+
serverVersions={publicConfigurationQuery.data.serverVersions}
102+
/>
78103
</>
79104
);
80105
};
81106

82-
const UpdateVersionModal = (props: {
107+
const ChangeVersionModal = (props: {
83108
open: boolean;
84109
onOpenChange: (open: boolean) => void;
85-
latestVersion: string;
86110
instance: EngineInstance;
111+
currentVersion: string;
112+
serverVersions: { latest: string; recent: string[] };
87113
}) => {
88-
const { open, onOpenChange, latestVersion, instance } = props;
89-
const updateDeploymentMutation = useEngineUpdateDeployment();
90-
91114
const teamId = "DEBUG - UNIMPLEMENTED";
115+
const { open, onOpenChange, instance, currentVersion, serverVersions } =
116+
props;
117+
const [selectedVersion, setSelectedVersion] = useState(serverVersions.latest);
118+
const updateDeploymentMutation = useEngineUpdateDeployment();
92119

93120
if (!instance.deploymentId) {
94-
// For self-hosted, show a prompt to the Github release page.
121+
// Self-hosted modal: prompt to update manually.
95122
return (
96123
<Dialog open={open} onOpenChange={onOpenChange}>
97124
<DialogContent
@@ -100,7 +127,7 @@ const UpdateVersionModal = (props: {
100127
>
101128
<DialogHeader>
102129
<DialogTitle className="mb-6 pr-4 font-semibold text-2xl tracking-tight">
103-
Update your self-hosted Engine to {latestVersion}
130+
Update your self-hosted Engine
104131
</DialogTitle>
105132
<DialogDescription>
106133
View the{" "}
@@ -121,40 +148,102 @@ const UpdateVersionModal = (props: {
121148
);
122149
}
123150

124-
const onClick = async () => {
151+
const onClickUpdate = async () => {
125152
invariant(instance.deploymentId, "Engine is missing deploymentId.");
126153

127154
try {
128155
const promise = updateDeploymentMutation.mutateAsync({
129156
teamId,
130157
deploymentId: instance.deploymentId,
131-
serverVersion: latestVersion,
158+
serverVersion: selectedVersion,
132159
});
133160
toast.promise(promise, {
134-
success: `Upgrading your Engine to ${latestVersion}. Please confirm after a few minutes.`,
135-
error: "Unexpected error updating your Engine.",
161+
success: `Updating your Engine to ${selectedVersion}.`,
162+
error: "Unexpected error updating Engine.",
136163
});
137164
await promise;
138165
} finally {
139166
onOpenChange(false);
140167
}
141168
};
142169

170+
// For cloud-hosted, prompt the user to select a version to update to.
143171
return (
144172
<Dialog open={open} onOpenChange={onOpenChange}>
145173
<DialogContent
146174
className="z-[10001] max-w-[400px]"
147175
dialogOverlayClassName="z-[10000]"
148176
>
149177
<DialogHeader>
150-
<DialogTitle>Update Engine to {latestVersion}?</DialogTitle>
151-
152-
<DialogDescription>
153-
It is recommended to pause traffic to Engine before performing this
154-
upgrade. There is &lt; 1 minute of expected downtime.
155-
</DialogDescription>
178+
<DialogTitle>Update Engine version</DialogTitle>
156179
</DialogHeader>
157180

181+
<DialogDescription>
182+
<Select
183+
onValueChange={(value) => setSelectedVersion(value)}
184+
value={selectedVersion}
185+
>
186+
<SelectTrigger>
187+
<SelectValue />
188+
</SelectTrigger>
189+
<SelectContent className="z-[10001]">
190+
<SelectGroup>
191+
<SelectItem
192+
value={serverVersions.latest}
193+
id={serverVersions.latest}
194+
>
195+
{serverVersions.latest}
196+
<Badge className="ml-2">latest</Badge>
197+
</SelectItem>
198+
{serverVersions.recent.map((version) => {
199+
const isCurrentVersion = version === currentVersion;
200+
return (
201+
<SelectItem
202+
key={version}
203+
value={version}
204+
id={version}
205+
disabled={isCurrentVersion}
206+
>
207+
{version}
208+
{isCurrentVersion && (
209+
<Badge className="ml-2">current</Badge>
210+
)}
211+
</SelectItem>
212+
);
213+
})}
214+
</SelectGroup>
215+
</SelectContent>
216+
</Select>
217+
218+
<div className="h-4" />
219+
220+
{currentVersion.startsWith("v") && (
221+
<div>
222+
<TrackedLinkTW
223+
href={`https://github.com/thirdweb-dev/engine/compare/${currentVersion}...${selectedVersion}`}
224+
category="engine"
225+
label="clicked-engine-releases"
226+
target="_blank"
227+
className="text-link-foreground hover:text-foreground"
228+
>
229+
View changes: {currentVersion} &rarr; {selectedVersion}
230+
</TrackedLinkTW>
231+
</div>
232+
)}
233+
234+
<div className="h-4" />
235+
236+
<Alert variant="warning">
237+
<TriangleAlertIcon className="!text-warning-text size-4" />
238+
<AlertTitle>There may be up to 1 minute of downtime.</AlertTitle>
239+
240+
<AlertDescription className="!pl-0 pt-2">
241+
We recommended pausing traffic to Engine before performing this
242+
version update.
243+
</AlertDescription>
244+
</Alert>
245+
</DialogDescription>
246+
158247
<DialogFooter className="mt-5">
159248
<Button
160249
type="button"
@@ -165,7 +254,7 @@ const UpdateVersionModal = (props: {
165254
</Button>
166255
<Button
167256
type="submit"
168-
onClick={onClick}
257+
onClick={onClickUpdate}
169258
variant="primary"
170259
className="gap-2"
171260
>
@@ -174,7 +263,7 @@ const UpdateVersionModal = (props: {
174263
) : (
175264
<CloudDownloadIcon className="size-4" />
176265
)}
177-
Update
266+
Update to {selectedVersion}
178267
</Button>
179268
</DialogFooter>
180269
</DialogContent>

apps/dashboard/src/components/engine/engine-instances-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ function DeleteSubscriptionModalContent(props: {
479479
<div className="h-4" />
480480

481481
<Alert variant="destructive">
482-
<TriangleAlertIcon className="!text-destructive-text size-5" />
482+
<TriangleAlertIcon className="!text-destructive-text size-4" />
483483
<AlertTitle>This action is irreversible!</AlertTitle>
484484

485485
<AlertDescription className="!pl-0 pt-2">

0 commit comments

Comments
 (0)