diff --git a/ui/app/(prowler)/integrations/page.tsx b/ui/app/(prowler)/integrations/page.tsx index 7270334756..265540c2be 100644 --- a/ui/app/(prowler)/integrations/page.tsx +++ b/ui/app/(prowler)/integrations/page.tsx @@ -1,12 +1,9 @@ import React from "react"; -import { getIntegrations } from "@/actions/integrations"; import { S3IntegrationCard } from "@/components/integrations"; import { ContentLayout } from "@/components/ui"; export default async function Integrations() { - const integrations = await getIntegrations(); - return (
@@ -19,7 +16,7 @@ export default async function Integrations() {
{/* Amazon S3 Integration */} - +
diff --git a/ui/app/(prowler)/integrations/s3/page.tsx b/ui/app/(prowler)/integrations/s3/page.tsx index 9243130d17..7193f33f9a 100644 --- a/ui/app/(prowler)/integrations/s3/page.tsx +++ b/ui/app/(prowler)/integrations/s3/page.tsx @@ -5,16 +5,47 @@ import { getProviders } from "@/actions/providers"; import { S3IntegrationsManager } from "@/components/integrations/s3/s3-integrations-manager"; import { ContentLayout } from "@/components/ui"; -export default async function S3Integrations() { +interface S3IntegrationsProps { + searchParams: { [key: string]: string | string[] | undefined }; +} + +export default async function S3Integrations({ + searchParams, +}: S3IntegrationsProps) { + const page = parseInt(searchParams.page?.toString() || "1", 10); + const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); + const sort = searchParams.sort?.toString(); + + // Extract all filter parameters + const filters = Object.fromEntries( + Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + ); + + const urlSearchParams = new URLSearchParams(); + urlSearchParams.set("filter[integration_type]", "amazon_s3"); + urlSearchParams.set("page[number]", page.toString()); + urlSearchParams.set("page[size]", pageSize.toString()); + + if (sort) { + urlSearchParams.set("sort", sort); + } + + // Add any additional filters + Object.entries(filters).forEach(([key, value]) => { + if (value !== undefined && key !== "filter[integration_type]") { + const stringValue = Array.isArray(value) ? value[0] : String(value); + urlSearchParams.set(key, stringValue); + } + }); + const [integrations, providers] = await Promise.all([ - getIntegrations( - new URLSearchParams({ "filter[integration_type]": "amazon_s3" }), - ), + getIntegrations(urlSearchParams), getProviders({ pageSize: 100 }), ]); const s3Integrations = integrations?.data || []; const availableProviders = providers?.data || []; + const metadata = integrations?.meta; return ( @@ -53,6 +84,7 @@ export default async function S3Integrations() { diff --git a/ui/components/integrations/s3/s3-integration-card.tsx b/ui/components/integrations/s3/s3-integration-card.tsx index f0833b246f..7f1b659c68 100644 --- a/ui/components/integrations/s3/s3-integration-card.tsx +++ b/ui/components/integrations/s3/s3-integration-card.tsx @@ -1,42 +1,13 @@ "use client"; -import { Card, CardBody, CardHeader, Chip } from "@nextui-org/react"; +import { Card, CardBody, CardHeader } from "@nextui-org/react"; import { SettingsIcon } from "lucide-react"; import { AmazonS3Icon } from "@/components/icons/services/IconServices"; import { CustomButton } from "@/components/ui/custom"; import { CustomLink } from "@/components/ui/custom/custom-link"; -import { IntegrationProps } from "@/types/integrations"; - -import { S3IntegrationCardSkeleton } from "./skeleton-s3-integration-card"; - -interface S3IntegrationCardProps { - integrations?: IntegrationProps[]; - isLoading?: boolean; -} - -export const S3IntegrationCard = ({ - integrations = [], - isLoading = false, -}: S3IntegrationCardProps) => { - const s3Integrations = integrations.filter( - (integration) => integration.attributes.integration_type === "amazon_s3", - ); - - const isConfigured = s3Integrations.length > 0; - const connectedCount = s3Integrations.filter( - (integration) => integration.attributes.connected, - ).length; - - if (isLoading) { - return ( - - ); - } +export const S3IntegrationCard = () => { return ( @@ -62,84 +33,24 @@ export const S3IntegrationCard = ({
- {isConfigured && ( - 0 ? "success" : "warning"} - variant="flat" - > - {connectedCount} / {s3Integrations.length} connected - - )} } asLink="/integrations/s3" - ariaLabel={ - isConfigured - ? "Manage S3 integrations" - : "Configure S3 integration" - } + ariaLabel="Manage S3 integrations" > - {isConfigured ? "Manage" : "Configure"} + Manage
- {isConfigured ? ( - <> -
- {s3Integrations.map((integration) => ( -
-
- - {integration.attributes.configuration.bucket_name || - "Unknown Bucket"} - - - Output directory:{" "} - {integration.attributes.configuration - .output_directory || - integration.attributes.configuration.path || - "/"} - -
- - {integration.attributes.connected - ? "Connected" - : "Disconnected"} - -
- ))} -
- - ) : ( - <> -
- Status: - Not configured -
- -
-

- Export your security findings to Amazon S3 buckets - automatically. -

-
- - )} +

+ Configure and manage your Amazon S3 integrations to automatically + export security findings to your S3 buckets. +

diff --git a/ui/components/integrations/s3/s3-integration-form.tsx b/ui/components/integrations/s3/s3-integration-form.tsx index 0488f7896a..3c5e95212b 100644 --- a/ui/components/integrations/s3/s3-integration-form.tsx +++ b/ui/components/integrations/s3/s3-integration-form.tsx @@ -49,7 +49,6 @@ export const S3IntegrationForm = ({ const isEditingConfig = editMode === "configuration"; const isEditingCredentials = editMode === "credentials"; - // Create the form with updated schema and default values const form = useForm({ resolver: zodResolver( // For credentials editing, use creation schema (all fields required) @@ -141,13 +140,30 @@ export const S3IntegrationForm = ({ return credentials; }; - const buildConfiguration = (values: any) => { + const buildConfiguration = (values: any, isPartial = false) => { const configuration: any = {}; - // Always include all fields for both creation and edit modes - // Backend expects complete configuration object - configuration.bucket_name = values.bucket_name; - configuration.output_directory = values.output_directory || "output"; + // For creation mode, include all fields + if (!isPartial) { + configuration.bucket_name = values.bucket_name; + configuration.output_directory = values.output_directory || "output"; + } else { + // For edit mode, bucket_name and output_directory are treated as a pair + const originalBucketName = + integration?.attributes.configuration.bucket_name || ""; + const originalOutputDirectory = + integration?.attributes.configuration.output_directory || ""; + + const bucketNameChanged = values.bucket_name !== originalBucketName; + const outputDirectoryChanged = + values.output_directory !== originalOutputDirectory; + + // If either field changed, send both (as a pair) + if (bucketNameChanged || outputDirectoryChanged) { + configuration.bucket_name = values.bucket_name; + configuration.output_directory = values.output_directory || "output"; + } + } return configuration; }; @@ -158,8 +174,10 @@ export const S3IntegrationForm = ({ formData.append("integration_type", values.integration_type); if (isEditingConfig) { - const configuration = buildConfiguration(values); - formData.append("configuration", JSON.stringify(configuration)); + const configuration = buildConfiguration(values, true); + if (Object.keys(configuration).length > 0) { + formData.append("configuration", JSON.stringify(configuration)); + } // Always send providers array, even if empty, to update relationships formData.append("providers", JSON.stringify(values.providers || [])); } else if (isEditingCredentials) { diff --git a/ui/components/integrations/s3/s3-integrations-manager.tsx b/ui/components/integrations/s3/s3-integrations-manager.tsx index e1e9c49641..3b4f417414 100644 --- a/ui/components/integrations/s3/s3-integrations-manager.tsx +++ b/ui/components/integrations/s3/s3-integrations-manager.tsx @@ -19,6 +19,8 @@ import { import { AmazonS3Icon } from "@/components/icons/services/IconServices"; import { useToast } from "@/components/ui"; import { CustomAlertModal, CustomButton } from "@/components/ui/custom"; +import { DataTablePagination } from "@/components/ui/table/data-table-pagination"; +import { MetaDataProps } from "@/types"; import { IntegrationProps } from "@/types/integrations"; import { ProviderProps } from "@/types/providers"; @@ -28,11 +30,13 @@ import { S3IntegrationCardSkeleton } from "./skeleton-s3-integration-card"; interface S3IntegrationsManagerProps { integrations: IntegrationProps[]; providers: ProviderProps[]; + metadata?: MetaDataProps; } export const S3IntegrationsManager = ({ integrations, providers, + metadata, }: S3IntegrationsManagerProps) => { const [isModalOpen, setIsModalOpen] = useState(false); const [editingIntegration, setEditingIntegration] = @@ -392,6 +396,12 @@ export const S3IntegrationsManager = ({ ))} ) : null} + + {metadata && integrations.length > 0 && ( +
+ +
+ )} );