diff --git a/actions/services.ts b/actions/services.ts deleted file mode 100644 index 2f9518291f..0000000000 --- a/actions/services.ts +++ /dev/null @@ -1,39 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { redirect } from "next/navigation"; - -import { parseStringify } from "@/lib"; - -export const getService = async ({ page = 1 }) => { - if (isNaN(Number(page)) || page < 1) redirect("/services"); - const keyServer = process.env.SITE_URL; - - try { - const services = await fetch( - `${keyServer}/api/services?page%5Bnumber%5D=${page}`, - ); - const data = await services.json(); - const parsedData = parseStringify(data); - revalidatePath("/services"); - return parsedData; - } catch (error) { - console.error("Error fetching services:", error); - return undefined; - } -}; - -export const getErrorMessage = (error: unknown): string => { - let message: string; - - if (error instanceof Error) { - message = error.message; - } else if (error && typeof error === "object" && "message" in error) { - message = String(error.message); - } else if (typeof error === "string") { - message = error; - } else { - message = "Oops! Something went wrong."; - } - return message; -}; diff --git a/actions/services/index.ts b/actions/services/index.ts new file mode 100644 index 0000000000..b2221a94a8 --- /dev/null +++ b/actions/services/index.ts @@ -0,0 +1 @@ +export * from "./services"; diff --git a/actions/services/services.ts b/actions/services/services.ts new file mode 100644 index 0000000000..dab8b80bb9 --- /dev/null +++ b/actions/services/services.ts @@ -0,0 +1,101 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { auth } from "@/auth.config"; +import { getErrorMessage, parseStringify } from "@/lib"; +import { parse } from "path"; + +export const getServices = async ({}) => { + const session = await auth(); + + const keyServer = process.env.API_BASE_URL; + const servicesToFetch = [ + { id: "accessanalyzer", alias: "IAM Access Analyzer" }, + { id: "account", alias: "AWS Account" }, + { id: "acm", alias: "AWS Certificate Manager" }, + { id: "apigateway", alias: "Amazon API Gateway" }, + { id: "apigatewayv2", alias: "Amazon API Gateway V2" }, + { id: "athena", alias: "Amazon Athena" }, + { id: "autoscaling", alias: "Amazon EC2 Auto Scaling" }, + { id: "awslambda", alias: "AWS Lambda" }, + { id: "backup", alias: "AWS Backup" }, + { id: "cloudformation", alias: "AWS CloudFormation" }, + { id: "cloudfront", alias: "Amazon CloudFront" }, + { id: "cloudtrail", alias: "AWS CloudTrail" }, + { id: "cloudwatch", alias: "Amazon CloudWatch" }, + { id: "codeartifact", alias: "AWS CodeArtifact" }, + { id: "codebuild", alias: "AWS CodeBuild" }, + { id: "config", alias: "AWS Config" }, + { id: "dlm", alias: "Amazon Data Lifecycle Manager" }, + { id: "drs", alias: "AWS Data Replication Service" }, + { id: "dynamodb", alias: "Amazon DynamoDB" }, + { id: "ec2", alias: "Amazon EC2" }, + { id: "ecr", alias: "Amazon ECR" }, + { id: "ecs", alias: "Amazon ECS" }, + { id: "efs", alias: "Amazon EFS" }, + { id: "eks", alias: "Amazon EKS" }, + { id: "elasticache", alias: "Amazon ElastiCache" }, + { id: "elb", alias: "Elastic Load Balancing" }, + { id: "elbv2", alias: "Elastic Load Balancing v2" }, + { id: "emr", alias: "Amazon EMR" }, + { id: "fms", alias: "AWS Firewall Manager" }, + { id: "glacier", alias: "Amazon Glacier" }, + { id: "glue", alias: "AWS Glue" }, + { id: "guardduty", alias: "Amazon GuardDuty" }, + { id: "iam", alias: "AWS IAM" }, + { id: "inspector2", alias: "Amazon Inspector" }, + { id: "kms", alias: "AWS KMS" }, + { id: "macie", alias: "Amazon Macie" }, + { id: "networkfirewall", alias: "AWS Network Firewall" }, + { id: "organizations", alias: "AWS Organizations" }, + { id: "rds", alias: "Amazon RDS" }, + { id: "resourceexplorer2", alias: "AWS Resource Groups" }, + { id: "route53", alias: "Amazon Route 53" }, + { id: "s3", alias: "Amazon S3" }, + { id: "secretsmanager", alias: "AWS Secrets Manager" }, + { id: "securityhub", alias: "AWS Security Hub" }, + { id: "sns", alias: "Amazon SNS" }, + { id: "sqs", alias: "Amazon SQS" }, + { id: "ssm", alias: "AWS Systems Manager" }, + { id: "ssmincidents", alias: "AWS Systems Manager Incident Manager" }, + { id: "trustedadvisor", alias: "AWS Trusted Advisor" }, + { id: "vpc", alias: "Amazon VPC" }, + { id: "wafv2", alias: "AWS WAF" }, + { id: "wellarchitected", alias: "AWS Well-Architected Tool" } + ]; + + + + const parsedData = []; + + for (const service of servicesToFetch) { + const url = new URL(`${keyServer}/findings`); + url.searchParams.append("filter[service]", service.id); + url.searchParams.append("filter[status]", "FAIL"); + + try { + const response = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + + const data = await response.json(); + const failFindings = data.meta.pagination.count; + + parsedData.push({ + service_id: service.id, + service_alias: service.alias, + fail_findings: failFindings, + }); + } catch (error) { + console.error(`Error fetching data for service ${service.id}:`, error); + } + } + + revalidatePath("/services"); + return parsedData; +}; diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index 24fef60b25..1772165913 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -1,8 +1,7 @@ import { Spacer } from "@nextui-org/react"; -import { redirect } from "next/navigation"; import { Suspense } from "react"; -import { getService } from "@/actions/services"; +import { getServices } from "@/actions/services"; import { FilterControls } from "@/components/filters"; import { ServiceCard, ServiceSkeletonGrid } from "@/components/services"; import { Header } from "@/components/ui"; @@ -35,19 +34,16 @@ const SSRServiceGrid = async ({ }: { searchParams: SearchParamsProps; }) => { - const page = parseInt(searchParams.page?.toString() || "1", 10); - const servicesData = await getService({ page }); + const servicesData = await getServices(searchParams); const [services] = await Promise.all([servicesData]); - if (services?.errors) redirect("/services"); - return (
- {services.services?.data.map((service: any) => ( + {services?.map((service: any) => ( ))}
diff --git a/components/findings/table/column-findings.tsx b/components/findings/table/column-findings.tsx index 9483ee9b86..bb9e95cd9c 100644 --- a/components/findings/table/column-findings.tsx +++ b/components/findings/table/column-findings.tsx @@ -4,8 +4,10 @@ import { ColumnDef } from "@tanstack/react-table"; import { SeverityBadge, Status, StatusBadge } from "@/components/ui/table"; import { FindingProps } from "@/types"; - +import { TriggerSheet } from "@/components/ui/sheet"; import { DataTableRowActions } from "./data-table-row-actions"; +import { PlusIcon } from "@/components/icons"; +import { DataTableRowDetails } from "@/components/findings/table"; const statusMap: Record<"PASS" | "FAIL" | "MANUAL" | "MUTED", Status> = { PASS: "completed", @@ -133,6 +135,21 @@ export const ColumnFindings: ColumnDef[] = [ ); }, }, + { + id: "moreInfo", + header: "Details", + cell: ({ row }) => { + return ( + } + title="Scan Details" + description="View the scan details" + > + + + ); + }, + }, { id: "actions", cell: ({ row }) => { diff --git a/components/findings/table/data-table-row-details.tsx b/components/findings/table/data-table-row-details.tsx new file mode 100644 index 0000000000..997fa0e5d9 --- /dev/null +++ b/components/findings/table/data-table-row-details.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { getScan } from "@/actions/scans"; +import { ScanDetail, SkeletonTableScans } from "@/components/scans/table"; +import { ScanProps } from "@/types"; + +export const DataTableRowDetails = ({ entityId }: { entityId: string }) => { + const [scanDetails, setScanDetails] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchScanDetails = async () => { + try { + const result = await getScan(entityId); + setScanDetails(result?.data); + } catch (error) { + console.error("Error fetching scan details:", error); + } finally { + setIsLoading(false); + } + }; + + fetchScanDetails(); + }, [entityId]); + + if (isLoading) { + return ; + } + + if (!scanDetails) { + return
No scan details available
; + } + + return ; +}; diff --git a/components/findings/table/finding-detail.tsx b/components/findings/table/finding-detail.tsx new file mode 100644 index 0000000000..1b8455cfe7 --- /dev/null +++ b/components/findings/table/finding-detail.tsx @@ -0,0 +1,142 @@ +"use client"; + +import { Card, CardBody, CardHeader, Divider } from "@nextui-org/react"; + +import { DateWithTime, SnippetId } from "@/components/ui/entities"; +import { StatusBadge } from "@/components/ui/table/status-badge"; +import { ScanProps } from "@/types"; + +export const FindingDetail = ({ findingDetails }: { findingDetails: ScanProps }) => { + const scanOnDemand = findingDetails.attributes; + return ( +
+
+
+

Scan Details -

+

{scanOnDemand.name}

+
+ + +
+ +
+
+
+ } + /> + + + + +
+
+ + ) : ( + "Not Started" + ) + } + /> + + ) : ( + "Not Started" + ) + } + /> + + ) : ( + "Not Scheduled" + ) + } + /> + + } + /> + + } + /> +
+
+
+ + +

Scan Arguments

+
+ + + + + + +
+
+ ); +}; + +const DateItem = ({ + label, + value, +}: { + label: string; + value: React.ReactNode; +}) => ( +
+ {label}: + {value} +
+); + +const DetailItem = ({ + label, + value, +}: { + label: string; + value: React.ReactNode; +}) => ( +
+ {label}: + {value} +
+); diff --git a/components/findings/table/index.ts b/components/findings/table/index.ts index 89d5150daf..131a0ac73c 100644 --- a/components/findings/table/index.ts +++ b/components/findings/table/index.ts @@ -1,3 +1,4 @@ export * from "./column-findings"; export * from "./data-table-row-actions"; +export * from "./data-table-row-details"; export * from "./skeleton-table-findings"; diff --git a/components/services/ServiceCard.tsx b/components/services/ServiceCard.tsx index 9da8b62f2e..07ac6c3591 100644 --- a/components/services/ServiceCard.tsx +++ b/components/services/ServiceCard.tsx @@ -20,7 +20,7 @@ export const ServiceCard: React.FC = ({ {fidingsFailed > 0 ? `${fidingsFailed} Failed Findings` - : "All findings passed"} + : "No failed findings"} @@ -39,7 +39,7 @@ export const ServiceCard: React.FC = ({ radius="full" size="md" > - {fidingsFailed > 0 ? fidingsFailed : "All passed"} + {fidingsFailed > 0 ? fidingsFailed : ""}