Skip to content

Commit 52723ed

Browse files
authored
feat(providers): refactor workflow providers v2 (#6001)
1 parent 4a46365 commit 52723ed

File tree

17 files changed

+754
-101
lines changed

17 files changed

+754
-101
lines changed

ui/actions/providers/providers.ts

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,102 @@ export const addCredentialsProvider = async (formData: FormData) => {
253253
}
254254
};
255255

256+
export const updateCredentialsProvider = async (
257+
credentialsId: string,
258+
formData: FormData,
259+
) => {
260+
const session = await auth();
261+
const keyServer = process.env.API_BASE_URL;
262+
const url = new URL(`${keyServer}/providers/secrets/${credentialsId}`);
263+
264+
const secretName = formData.get("secretName");
265+
const providerType = formData.get("providerType");
266+
267+
const isRole = formData.get("role_arn") !== null;
268+
269+
let secret = {};
270+
271+
if (providerType === "aws") {
272+
if (isRole) {
273+
// Role-based configuration for AWS
274+
secret = {
275+
role_arn: formData.get("role_arn"),
276+
aws_access_key_id: formData.get("aws_access_key_id") || undefined,
277+
aws_secret_access_key:
278+
formData.get("aws_secret_access_key") || undefined,
279+
aws_session_token: formData.get("aws_session_token") || undefined,
280+
session_duration:
281+
parseInt(formData.get("session_duration") as string, 10) || 3600,
282+
external_id: formData.get("external_id") || undefined,
283+
role_session_name: formData.get("role_session_name") || undefined,
284+
};
285+
} else {
286+
// Static credentials configuration for AWS
287+
secret = {
288+
aws_access_key_id: formData.get("aws_access_key_id"),
289+
aws_secret_access_key: formData.get("aws_secret_access_key"),
290+
aws_session_token: formData.get("aws_session_token") || undefined,
291+
};
292+
}
293+
} else if (providerType === "azure") {
294+
// Static credentials configuration for Azure
295+
secret = {
296+
client_id: formData.get("client_id"),
297+
client_secret: formData.get("client_secret"),
298+
tenant_id: formData.get("tenant_id"),
299+
};
300+
} else if (providerType === "gcp") {
301+
// Static credentials configuration for GCP
302+
secret = {
303+
client_id: formData.get("client_id"),
304+
client_secret: formData.get("client_secret"),
305+
refresh_token: formData.get("refresh_token"),
306+
};
307+
} else if (providerType === "kubernetes") {
308+
// Static credentials configuration for Kubernetes
309+
secret = {
310+
kubeconfig_content: formData.get("kubeconfig_content"),
311+
};
312+
}
313+
314+
const bodyData = {
315+
data: {
316+
type: "provider-secrets",
317+
id: credentialsId,
318+
attributes: {
319+
name: secretName,
320+
secret,
321+
},
322+
},
323+
};
324+
325+
try {
326+
const response = await fetch(url.toString(), {
327+
method: "PATCH",
328+
headers: {
329+
"Content-Type": "application/vnd.api+json",
330+
Accept: "application/vnd.api+json",
331+
Authorization: `Bearer ${session?.accessToken}`,
332+
},
333+
body: JSON.stringify(bodyData),
334+
});
335+
336+
if (!response.ok) {
337+
throw new Error(`Failed to update credentials: ${response.statusText}`);
338+
}
339+
340+
const data = await response.json();
341+
revalidatePath("/providers");
342+
return parseStringify(data);
343+
} catch (error) {
344+
// eslint-disable-next-line no-console
345+
console.error(error);
346+
return {
347+
error: getErrorMessage(error),
348+
};
349+
}
350+
};
351+
256352
export const checkConnectionProvider = async (formData: FormData) => {
257353
const session = await auth();
258354
const keyServer = process.env.API_BASE_URL;
@@ -270,7 +366,7 @@ export const checkConnectionProvider = async (formData: FormData) => {
270366
},
271367
});
272368
const data = await response.json();
273-
await wait(1000);
369+
await wait(2000);
274370
revalidatePath("/providers");
275371
return parseStringify(data);
276372
} catch (error) {
Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,30 @@
1-
import { redirect } from "next/navigation";
21
import React from "react";
32

43
import {
54
ViaCredentialsForm,
65
ViaRoleForm,
76
} from "@/components/providers/workflow/forms";
7+
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-via-aws/select-via-aws";
88

99
interface Props {
1010
searchParams: { type: string; id: string; via?: string };
1111
}
1212

1313
export default function AddCredentialsPage({ searchParams }: Props) {
14-
if (
15-
!searchParams.type ||
16-
!searchParams.id ||
17-
(searchParams.type === "aws" && !searchParams.via)
18-
) {
19-
redirect("/providers/connect-account");
20-
}
21-
22-
const useCredentialsForm =
23-
(searchParams.type === "aws" && searchParams.via === "credentials") ||
24-
(searchParams.type !== "aws" && !searchParams.via);
25-
26-
const useRoleForm =
27-
searchParams.type === "aws" && searchParams.via === "role";
28-
2914
return (
3015
<>
31-
{useCredentialsForm && <ViaCredentialsForm searchParams={searchParams} />}
32-
{useRoleForm && <ViaRoleForm searchParams={searchParams} />}
16+
{searchParams.type === "aws" && !searchParams.via && (
17+
<SelectViaAWS initialVia={searchParams.via} />
18+
)}
19+
20+
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
21+
searchParams.type !== "aws") && (
22+
<ViaCredentialsForm searchParams={searchParams} />
23+
)}
24+
25+
{searchParams.type === "aws" && searchParams.via === "role" && (
26+
<ViaRoleForm searchParams={searchParams} />
27+
)}
3328
</>
3429
);
3530
}

ui/app/(prowler)/providers/(set-up-provider)/test-connection/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getProvider } from "@/actions/providers";
55
import { TestConnectionForm } from "@/components/providers/workflow/forms";
66

77
interface Props {
8-
searchParams: { type: string; id: string };
8+
searchParams: { type: string; id: string; updated: string };
99
}
1010

1111
export default async function TestConnectionPage({ searchParams }: Props) {
@@ -25,7 +25,7 @@ export default async function TestConnectionPage({ searchParams }: Props) {
2525
async function SSRTestConnection({
2626
searchParams,
2727
}: {
28-
searchParams: { type: string; id: string };
28+
searchParams: { type: string; id: string; updated: string };
2929
}) {
3030
const formData = new FormData();
3131
formData.append("id", searchParams.id);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from "react";
2+
3+
import {
4+
UpdateViaCredentialsForm,
5+
UpdateViaRoleForm,
6+
} from "@/components/providers/workflow/forms";
7+
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-via-aws/select-via-aws";
8+
9+
interface Props {
10+
searchParams: { type: string; id: string; via?: string };
11+
}
12+
13+
export default function UpdateCredentialsPage({ searchParams }: Props) {
14+
return (
15+
<>
16+
{searchParams.type === "aws" && !searchParams.via && (
17+
<SelectViaAWS initialVia={searchParams.via} />
18+
)}
19+
20+
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
21+
searchParams.type !== "aws") && (
22+
<UpdateViaCredentialsForm searchParams={searchParams} />
23+
)}
24+
25+
{searchParams.type === "aws" && searchParams.via === "role" && (
26+
<UpdateViaRoleForm searchParams={searchParams} />
27+
)}
28+
</>
29+
);
30+
}

ui/components/providers/table/data-table-row-actions.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Row } from "@tanstack/react-table";
1717
import clsx from "clsx";
1818
import { useState } from "react";
1919

20+
import { checkConnectionProvider } from "@/actions/providers/providers";
2021
import { VerticalDotsIcon } from "@/components/icons";
2122
import { CustomAlertModal } from "@/components/ui/custom";
2223

@@ -37,6 +38,15 @@ export function DataTableRowActions<ProviderProps>({
3738
const providerId = (row.original as { id: string }).id;
3839
const providerType = (row.original as any).attributes?.provider;
3940
const providerAlias = (row.original as any).attributes?.alias;
41+
const providerSecretId =
42+
(row.original as any).relationships?.secret?.data?.id || null;
43+
44+
const handleTestConnection = async () => {
45+
const formData = new FormData();
46+
formData.append("providerId", providerId);
47+
await checkConnectionProvider(formData);
48+
};
49+
4050
return (
4151
<>
4252
<CustomAlertModal
@@ -77,11 +87,20 @@ export function DataTableRowActions<ProviderProps>({
7787
>
7888
<DropdownSection title="Actions">
7989
<DropdownItem
80-
href={`/providers/test-connection?type=${providerType}&id=${providerId}`}
90+
href={`/providers/update-credentials?type=${providerType}&id=${providerId}${providerSecretId ? `&secretId=${providerSecretId}` : ""}`}
91+
key="update"
92+
description="Update the provider credentials"
93+
textValue="Update Credentials"
94+
startContent={<EditDocumentBulkIcon className={iconClasses} />}
95+
>
96+
Update Credentials
97+
</DropdownItem>
98+
<DropdownItem
8199
key="new"
82100
description="Check the connection to the provider"
83101
textValue="Check Connection"
84102
startContent={<AddNoteBulkIcon className={iconClasses} />}
103+
onClick={handleTestConnection}
85104
>
86105
Test Connection
87106
</DropdownItem>

ui/components/providers/workflow/forms/connect-account-form.tsx

Lines changed: 53 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { Form } from "@/components/ui/form";
1919
import { addProvider } from "../../../../actions/providers/providers";
2020
import { addProviderFormSchema, ApiError } from "../../../../types";
2121
import { RadioGroupProvider } from "../../radio-group-provider";
22-
import { RadioGroupAWSViaCredentialsForm } from "./radio-group-aws-via-credentials-form";
2322

2423
export type FormValues = z.infer<typeof addProviderFormSchema>;
2524

@@ -36,7 +35,6 @@ export const ConnectAccountForm = () => {
3635
providerType: undefined,
3736
providerUid: "",
3837
providerAlias: "",
39-
awsCredentialsType: "",
4038
},
4139
});
4240

@@ -51,55 +49,60 @@ export const ConnectAccountForm = () => {
5149
([key, value]) => value !== undefined && formData.append(key, value),
5250
);
5351

54-
const data = await addProvider(formData);
55-
56-
if (data?.errors && data.errors.length > 0) {
57-
// Handle server-side validation errors
58-
data.errors.forEach((error: ApiError) => {
59-
const errorMessage = error.detail;
60-
const pointer = error.source?.pointer;
61-
62-
switch (pointer) {
63-
case "/data/attributes/provider":
64-
form.setError("providerType", {
65-
type: "server",
66-
message: errorMessage,
67-
});
68-
break;
69-
case "/data/attributes/uid":
70-
case "/data/attributes/__all__":
71-
form.setError("providerUid", {
72-
type: "server",
73-
message: errorMessage,
74-
});
75-
break;
76-
case "/data/attributes/alias":
77-
form.setError("providerAlias", {
78-
type: "server",
79-
message: errorMessage,
80-
});
81-
break;
82-
default:
83-
toast({
84-
variant: "destructive",
85-
title: "Oops! Something went wrong",
86-
description: errorMessage,
87-
});
88-
}
52+
try {
53+
const data = await addProvider(formData);
54+
55+
if (data?.errors && data.errors.length > 0) {
56+
// Handle server-side validation errors
57+
data.errors.forEach((error: ApiError) => {
58+
const errorMessage = error.detail;
59+
const pointer = error.source?.pointer;
60+
61+
switch (pointer) {
62+
case "/data/attributes/provider":
63+
form.setError("providerType", {
64+
type: "server",
65+
message: errorMessage,
66+
});
67+
break;
68+
case "/data/attributes/uid":
69+
case "/data/attributes/__all__":
70+
form.setError("providerUid", {
71+
type: "server",
72+
message: errorMessage,
73+
});
74+
break;
75+
case "/data/attributes/alias":
76+
form.setError("providerAlias", {
77+
type: "server",
78+
message: errorMessage,
79+
});
80+
break;
81+
default:
82+
toast({
83+
variant: "destructive",
84+
title: "Oops! Something went wrong",
85+
description: errorMessage,
86+
});
87+
}
88+
});
89+
return;
90+
} else {
91+
// Go to the next step after successful submission
92+
const {
93+
id,
94+
attributes: { provider: providerType },
95+
} = data.data;
96+
97+
router.push(`/providers/add-credentials?type=${providerType}&id=${id}`);
98+
}
99+
} catch (error: any) {
100+
console.error("Error during submission:", error);
101+
toast({
102+
variant: "destructive",
103+
title: "Submission Error",
104+
description: error.message || "Something went wrong. Please try again.",
89105
});
90-
return;
91-
} else {
92-
// Navigate to the next step after successful submission
93-
const {
94-
id,
95-
attributes: { provider: providerType },
96-
} = data.data;
97-
const credentialsParam = values.awsCredentialsType
98-
? `&via=${values.awsCredentialsType}`
99-
: "";
100-
router.push(
101-
`/providers/add-credentials?type=${providerType}&id=${id}${credentialsParam}`,
102-
);
103106
}
104107
};
105108

@@ -158,13 +161,6 @@ export const ConnectAccountForm = () => {
158161
isRequired={false}
159162
isInvalid={!!form.formState.errors.providerAlias}
160163
/>
161-
{providerType === "aws" && (
162-
<RadioGroupAWSViaCredentialsForm
163-
control={form.control}
164-
isInvalid={!!form.formState.errors.awsCredentialsType}
165-
errorMessage={form.formState.errors.awsCredentialsType?.message}
166-
/>
167-
)}
168164
</>
169165
)}
170166
{/* Navigation buttons */}

0 commit comments

Comments
 (0)