diff --git a/ui/actions/auth/auth.ts b/ui/actions/auth/auth.ts index 1bbc79b17e..d39314d2da 100644 --- a/ui/actions/auth/auth.ts +++ b/ui/actions/auth/auth.ts @@ -37,6 +37,10 @@ export async function authenticate( credentials: "Incorrect email or password", }, }; + case "CallbackRouteError": + return { + message: error.cause?.err?.message, + }; default: return { message: "Unknown error", @@ -152,22 +156,31 @@ export const getUserByMe = async (accessToken: string) => { }, }); - if (!response.ok) throw new Error("Error in trying to get user by me"); - const parsedResponse = await response.json(); + if (!response.ok) { + // Handle different HTTP error codes + switch (response.status) { + case 401: + throw new Error("Invalid or expired token"); + case 403: + throw new Error(parsedResponse.errors?.[0]?.detail); + case 404: + throw new Error("User not found"); + default: + throw new Error( + parsedResponse.errors?.[0]?.detail || "Unknown error", + ); + } + } - const name = parsedResponse.data.attributes.name; - const email = parsedResponse.data.attributes.email; - const company = parsedResponse.data.attributes.company_name; - const dateJoined = parsedResponse.data.attributes.date_joined; return { - name, - email, - company, - dateJoined, + name: parsedResponse.data.attributes.name, + email: parsedResponse.data.attributes.email, + company: parsedResponse.data.attributes.company_name, + dateJoined: parsedResponse.data.attributes.date_joined, }; - } catch (error) { - throw new Error("Error in trying to get user by me"); + } catch (error: any) { + throw new Error(error.message || "Network error or server unreachable"); } }; diff --git a/ui/app/(prowler)/compliance/page.tsx b/ui/app/(prowler)/compliance/page.tsx index 1bc58ad741..558caeaf9b 100644 --- a/ui/app/(prowler)/compliance/page.tsx +++ b/ui/app/(prowler)/compliance/page.tsx @@ -140,6 +140,7 @@ const SSRComplianceGrid = async ({ const { attributes } = compliance; const { framework, + version, requirements_status: { passed, total }, } = attributes; @@ -147,6 +148,7 @@ const SSRComplianceGrid = async ({ { const formSchema = authFormSchema(type); const router = useRouter(); @@ -47,7 +49,6 @@ export const AuthForm = ({ email: data.email.toLowerCase(), password: data.password, }); - if (result?.message === "Success") { router.push("/"); } else if (result?.errors && "credentials" in result.errors) { @@ -55,6 +56,8 @@ export const AuthForm = ({ type: "server", message: result.errors.credentials ?? "Incorrect email or password", }); + } else if (result?.message === "User email is not verified") { + router.push("/email-verification"); } else { toast({ variant: "destructive", @@ -73,7 +76,12 @@ export const AuthForm = ({ description: "The user was registered successfully.", }); form.reset(); - router.push("/sign-in"); + + if (isCloudEnv) { + router.push("/email-verification"); + } else { + router.push("/sign-in"); + } } else { newUser.errors.forEach((error: ApiError) => { const errorMessage = error.detail; diff --git a/ui/components/compliance/compliance-card.tsx b/ui/components/compliance/compliance-card.tsx index 7fcfdbf5dd..b1b8d8ec10 100644 --- a/ui/components/compliance/compliance-card.tsx +++ b/ui/components/compliance/compliance-card.tsx @@ -6,6 +6,7 @@ import { getComplianceIcon } from "../icons"; interface ComplianceCardProps { title: string; + version: string; passingRequirements: number; totalRequirements: number; prevPassingRequirements: number; @@ -14,9 +15,14 @@ interface ComplianceCardProps { export const ComplianceCard: React.FC = ({ title, + version, passingRequirements, totalRequirements, }) => { + const formatTitle = (title: string) => { + return title.split("-").join(" "); + }; + const ratingPercentage = Math.floor( (passingRequirements / totalRequirements) * 100, ); @@ -47,7 +53,7 @@ export const ComplianceCard: React.FC = ({ }; return ( - +
= ({ className="h-10 w-10 min-w-10 rounded-md border-1 border-gray-300 bg-white object-contain p-1" />
-

{title}

+

+ {formatTitle(title)} + {version ? ` - ${version}` : ""} +

{ const searchParams = useSearchParams(); const [showClearButton, setShowClearButton] = useState(false); const scanIdParam = searchParams.get("scanId"); - const selectedScanId = scanIdParam || scans[0]?.id; + const selectedScanId = scanIdParam || (scans.length > 0 ? scans[0].id : ""); + + useEffect(() => { + if (!scanIdParam && scans.length > 0) { + const params = new URLSearchParams(searchParams); + params.set("scanId", scans[0].id); + router.push(`?${params.toString()}`); + } + }, [scans, scanIdParam, searchParams, router]); useEffect(() => { const hasFilters = Array.from(searchParams.keys()).some( diff --git a/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx index 0725d134eb..3e6caeca88 100644 --- a/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/via-credentials/k8s-credentials-form.tsx @@ -1,6 +1,6 @@ import { Control } from "react-hook-form"; -import { CustomInput } from "@/components/ui/custom"; +import { CustomTextarea } from "@/components/ui/custom"; import { KubernetesCredentials } from "@/types"; export const KubernetesCredentialsForm = ({ @@ -15,17 +15,17 @@ export const KubernetesCredentialsForm = ({ Connect via Credentials
- Please provide the information for your Kubernetes credentials. + Please provide the kubeconfig content for your Kubernetes credentials.
- diff --git a/ui/components/ui/custom/custom-textarea.tsx b/ui/components/ui/custom/custom-textarea.tsx new file mode 100644 index 0000000000..c3f6dc0994 --- /dev/null +++ b/ui/components/ui/custom/custom-textarea.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { Textarea } from "@nextui-org/input"; +import React from "react"; +import { Control, FieldPath, FieldValues } from "react-hook-form"; + +import { FormControl, FormField, FormMessage } from "@/components/ui/form"; + +interface CustomTextareaProps { + control: Control; + name: FieldPath; + label?: string; + labelPlacement?: "inside" | "outside" | "outside-left"; + variant?: "flat" | "bordered" | "underlined" | "faded"; + size?: "sm" | "md" | "lg"; + placeholder?: string; + defaultValue?: string; + isRequired?: boolean; + isInvalid?: boolean; + minRows?: number; + maxRows?: number; + fullWidth?: boolean; + disableAutosize?: boolean; + description?: React.ReactNode; +} + +export const CustomTextarea = ({ + control, + name, + label = name, + labelPlacement = "inside", + placeholder, + variant = "flat", + size = "md", + defaultValue, + isRequired = false, + isInvalid = false, + minRows = 3, + maxRows = 8, + fullWidth = true, + disableAutosize = false, + description, +}: CustomTextareaProps) => { + return ( + ( + <> + +