From e632d50790b209bf2730d3c57e386e90ff24f74f Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Mon, 4 Nov 2024 07:50:03 +0800 Subject: [PATCH 1/5] wip --- .../src/@3rdweb-sdk/react/cache-keys.ts | 2 +- .../src/@3rdweb-sdk/react/hooks/useEngine.ts | 44 +++++++++++++------ .../src/components/engine/badges/version.tsx | 15 ++++--- .../engine/engine-instances-table.tsx | 21 +++++---- 4 files changed, 53 insertions(+), 29 deletions(-) diff --git a/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts b/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts index 1d2c0ca1f90..d19ee000180 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts @@ -111,7 +111,7 @@ export const engineKeys = { ] as const, health: (instance: string) => [...engineKeys.all, instance, "health"] as const, - latestVersion: () => [...engineKeys.all, "latestVersion"] as const, + deployment: () => [...engineKeys.all, "deployment"] as const, systemMetrics: (engineId: string) => [...engineKeys.all, engineId, "systemMetrics"] as const, queueMetrics: (engineId: string) => diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts index 28e88c9e4d8..7900ffbd39d 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts @@ -190,32 +190,46 @@ export function useEngineQueueMetrics( }); } -export function useEngineLatestVersion() { +interface GetDeploymentInput { + teamId: string; + deploymentId: string; +} + +export function useEngineGetDeployment(input: GetDeploymentInput) { return useQuery({ - queryKey: engineKeys.latestVersion(), + queryKey: engineKeys.deployment(), queryFn: async () => { - const res = await fetch(`${THIRDWEB_API_HOST}/v1/engine/latest-version`, { - method: "GET", - }); + const res = await fetch( + `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/${input.deploymentId}`, + { + method: "GET", + }, + ); if (!res.ok) { throw new Error(`Unexpected status ${res.status}: ${await res.text()}`); } const json = await res.json(); - return json.data.version as string; + return json.data as { + serverVersions: { + latest: string; + recent: string[]; + }; + }; }, }); } -interface UpdateVersionInput { +interface UpdateDeploymentInput { + teamId: string; deploymentId: string; serverVersion: string; } -export function useEngineUpdateServerVersion() { +export function useEngineUpdateDeployment() { return useMutation({ - mutationFn: async (input: UpdateVersionInput) => { + mutationFn: async (input: UpdateDeploymentInput) => { const res = await fetch( - `${THIRDWEB_API_HOST}/v2/engine/deployments/${input.deploymentId}/infrastructure`, + `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/${input.deploymentId}`, { method: "PUT", headers: { @@ -261,24 +275,26 @@ export function useEngineRemoveFromDashboard() { }); } -export interface DeleteCloudHostedInput { +export interface DeleteDeploymentInput { + teamId: string; deploymentId: string; reason: "USING_SELF_HOSTED" | "TOO_EXPENSIVE" | "MISSING_FEATURES" | "OTHER"; feedback: string; } -export function useEngineDeleteCloudHosted() { +export function useEngineDeleteDeployment() { const { user } = useLoggedInUser(); const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ + teamId, deploymentId, reason, feedback, - }: DeleteCloudHostedInput) => { + }: DeleteDeploymentInput) => { const res = await fetch( - `${THIRDWEB_API_HOST}/v2/engine/deployments/${deploymentId}/infrastructure/delete`, + `${THIRDWEB_API_HOST}/v1/teams/${teamId}/engine/deployments/${deploymentId}/delete`, { method: "POST", headers: { diff --git a/apps/dashboard/src/components/engine/badges/version.tsx b/apps/dashboard/src/components/engine/badges/version.tsx index 390584f8623..595e8c40b51 100644 --- a/apps/dashboard/src/components/engine/badges/version.tsx +++ b/apps/dashboard/src/components/engine/badges/version.tsx @@ -2,9 +2,9 @@ import { Button } from "@/components/ui/button"; import { ToolTipLabel } from "@/components/ui/tooltip"; import { type EngineInstance, - useEngineLatestVersion, + useEngineGetDeployment, useEngineSystemHealth, - useEngineUpdateServerVersion, + useEngineUpdateDeployment, } from "@3rdweb-sdk/react/hooks/useEngine"; import { CircleArrowDownIcon, CloudDownloadIcon } from "lucide-react"; import { useState } from "react"; @@ -28,7 +28,7 @@ export const EngineVersionBadge = ({ instance: EngineInstance; }) => { const healthQuery = useEngineSystemHealth(instance.url); - const latestVersionQuery = useEngineLatestVersion(); + const latestVersionQuery = useEngineGetDeployment(); const [isModalOpen, setModalOpen] = useState(false); const currentVersion = healthQuery.data?.engineVersion ?? "..."; @@ -86,7 +86,9 @@ const UpdateVersionModal = (props: { instance: EngineInstance; }) => { const { open, onOpenChange, latestVersion, instance } = props; - const updateEngineServerMutation = useEngineUpdateServerVersion(); + const updateDeploymentMutation = useEngineUpdateDeployment(); + + const teamId = "DEBUG - UNIMPLEMENTED"; if (!instance.deploymentId) { // For self-hosted, show a prompt to the Github release page. @@ -123,7 +125,8 @@ const UpdateVersionModal = (props: { invariant(instance.deploymentId, "Engine is missing deploymentId."); try { - const promise = updateEngineServerMutation.mutateAsync({ + const promise = updateDeploymentMutation.mutateAsync({ + teamId, deploymentId: instance.deploymentId, serverVersion: latestVersion, }); @@ -166,7 +169,7 @@ const UpdateVersionModal = (props: { variant="primary" className="gap-2" > - {updateEngineServerMutation.isPending ? ( + {updateDeploymentMutation.isPending ? ( ) : ( diff --git a/apps/dashboard/src/components/engine/engine-instances-table.tsx b/apps/dashboard/src/components/engine/engine-instances-table.tsx index 92ccfb59440..608808d7df1 100644 --- a/apps/dashboard/src/components/engine/engine-instances-table.tsx +++ b/apps/dashboard/src/components/engine/engine-instances-table.tsx @@ -14,10 +14,10 @@ import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { ToolTipLabel } from "@/components/ui/tooltip"; import { - type DeleteCloudHostedInput, + type DeleteDeploymentInput, type EditEngineInstanceInput, type EngineInstance, - useEngineDeleteCloudHosted, + useEngineDeleteDeployment, useEngineEditInstance, type useEngineInstances, useEngineRemoveFromDashboard, @@ -383,17 +383,20 @@ function DeleteSubscriptionModalContent(props: { "Instance must have a deploymentId to be cancelled.", ); - const deleteCloudHosted = useEngineDeleteCloudHosted(); + const teamId = "DEBUG - UNIMPLEMENTED"; + + const deleteDeploymentMutation = useEngineDeleteDeployment(); const [ackDeletion, setAckDeletion] = useState(false); - const form = useForm({ + const form = useForm({ defaultValues: { + teamId, deploymentId: instance.deploymentId, }, reValidateMode: "onChange", }); - const onSubmit = (data: DeleteCloudHostedInput) => { - deleteCloudHosted.mutate(data, { + const onSubmit = (data: DeleteDeploymentInput) => { + deleteDeploymentMutation.mutate(data, { onSuccess: () => { toast.success("Deleting Engine. Please check again in a few minutes.", { dismissible: true, @@ -502,12 +505,14 @@ function DeleteSubscriptionModalContent(props: { variant="destructive" disabled={ !ackDeletion || - deleteCloudHosted.isPending || + deleteDeploymentMutation.isPending || !form.formState.isValid } className="gap-2" > - {deleteCloudHosted.isPending && } + {deleteDeploymentMutation.isPending && ( + + )} Permanently Delete Engine From e1dfca154d820d15f2036ec8d50377d860490926 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Tue, 5 Nov 2024 09:05:07 +0800 Subject: [PATCH 2/5] engine version change dropdown --- .../src/@3rdweb-sdk/react/hooks/useEngine.ts | 33 ++- .../src/components/engine/badges/version.tsx | 195 +++++++++++++----- .../engine/engine-instances-table.tsx | 2 +- 3 files changed, 169 insertions(+), 61 deletions(-) diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts index 7900ffbd39d..a2d46fa880b 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts @@ -190,24 +190,43 @@ export function useEngineQueueMetrics( }); } -interface GetDeploymentInput { +interface GetDeploymentPublicConfigurationInput { teamId: string; - deploymentId: string; } -export function useEngineGetDeployment(input: GetDeploymentInput) { +export function useEngineGetDeploymentPublicConfiguration( + input: GetDeploymentPublicConfigurationInput, +) { return useQuery({ queryKey: engineKeys.deployment(), queryFn: async () => { - const res = await fetch( - `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/${input.deploymentId}`, - { - method: "GET", + // DEBUG + return { + serverVersions: { + latest: "v2.1.0", + recent: [ + "v2.0.35", + "v2.0.34", + "v2.0.33", + "v2.0.32", + "v2.0.31", + "v2.0.30", + "v2.0.29", + "v2.0.28", + "v2.0.27", + "v2.0.26", + ], }, + }; + + const res = await fetch( + `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/public-configuration`, + { method: "GET" }, ); if (!res.ok) { throw new Error(`Unexpected status ${res.status}: ${await res.text()}`); } + const json = await res.json(); return json.data as { serverVersions: { diff --git a/apps/dashboard/src/components/engine/badges/version.tsx b/apps/dashboard/src/components/engine/badges/version.tsx index 595e8c40b51..469bc9a5560 100644 --- a/apps/dashboard/src/components/engine/badges/version.tsx +++ b/apps/dashboard/src/components/engine/badges/version.tsx @@ -1,15 +1,7 @@ -import { Button } from "@/components/ui/button"; -import { ToolTipLabel } from "@/components/ui/tooltip"; -import { - type EngineInstance, - useEngineGetDeployment, - useEngineSystemHealth, - useEngineUpdateDeployment, -} from "@3rdweb-sdk/react/hooks/useEngine"; -import { CircleArrowDownIcon, CloudDownloadIcon } from "lucide-react"; -import { useState } from "react"; - import { Spinner } from "@/components/ui/Spinner/Spinner"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, @@ -18,7 +10,28 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ToolTipLabel } from "@/components/ui/tooltip"; import { TrackedLinkTW } from "@/components/ui/tracked-link"; +import { + type EngineInstance, + useEngineGetDeploymentPublicConfiguration, + useEngineSystemHealth, + useEngineUpdateDeployment, +} from "@3rdweb-sdk/react/hooks/useEngine"; +import { + CircleArrowDownIcon, + CloudDownloadIcon, + TriangleAlertIcon, +} from "lucide-react"; +import { useState } from "react"; import { toast } from "sonner"; import invariant from "tiny-invariant"; @@ -27,28 +40,39 @@ export const EngineVersionBadge = ({ }: { instance: EngineInstance; }) => { + const teamId = "DEBUG - UNIMPLEMENTED"; + const healthQuery = useEngineSystemHealth(instance.url); - const latestVersionQuery = useEngineGetDeployment(); + const publicConfigurationQuery = useEngineGetDeploymentPublicConfiguration({ + teamId, + }); const [isModalOpen, setModalOpen] = useState(false); - const currentVersion = healthQuery.data?.engineVersion ?? "..."; - const latestVersion = latestVersionQuery.data; - const isStale = latestVersion && currentVersion !== latestVersion; + if (!healthQuery.data || !publicConfigurationQuery.data) { + return null; + } + + const currentVersion = healthQuery.data.engineVersion ?? "N/A"; + const hasNewerVersion = + publicConfigurationQuery.data.serverVersions.latest !== currentVersion; - if (!isStale) { + // Hide the change version modal unless owner. + if (!instance.deploymentId) { return ( - - - + ); } return ( <> } @@ -60,38 +84,41 @@ export const EngineVersionBadge = ({ > {currentVersion} - {/* Notification Dot */} - - - + {/* Notification dot if an update is available */} + {hasNewerVersion && ( + + + + )} - {latestVersion && ( - - )} + ); }; -const UpdateVersionModal = (props: { +const ChangeVersionModal = (props: { open: boolean; onOpenChange: (open: boolean) => void; - latestVersion: string; instance: EngineInstance; + currentVersion: string; + serverVersions: { latest: string; recent: string[] }; }) => { - const { open, onOpenChange, latestVersion, instance } = props; - const updateDeploymentMutation = useEngineUpdateDeployment(); - const teamId = "DEBUG - UNIMPLEMENTED"; + const { open, onOpenChange, instance, currentVersion, serverVersions } = + props; + const [selectedVersion, setSelectedVersion] = useState(serverVersions.latest); + const updateDeploymentMutation = useEngineUpdateDeployment(); if (!instance.deploymentId) { - // For self-hosted, show a prompt to the Github release page. + // Self-hosted modal: prompt to update manually. return ( - Update your self-hosted Engine to {latestVersion} + Update your self-hosted Engine View the{" "} @@ -121,18 +148,18 @@ const UpdateVersionModal = (props: { ); } - const onClick = async () => { + const onClickUpdate = async () => { invariant(instance.deploymentId, "Engine is missing deploymentId."); try { const promise = updateDeploymentMutation.mutateAsync({ teamId, deploymentId: instance.deploymentId, - serverVersion: latestVersion, + serverVersion: selectedVersion, }); toast.promise(promise, { - success: `Upgrading your Engine to ${latestVersion}. Please confirm after a few minutes.`, - error: "Unexpected error updating your Engine.", + success: `Updating your Engine to ${selectedVersion}.`, + error: "Unexpected error updating Engine.", }); await promise; } finally { @@ -140,6 +167,7 @@ const UpdateVersionModal = (props: { } }; + // For cloud-hosted, prompt the user to select a version to update to. return ( - Update Engine to {latestVersion}? - - - It is recommended to pause traffic to Engine before performing this - upgrade. There is < 1 minute of expected downtime. - + Update Engine version + + + +
+ + {currentVersion.startsWith("v") && ( +
+ + View changes: {currentVersion} → {selectedVersion} + +
+ )} + +
+ + + + There may be up to 1 minute of downtime. + + + We recommended pausing traffic to Engine before performing this + version update. + + + + diff --git a/apps/dashboard/src/components/engine/engine-instances-table.tsx b/apps/dashboard/src/components/engine/engine-instances-table.tsx index 608808d7df1..eee64579d86 100644 --- a/apps/dashboard/src/components/engine/engine-instances-table.tsx +++ b/apps/dashboard/src/components/engine/engine-instances-table.tsx @@ -479,7 +479,7 @@ function DeleteSubscriptionModalContent(props: {
- + This action is irreversible! From bbbe8fa07969b230525963e5ad47f102fce36fe3 Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Fri, 22 Nov 2024 13:32:29 +0800 Subject: [PATCH 3/5] select engine version to update to --- .../src/@3rdweb-sdk/react/hooks/useEngine.ts | 35 +++------- .../_components/EnginePageLayout.tsx | 2 +- .../[engineId]/_components/version.tsx | 66 +++++++++++-------- .../overview/overview-page.client.tsx | 7 +- .../~/engine/(instance)/[engineId]/page.tsx | 10 ++- .../src/data/analytics/fetch-analytics.ts | 2 + 6 files changed, 65 insertions(+), 57 deletions(-) diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts index f888edaad54..829718d8ddb 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts @@ -195,31 +195,19 @@ interface GetDeploymentPublicConfigurationInput { teamId: string; } +interface DeploymentPublicConfigurationResponse { + serverVersions: { + name: string; + createdAt: string; + }[]; +} + export function useEngineGetDeploymentPublicConfiguration( input: GetDeploymentPublicConfigurationInput, ) { - return useQuery({ + return useQuery({ queryKey: engineKeys.deployment(), queryFn: async () => { - // DEBUG - return { - serverVersions: { - latest: "v2.1.0", - recent: [ - "v2.0.35", - "v2.0.34", - "v2.0.33", - "v2.0.32", - "v2.0.31", - "v2.0.30", - "v2.0.29", - "v2.0.28", - "v2.0.27", - "v2.0.26", - ], - }, - }; - const res = await fetch( `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/public-configuration`, { method: "GET" }, @@ -229,12 +217,7 @@ export function useEngineGetDeploymentPublicConfiguration( } const json = await res.json(); - return json.data as { - serverVersions: { - latest: string; - recent: string[]; - }; - }; + return json.data as DeploymentPublicConfigurationResponse; }, }); } diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx index 78cd63c5b49..c603b848f32 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx @@ -261,7 +261,7 @@ function RenderEngineInstanceHeader(props: { )}
- +
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx index 469bc9a5560..0f918314419 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx @@ -26,6 +26,7 @@ import { useEngineSystemHealth, useEngineUpdateDeployment, } from "@3rdweb-sdk/react/hooks/useEngine"; +import { formatDistanceToNow } from "date-fns"; import { CircleArrowDownIcon, CloudDownloadIcon, @@ -37,24 +38,27 @@ import invariant from "tiny-invariant"; export const EngineVersionBadge = ({ instance, + teamId, }: { instance: EngineInstance; + teamId: string; }) => { - const teamId = "DEBUG - UNIMPLEMENTED"; - const healthQuery = useEngineSystemHealth(instance.url); const publicConfigurationQuery = useEngineGetDeploymentPublicConfiguration({ teamId, }); const [isModalOpen, setModalOpen] = useState(false); - if (!healthQuery.data || !publicConfigurationQuery.data) { + if ( + !healthQuery.data || + publicConfigurationQuery.data.serverVersions?.length === 0 + ) { return null; } + const serverVersions = publicConfigurationQuery.data.serverVersions; const currentVersion = healthQuery.data.engineVersion ?? "N/A"; - const hasNewerVersion = - publicConfigurationQuery.data.serverVersions.latest !== currentVersion; + const hasNewerVersion = serverVersions[0].name !== currentVersion; // Hide the change version modal unless owner. if (!instance.deploymentId) { @@ -98,7 +102,8 @@ export const EngineVersionBadge = ({ onOpenChange={setModalOpen} instance={instance} currentVersion={currentVersion} - serverVersions={publicConfigurationQuery.data.serverVersions} + serverVersions={serverVersions} + teamId={teamId} /> ); @@ -109,12 +114,20 @@ const ChangeVersionModal = (props: { onOpenChange: (open: boolean) => void; instance: EngineInstance; currentVersion: string; - serverVersions: { latest: string; recent: string[] }; + serverVersions: { name: string; createdAt: string }[]; + teamId: string; }) => { - const teamId = "DEBUG - UNIMPLEMENTED"; - const { open, onOpenChange, instance, currentVersion, serverVersions } = - props; - const [selectedVersion, setSelectedVersion] = useState(serverVersions.latest); + const { + open, + onOpenChange, + instance, + currentVersion, + serverVersions, + teamId, + } = props; + const [selectedVersion, setSelectedVersion] = useState( + serverVersions[0]?.name, + ); const updateDeploymentMutation = useEngineUpdateDeployment(); if (!instance.deploymentId) { @@ -188,26 +201,27 @@ const ChangeVersionModal = (props: { - - {serverVersions.latest} - latest - - {serverVersions.recent.map((version) => { - const isCurrentVersion = version === currentVersion; + {serverVersions.map(({ name, createdAt }, idx) => { + const isCurrentVersion = name === currentVersion; + const isLatestVersion = idx === 0; return ( - {version} - {isCurrentVersion && ( + {name} + + {formatDistanceToNow(new Date(createdAt), { + addSuffix: true, + })} + + {isCurrentVersion ? ( current - )} + ) : isLatestVersion ? ( + latest + ) : null} ); })} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx index 565c1a78d9d..e6f829a8d56 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx @@ -5,15 +5,16 @@ import { EngineOverview } from "./components/engine-overview"; export function EngineOverviewPage(props: { engineId: string; - team_slug: string; + teamSlug: string; + teamId: string; }) { return ( ( - + )} - teamSlug={props.team_slug} + teamId={props.teamId} /> ); } diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx index f6947e239bd..3074e2098c8 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx @@ -1,12 +1,20 @@ +import { getTeamBySlug } from "@/api/team"; +import { redirect } from "next/navigation"; import { EngineOverviewPage } from "./overview/overview-page.client"; import type { EngineInstancePageProps } from "./types"; export default async function Page(props: EngineInstancePageProps) { const params = await props.params; + const team = await getTeamBySlug(params.team_slug); + if (!team) { + redirect("/team"); + } + return ( ); } diff --git a/apps/dashboard/src/data/analytics/fetch-analytics.ts b/apps/dashboard/src/data/analytics/fetch-analytics.ts index 533c5ec789e..3127e74ff60 100644 --- a/apps/dashboard/src/data/analytics/fetch-analytics.ts +++ b/apps/dashboard/src/data/analytics/fetch-analytics.ts @@ -4,6 +4,8 @@ export async function fetchAnalytics( input: string | URL, init?: RequestInit, ): Promise { + return; + const [pathname, searchParams] = input.toString().split("?"); if (!pathname) { throw new Error("Invalid input, no pathname provided"); From b41411815238c4c80f24fa894d883d9dcaeb912b Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Fri, 22 Nov 2024 13:44:07 +0800 Subject: [PATCH 4/5] undo unnecessary changes --- .../src/@3rdweb-sdk/react/cache-keys.ts | 3 ++- .../src/@3rdweb-sdk/react/hooks/useEngine.ts | 12 +++++------ .../overview/engine-instances-table.tsx | 21 +++++++------------ .../src/data/analytics/fetch-analytics.ts | 2 -- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts b/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts index d19ee000180..4d99b6174ac 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts @@ -111,7 +111,8 @@ export const engineKeys = { ] as const, health: (instance: string) => [...engineKeys.all, instance, "health"] as const, - deployment: () => [...engineKeys.all, "deployment"] as const, + deploymentPublicConfiguration: () => + [...engineKeys.all, "deploymentPublicConfiguration"] as const, systemMetrics: (engineId: string) => [...engineKeys.all, engineId, "systemMetrics"] as const, queueMetrics: (engineId: string) => diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts index 829718d8ddb..9fe1e6b789a 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts @@ -206,7 +206,7 @@ export function useEngineGetDeploymentPublicConfiguration( input: GetDeploymentPublicConfigurationInput, ) { return useQuery({ - queryKey: engineKeys.deployment(), + queryKey: engineKeys.deploymentPublicConfiguration(), queryFn: async () => { const res = await fetch( `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/public-configuration`, @@ -278,26 +278,24 @@ export function useEngineRemoveFromDashboard() { }); } -export interface DeleteDeploymentInput { - teamId: string; +export interface DeleteCloudHostedInput { deploymentId: string; reason: "USING_SELF_HOSTED" | "TOO_EXPENSIVE" | "MISSING_FEATURES" | "OTHER"; feedback: string; } -export function useEngineDeleteDeployment() { +export function useEngineDeleteCloudHosted() { const { user } = useLoggedInUser(); const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ - teamId, deploymentId, reason, feedback, - }: DeleteDeploymentInput) => { + }: DeleteCloudHostedInput) => { const res = await fetch( - `${THIRDWEB_API_HOST}/v1/teams/${teamId}/engine/deployments/${deploymentId}/delete`, + `${THIRDWEB_API_HOST}/v2/engine/deployments/${deploymentId}/infrastructure/delete`, { method: "POST", headers: { diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx index 24af01f4ae3..9a351051350 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(general)/overview/engine-instances-table.tsx @@ -14,10 +14,10 @@ import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { ToolTipLabel } from "@/components/ui/tooltip"; import { - type DeleteDeploymentInput, + type DeleteCloudHostedInput, type EditEngineInstanceInput, type EngineInstance, - useEngineDeleteDeployment, + useEngineDeleteCloudHosted, useEngineEditInstance, type useEngineInstances, useEngineRemoveFromDashboard, @@ -384,20 +384,17 @@ function DeleteSubscriptionModalContent(props: { "Instance must have a deploymentId to be cancelled.", ); - const teamId = "DEBUG - UNIMPLEMENTED"; - - const deleteDeploymentMutation = useEngineDeleteDeployment(); + const deleteCloudHosted = useEngineDeleteCloudHosted(); const [ackDeletion, setAckDeletion] = useState(false); - const form = useForm({ + const form = useForm({ defaultValues: { - teamId, deploymentId: instance.deploymentId, }, reValidateMode: "onChange", }); - const onSubmit = (data: DeleteDeploymentInput) => { - deleteDeploymentMutation.mutate(data, { + const onSubmit = (data: DeleteCloudHostedInput) => { + deleteCloudHosted.mutate(data, { onSuccess: () => { toast.success("Deleting Engine. Please check again in a few minutes.", { dismissible: true, @@ -494,14 +491,12 @@ function DeleteSubscriptionModalContent(props: { variant="destructive" disabled={ !ackDeletion || - deleteDeploymentMutation.isPending || + deleteCloudHosted.isPending || !form.formState.isValid } className="gap-2" > - {deleteDeploymentMutation.isPending && ( - - )} + {deleteCloudHosted.isPending && } Permanently Delete Engine diff --git a/apps/dashboard/src/data/analytics/fetch-analytics.ts b/apps/dashboard/src/data/analytics/fetch-analytics.ts index 3127e74ff60..533c5ec789e 100644 --- a/apps/dashboard/src/data/analytics/fetch-analytics.ts +++ b/apps/dashboard/src/data/analytics/fetch-analytics.ts @@ -4,8 +4,6 @@ export async function fetchAnalytics( input: string | URL, init?: RequestInit, ): Promise { - return; - const [pathname, searchParams] = input.toString().split("?"); if (!pathname) { throw new Error("Invalid input, no pathname provided"); From 689fd31d7e5d78514a80b4ae8d7c26f44d21067e Mon Sep 17 00:00:00 2001 From: Phillip Ho Date: Sun, 24 Nov 2024 10:06:59 +0800 Subject: [PATCH 5/5] move to teamSlug --- .../src/@3rdweb-sdk/react/hooks/useEngine.ts | 8 ++-- .../_components/EnginePageLayout.tsx | 9 ++++- .../[engineId]/_components/version.tsx | 38 ++++++++----------- .../overview/overview-page.client.tsx | 3 +- .../~/engine/(instance)/[engineId]/page.tsx | 8 ---- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts index 9fe1e6b789a..8d161234531 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts @@ -192,7 +192,7 @@ export function useEngineQueueMetrics( } interface GetDeploymentPublicConfigurationInput { - teamId: string; + teamSlug: string; } interface DeploymentPublicConfigurationResponse { @@ -209,7 +209,7 @@ export function useEngineGetDeploymentPublicConfiguration( queryKey: engineKeys.deploymentPublicConfiguration(), queryFn: async () => { const res = await fetch( - `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/public-configuration`, + `${THIRDWEB_API_HOST}/v1/teams/${input.teamSlug}/engine/deployments/public-configuration`, { method: "GET" }, ); if (!res.ok) { @@ -223,7 +223,7 @@ export function useEngineGetDeploymentPublicConfiguration( } interface UpdateDeploymentInput { - teamId: string; + teamSlug: string; deploymentId: string; serverVersion: string; } @@ -232,7 +232,7 @@ export function useEngineUpdateDeployment() { return useMutation({ mutationFn: async (input: UpdateDeploymentInput) => { const res = await fetch( - `${THIRDWEB_API_HOST}/v1/teams/${input.teamId}/engine/deployments/${input.deploymentId}`, + `${THIRDWEB_API_HOST}/v1/teams/${input.teamSlug}/engine/deployments/${input.deploymentId}`, { method: "PUT", headers: { diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx index c603b848f32..a7bd43d2e6c 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/EnginePageLayout.tsx @@ -119,6 +119,7 @@ export function WithEngineInstance(props: { instance={sandboxEngine} content={props.content} rootPath={rootPath} + teamSlug={props.teamSlug} /> ); } @@ -128,6 +129,7 @@ export function WithEngineInstance(props: { content={props.content} engineId={props.engineId} rootPath={rootPath} + teamSlug={props.teamSlug} /> ); } @@ -136,6 +138,7 @@ function QueryAndRenderInstanceHeader(props: { engineId: string; content: React.FC<{ instance: EngineInstance }>; rootPath: string; + teamSlug: string; }) { const instancesQuery = useEngineInstances(); const instance = instancesQuery.data?.find((x) => x.id === props.engineId); @@ -157,6 +160,7 @@ function QueryAndRenderInstanceHeader(props: { instance={instance} content={props.content} rootPath={props.rootPath} + teamSlug={props.teamSlug} /> ); } @@ -165,6 +169,7 @@ function EnsurePermissionAndRenderInstance(props: { content: React.FC<{ instance: EngineInstance }>; instance: EngineInstance; rootPath: string; + teamSlug: string; }) { const permissionQuery = useHasEnginePermission({ instanceUrl: props.instance.url, @@ -210,6 +215,7 @@ function EnsurePermissionAndRenderInstance(props: { rootPath={props.rootPath} instance={props.instance} content={props.content} + teamSlug={props.teamSlug} /> ); } @@ -218,6 +224,7 @@ function RenderEngineInstanceHeader(props: { instance: EngineInstance; content: React.FC<{ instance: EngineInstance }>; rootPath: string; + teamSlug: string; }) { const { instance } = props; @@ -261,7 +268,7 @@ function RenderEngineInstanceHeader(props: { )}
- +
diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx index 0f918314419..641428a5cc5 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/_components/version.tsx @@ -27,38 +27,32 @@ import { useEngineUpdateDeployment, } from "@3rdweb-sdk/react/hooks/useEngine"; import { formatDistanceToNow } from "date-fns"; -import { - CircleArrowDownIcon, - CloudDownloadIcon, - TriangleAlertIcon, -} from "lucide-react"; +import { CircleArrowUpIcon, TriangleAlertIcon } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import invariant from "tiny-invariant"; export const EngineVersionBadge = ({ instance, - teamId, + teamSlug, }: { instance: EngineInstance; - teamId: string; + teamSlug: string; }) => { const healthQuery = useEngineSystemHealth(instance.url); const publicConfigurationQuery = useEngineGetDeploymentPublicConfiguration({ - teamId, + teamSlug, }); const [isModalOpen, setModalOpen] = useState(false); - if ( - !healthQuery.data || - publicConfigurationQuery.data.serverVersions?.length === 0 - ) { + if (!healthQuery.data || !publicConfigurationQuery.data) { return null; } const serverVersions = publicConfigurationQuery.data.serverVersions; + const latestVersion = serverVersions[0]; const currentVersion = healthQuery.data.engineVersion ?? "N/A"; - const hasNewerVersion = serverVersions[0].name !== currentVersion; + const hasNewerVersion = latestVersion?.name !== currentVersion; // Hide the change version modal unless owner. if (!instance.deploymentId) { @@ -75,11 +69,9 @@ export const EngineVersionBadge = ({ label={ hasNewerVersion ? "An update is available" - : "You are on the latest version" - } - leftIcon={ - + : "Engine is on the latest update" } + leftIcon={} > diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx index e6f829a8d56..7b327d21c27 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/overview-page.client.tsx @@ -6,7 +6,6 @@ import { EngineOverview } from "./components/engine-overview"; export function EngineOverviewPage(props: { engineId: string; teamSlug: string; - teamId: string; }) { return ( ( )} - teamId={props.teamId} + teamSlug={props.teamSlug} /> ); } diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx index 3074e2098c8..a55e89332d0 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/page.tsx @@ -1,20 +1,12 @@ -import { getTeamBySlug } from "@/api/team"; -import { redirect } from "next/navigation"; import { EngineOverviewPage } from "./overview/overview-page.client"; import type { EngineInstancePageProps } from "./types"; export default async function Page(props: EngineInstancePageProps) { const params = await props.params; - const team = await getTeamBySlug(params.team_slug); - if (!team) { - redirect("/team"); - } - return ( ); }