feat(ui): S3 integrations pagination added (#8450)

This commit is contained in:
Alejandro Bailo
2025-08-05 18:11:32 +02:00
committed by GitHub
parent a9d16bbbce
commit c3d25e6f39
5 changed files with 81 additions and 113 deletions

View File

@@ -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 (
<S3IntegrationCardSkeleton
variant="main"
count={s3Integrations.length || 1}
/>
);
}
export const S3IntegrationCard = () => {
return (
<Card className="dark:bg-gray-800">
<CardHeader className="gap-2">
@@ -62,84 +33,24 @@ export const S3IntegrationCard = ({
</div>
</div>
<div className="flex items-center gap-2 self-end sm:self-center">
{isConfigured && (
<Chip
size="sm"
color={connectedCount > 0 ? "success" : "warning"}
variant="flat"
>
{connectedCount} / {s3Integrations.length} connected
</Chip>
)}
<CustomButton
size="sm"
variant="bordered"
startContent={<SettingsIcon size={14} />}
asLink="/integrations/s3"
ariaLabel={
isConfigured
? "Manage S3 integrations"
: "Configure S3 integration"
}
ariaLabel="Manage S3 integrations"
>
{isConfigured ? "Manage" : "Configure"}
Manage
</CustomButton>
</div>
</div>
</CardHeader>
<CardBody>
<div className="flex flex-col gap-4">
{isConfigured ? (
<>
<div className="space-y-2">
{s3Integrations.map((integration) => (
<div
key={integration.id}
className="flex items-center justify-between rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800"
>
<div className="flex flex-col">
<span className="text-sm font-medium">
{integration.attributes.configuration.bucket_name ||
"Unknown Bucket"}
</span>
<span className="text-xs text-gray-500 dark:text-gray-300">
Output directory:{" "}
{integration.attributes.configuration
.output_directory ||
integration.attributes.configuration.path ||
"/"}
</span>
</div>
<Chip
size="sm"
color={
integration.attributes.connected ? "success" : "danger"
}
variant="dot"
>
{integration.attributes.connected
? "Connected"
: "Disconnected"}
</Chip>
</div>
))}
</div>
</>
) : (
<>
<div className="text-sm">
<span className="font-medium">Status: </span>
<span className="text-gray-500">Not configured</span>
</div>
<div className="space-y-3">
<p className="text-sm text-gray-600 dark:text-gray-300">
Export your security findings to Amazon S3 buckets
automatically.
</p>
</div>
</>
)}
<p className="text-sm text-gray-600 dark:text-gray-300">
Configure and manage your Amazon S3 integrations to automatically
export security findings to your S3 buckets.
</p>
</div>
</CardBody>
</Card>

View File

@@ -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) {

View File

@@ -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 = ({
))}
</div>
) : null}
{metadata && integrations.length > 0 && (
<div className="mt-6">
<DataTablePagination metadata={metadata} />
</div>
)}
</div>
</>
);