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 && (
+
+
+
+ )}
>
);