Skip to content

Commit a285c12

Browse files
committed
feat: allow configuring multiple Engine wallets
1 parent 7cd39f0 commit a285c12

File tree

17 files changed

+847
-654
lines changed

17 files changed

+847
-654
lines changed

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

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import invariant from "tiny-invariant";
99
import { engineKeys } from "../cache-keys";
1010
import { useLoggedInUser } from "./useLoggedInUser";
1111

12-
export type EngineTier = "STARTER" | "PREMIUM" | "ENTERPRISE";
12+
export type EngineTier = "STARTER" | "PREMIUM";
1313

1414
// Engine instances
1515
export type EngineInstance = {
@@ -188,17 +188,15 @@ export function useEngineLatestVersion() {
188188
}
189189

190190
interface UpdateVersionInput {
191-
engineId: string;
191+
deploymentId: string;
192192
serverVersion: string;
193193
}
194194

195195
export function useEngineUpdateServerVersion() {
196196
return useMutation({
197197
mutationFn: async (input: UpdateVersionInput) => {
198-
invariant(input.engineId, "engineId is required");
199-
200198
const res = await fetch(
201-
`${THIRDWEB_API_HOST}/v2/engine/${input.engineId}/infrastructure`,
199+
`${THIRDWEB_API_HOST}/v2/engine/deployments/${input.deploymentId}/infrastructure`,
202200
{
203201
method: "PUT",
204202
headers: {
@@ -401,29 +399,24 @@ export function useEngineTransactions(instance: string, autoUpdate: boolean) {
401399
});
402400
}
403401

404-
type WalletConfig =
405-
| {
406-
type: "local";
407-
}
408-
| {
409-
type: "aws-kms";
410-
awsAccessKeyId: string;
411-
awsSecretAccessKey: string;
412-
awsRegion: string;
413-
}
414-
| {
415-
type: "gcp-kms";
416-
gcpApplicationProjectId: string;
417-
gcpKmsLocationId: string;
418-
gcpKmsKeyRingId: string;
419-
gcpApplicationCredentialEmail: string;
420-
gcpApplicationCredentialPrivateKey: string;
421-
};
402+
export type EngineBackendWalletType = "local" | "aws-kms" | "gcp-kms";
403+
404+
interface WalletConfigResponse {
405+
type: EngineBackendWalletType;
406+
407+
awsAccessKeyId?: string | null;
408+
awsRegion?: string | null;
409+
410+
gcpApplicationProjectId?: string | null;
411+
gcpKmsLocationId?: string | null;
412+
gcpKmsKeyRingId?: string | null;
413+
gcpApplicationCredentialEmail?: string | null;
414+
}
422415

423416
export function useEngineWalletConfig(instance: string) {
424417
const token = useLoggedInUser().user?.jwt ?? null;
425418

426-
return useQuery({
419+
return useQuery<WalletConfigResponse>({
427420
queryKey: engineKeys.walletConfig(instance),
428421
queryFn: async () => {
429422
const res = await fetch(`${instance}configuration/wallets`, {
@@ -432,8 +425,7 @@ export function useEngineWalletConfig(instance: string) {
432425
});
433426

434427
const json = await res.json();
435-
436-
return (json.result as WalletConfig) || {};
428+
return json.result;
437429
},
438430
enabled: !!instance && !!token,
439431
});
@@ -799,9 +791,6 @@ export function useEngineWebhooks(instance: string) {
799791

800792
// POST REQUESTS
801793
export type SetWalletConfigInput =
802-
| {
803-
type: "local";
804-
}
805794
| {
806795
type: "aws-kms";
807796
awsAccessKeyId: string;
@@ -821,8 +810,8 @@ export function useEngineSetWalletConfig(instance: string) {
821810
const token = useLoggedInUser().user?.jwt ?? null;
822811
const queryClient = useQueryClient();
823812

824-
return useMutation({
825-
mutationFn: async (input: SetWalletConfigInput) => {
813+
return useMutation<WalletConfigResponse, void, SetWalletConfigInput>({
814+
mutationFn: async (input) => {
826815
invariant(instance, "instance is required");
827816

828817
const res = await fetch(`${instance}configuration/wallets`, {
@@ -847,6 +836,7 @@ export function useEngineSetWalletConfig(instance: string) {
847836
}
848837

849838
export type CreateBackendWalletInput = {
839+
type: EngineBackendWalletType;
850840
label?: string;
851841
};
852842

@@ -913,25 +903,20 @@ export function useEngineUpdateBackendWallet(instance: string) {
913903
});
914904
}
915905

916-
export type ImportBackendWalletInput =
917-
| {
918-
awsKmsKeyId: string;
919-
awsKmsArn: string;
920-
}
921-
| {
922-
gcpKmsKeyId: string;
923-
gcpKmsKeyVersionId: string;
924-
}
925-
| {
926-
privateKey?: string;
927-
}
928-
| {
929-
mnemonic?: string;
930-
}
931-
| {
932-
encryptedJson?: string;
933-
password?: string;
934-
};
906+
// The backend determines the wallet imported based on the provided fields.
907+
export type ImportBackendWalletInput = {
908+
label?: string;
909+
910+
awsKmsArn?: string;
911+
912+
gcpKmsKeyId?: string;
913+
gcpKmsKeyVersionId?: string;
914+
915+
privateKey?: string;
916+
mnemonic?: string;
917+
encryptedJson?: string;
918+
password?: string;
919+
};
935920

936921
export function useEngineImportBackendWallet(instance: string) {
937922
const token = useLoggedInUser().user?.jwt ?? null;
@@ -1622,8 +1607,9 @@ export function useEngineNotificationChannels(engineId: string) {
16221607
}
16231608

16241609
export interface CreateNotificationChannelInput {
1610+
deploymentId: string;
16251611
subscriptionRoutes: string[];
1626-
type: "slack" | "email"; // TODO: Add others when implemented.
1612+
type: "slack" | "email" | "webhook";
16271613
value: string;
16281614
}
16291615

@@ -1639,6 +1625,9 @@ export function useEngineCreateNotificationChannel(engineId: string) {
16391625
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/notification-channels`,
16401626
{
16411627
method: "POST",
1628+
headers: {
1629+
"Content-Type": "application/json",
1630+
},
16421631
body: JSON.stringify(input),
16431632
},
16441633
);
@@ -1667,7 +1656,47 @@ export function useEngineDeleteNotificationChannel(engineId: string) {
16671656

16681657
const res = await fetch(
16691658
`${THIRDWEB_API_HOST}/v1/engine/${engineId}/notification-channels/${notificationChannelId}`,
1670-
{ method: "DELETE" },
1659+
{
1660+
method: "DELETE",
1661+
},
1662+
);
1663+
if (!res.ok) {
1664+
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);
1665+
}
1666+
res.body?.cancel();
1667+
},
1668+
onSuccess: () => {
1669+
return queryClient.invalidateQueries({
1670+
queryKey: engineKeys.notificationChannels(engineId),
1671+
});
1672+
},
1673+
});
1674+
}
1675+
1676+
export interface TestNotificationChannelInput {
1677+
notificationChannelId: string;
1678+
status: "firing" | "resolved";
1679+
}
1680+
1681+
export function useEngineTestNotificationChannel(engineId: string) {
1682+
const { isLoggedIn } = useLoggedInUser();
1683+
const queryClient = useQueryClient();
1684+
1685+
return useMutation({
1686+
mutationFn: async (input: TestNotificationChannelInput) => {
1687+
invariant(isLoggedIn, "Must be logged in.");
1688+
1689+
const res = await fetch(
1690+
`${THIRDWEB_API_HOST}/v2/engine/notification-channels/${input.notificationChannelId}/test-trigger`,
1691+
{
1692+
method: "POST",
1693+
headers: {
1694+
"Content-Type": "application/json",
1695+
},
1696+
body: JSON.stringify({
1697+
status: input.status,
1698+
}),
1699+
},
16711700
);
16721701
if (!res.ok) {
16731702
throw new Error(`Unexpected status ${res.status}: ${await res.text()}`);

apps/dashboard/src/app/team/[team_slug]/[project_slug]/engine/(instance)/[engineId]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function Page(props: EngineInstancePageProps) {
88
return (
99
<WithEngineInstance
1010
engineId={props.params.engineId}
11-
content={(res) => <EngineOverview instanceUrl={res.instance.url} />}
11+
content={(res) => <EngineOverview instance={res.instance} />}
1212
rootPath={`/team/${props.params.team_slug}/${props.params.project_slug}/engine`}
1313
/>
1414
);

apps/dashboard/src/components/engine/alerts/EngineAlertDialogForm.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ import {
2929
} from "@/components/ui/select";
3030
import {
3131
type EngineAlertRule,
32+
type EngineInstance,
3233
EngineNotificationChannelTypeConfig,
3334
} from "@3rdweb-sdk/react/hooks/useEngine";
3435
import { zodResolver } from "@hookform/resolvers/zod";
3536
import { useForm } from "react-hook-form";
3637
import { z } from "zod";
3738

3839
const alertFormSchema = z.object({
40+
deploymentId: z.string(),
3941
subscriptionRoutes: z.array(z.string()),
4042
type: z.enum(
4143
Object.keys(EngineNotificationChannelTypeConfig) as [
@@ -48,6 +50,7 @@ const alertFormSchema = z.object({
4850
type EngineAlertFormValues = z.infer<typeof alertFormSchema>;
4951

5052
export function EngineAlertDialogForm(props: {
53+
instance: EngineInstance;
5154
alertRules: EngineAlertRule[];
5255
title: string;
5356
values: EngineAlertFormValues | null;
@@ -62,6 +65,7 @@ export function EngineAlertDialogForm(props: {
6265
const form = useForm<z.infer<typeof alertFormSchema>>({
6366
resolver: zodResolver(alertFormSchema),
6467
defaultValues: {
68+
deploymentId: props.instance.deploymentId,
6569
subscriptionRoutes: ["alert.*"],
6670
type: "slack",
6771
},

apps/dashboard/src/components/engine/alerts/EngineAlertsPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ export function EngineAlertsPage(props: {
1414
return (
1515
<div>
1616
<ManageEngineAlertsSection
17+
instance={props.instance}
1718
alertRules={alertRules}
18-
engineId={props.instance.id}
1919
alertRulesIsLoading={alertRulesQuery.isLoading}
2020
/>
2121
<div className="h-8" />
2222
<RecentEngineAlertsSection
23+
instance={props.instance}
2324
alertRules={alertRules}
24-
engineId={props.instance.id}
2525
alertRulesIsLoading={alertRulesQuery.isLoading}
2626
/>
2727
</div>

apps/dashboard/src/components/engine/alerts/ManageEngineAlerts.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { ToolTipLabel } from "@/components/ui/tooltip";
2222
import {
2323
type CreateNotificationChannelInput,
2424
type EngineAlertRule,
25+
type EngineInstance,
2526
type EngineNotificationChannel,
2627
useEngineCreateNotificationChannel,
2728
useEngineDeleteNotificationChannel,
@@ -43,19 +44,19 @@ type CreateAlertMutation = UseMutationResult<
4344
>;
4445

4546
export function ManageEngineAlertsSection(props: {
47+
instance: EngineInstance;
4648
alertRules: EngineAlertRule[];
4749
alertRulesIsLoading: boolean;
48-
engineId: string;
4950
}) {
5051
const notificationChannelsQuery = useEngineNotificationChannels(
51-
props.engineId,
52+
props.instance.id,
5253
);
5354
const deleteAlertMutation = useEngineDeleteNotificationChannel(
54-
props.engineId,
55+
props.instance.id,
5556
);
5657

5758
const createAlertMutation = useEngineCreateNotificationChannel(
58-
props.engineId,
59+
props.instance.id,
5960
);
6061

6162
// not passing the mutation to avoid multiple rows sharing the same mutation state, we create the new mutation for each row instead in each component instead
@@ -65,8 +66,8 @@ export function ManageEngineAlertsSection(props: {
6566

6667
return (
6768
<ManageEngineAlertsSectionUI
69+
instance={props.instance}
6870
alertRules={props.alertRules}
69-
engineId={props.engineId}
7071
notificationChannels={notificationChannelsQuery.data ?? []}
7172
isLoading={
7273
notificationChannelsQuery.isLoading || props.alertRulesIsLoading
@@ -81,16 +82,17 @@ export function ManageEngineAlertsSection(props: {
8182
}
8283

8384
export function ManageEngineAlertsSectionUI(props: {
85+
instance: EngineInstance;
8486
alertRules: EngineAlertRule[];
85-
engineId: string;
8687
notificationChannels: EngineNotificationChannel[];
8788
isLoading: boolean;
8889
onAlertsUpdated: () => void;
8990
createAlertMutation: CreateAlertMutation;
9091
deleteAlert: (notificationChannelId: string) => Promise<void>;
9192
}) {
9293
const {
93-
engineId,
94+
instance,
95+
alertRules,
9496
notificationChannels,
9597
isLoading,
9698
onAlertsUpdated,
@@ -111,8 +113,8 @@ export function ManageEngineAlertsSectionUI(props: {
111113
</div>
112114

113115
<CreateAlertButton
114-
engineId={engineId}
115-
alertRules={props.alertRules}
116+
instance={instance}
117+
alertRules={alertRules}
116118
onSuccess={onAlertsUpdated}
117119
createAlertMutation={createAlertMutation}
118120
/>
@@ -222,7 +224,7 @@ function EngineAlertsTableUI(props: {
222224
}
223225

224226
function CreateAlertButton(props: {
225-
engineId: string;
227+
instance: EngineInstance;
226228
alertRules: EngineAlertRule[];
227229
onSuccess: () => void;
228230
createAlertMutation: CreateAlertMutation;
@@ -252,6 +254,7 @@ function CreateAlertButton(props: {
252254
</ToolTipLabel>
253255

254256
<EngineAlertDialogForm
257+
instance={props.instance}
255258
alertRules={props.alertRules}
256259
open={isModalOpen}
257260
onOpenChange={setIsModalOpen}

apps/dashboard/src/components/engine/alerts/RecentEngineAlerts.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@ import {
1414
import {
1515
type EngineAlert,
1616
type EngineAlertRule,
17+
type EngineInstance,
1718
useEngineAlerts,
1819
} from "@3rdweb-sdk/react/hooks/useEngine";
1920
import { formatDistance } from "date-fns";
2021
import { useMemo } from "react";
2122

2223
export function RecentEngineAlertsSection(props: {
24+
instance: EngineInstance;
2325
alertRules: EngineAlertRule[];
2426
alertRulesIsLoading: boolean;
25-
engineId: string;
2627
}) {
2728
// TODO - pagination
2829
// required : return the total number of alerts in response from API
29-
const alertsQuery = useEngineAlerts(props.engineId, 100, 0);
30+
const alertsQuery = useEngineAlerts(props.instance.id, 100, 0);
3031
const alerts = alertsQuery.data ?? [];
3132

3233
return (

0 commit comments

Comments
 (0)