Compare commits

...

5 Commits

Author SHA1 Message Date
Chandrapal Badshah 77451d5cf8 chore: update changelog 2025-09-23 16:45:21 +05:30
Chandrapal Badshah 5b54b47930 fix(lighthouse): allow scrolling during AI response streaming (#8669)
Co-authored-by: Chandrapal Badshah <12944530+Chan9390@users.noreply.github.com>
(cherry picked from commit 3949ab736d)
2025-09-23 09:09:41 +00:00
Prowler Bot a1168e3082 fix: handle 4XX and 204 properly (#8732)
Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
2025-09-15 17:43:18 +02:00
Prowler Bot f2341c9878 chore(changelog): remove whitespace in links (#8718)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2025-09-12 18:10:48 +05:45
Prowler Bot 67b8e925e5 chore(release): Bump version to v5.12.2 (#8713)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2025-09-12 13:24:37 +02:00
14 changed files with 83 additions and 36 deletions
+3
View File
@@ -78,3 +78,6 @@ _data/
# Claude
CLAUDE.md
# LLM's (Until we have a standard one)
AGENTS.md
+1 -1
View File
@@ -12,7 +12,7 @@ from prowler.lib.logger import logger
timestamp = datetime.today()
timestamp_utc = datetime.now(timezone.utc).replace(tzinfo=timezone.utc)
prowler_version = "5.12.1"
prowler_version = "5.12.2"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://prowler.com/wp-content/uploads/logo-html.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
+1 -1
View File
@@ -74,7 +74,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"}
name = "prowler"
readme = "README.md"
requires-python = ">3.9.1,<3.13"
version = "5.12.1"
version = "5.12.2"
[project.scripts]
prowler = "prowler.__main__:prowler"
+10 -2
View File
@@ -2,12 +2,20 @@
All notable changes to the **Prowler UI** are documented in this file.
## [1.12.2] (Prowler v5.12.2)
### 🐞 Fixed
- Handle 4XX errors consistently and 204 responses properly[(#8722)](https://github.com/prowler-cloud/prowler/pull/8722)
- Scrolling during Lighthouse AI response streaming [(#8669)](https://github.com/prowler-cloud/prowler/pull/8669)
## [1.12.1] (Prowler v5.12.1)
### 🐞 Fixed
- Field-level email validation message [(#8698)] (https://github.com/prowler-cloud/prowler/pull/8698)
- POST method on auth form [(#8699)] (https://github.com/prowler-cloud/prowler/pull/8699)
- Field-level email validation message [(#8698)](https://github.com/prowler-cloud/prowler/pull/8698)
- POST method on auth form [(#8699)](https://github.com/prowler-cloud/prowler/pull/8699)
## [1.12.0] (Prowler v5.12.0)
+1 -2
View File
@@ -159,8 +159,7 @@ export const updateRole = async (formData: FormData, roleId: string) => {
manage_providers: formData.get("manage_providers") === "true",
manage_account: formData.get("manage_account") === "true",
manage_scans: formData.get("manage_scans") === "true",
// TODO: Add back when we have integrations ready
// manage_integrations: formData.get("manage_integrations") === "true",
manage_integrations: formData.get("manage_integrations") === "true",
unlimited_visibility: formData.get("unlimited_visibility") === "true",
},
relationships: {},
+2 -1
View File
@@ -147,7 +147,8 @@ export const AuthForm = ({
} else {
newUser.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
switch (error.source.pointer) {
const pointer = error.source?.pointer;
switch (pointer) {
case "/data/attributes/name":
form.setError("name", { type: "server", message: errorMessage });
break;
@@ -53,7 +53,8 @@ export const SendInvitationForm = ({
if (data?.errors && data.errors.length > 0) {
data.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
switch (error.source.pointer) {
const pointer = error.source?.pointer;
switch (pointer) {
case "/data/attributes/email":
form.setError("email", {
type: "server",
-10
View File
@@ -146,16 +146,6 @@ export const Chat = ({ hasConfig, isActive }: ChatProps) => {
return () => document.removeEventListener("keydown", handleKeyDown);
}, [messageValue, onFormSubmit]);
useEffect(() => {
if (messagesContainerRef.current && latestUserMsgRef.current) {
const container = messagesContainerRef.current;
const userMsg = latestUserMsgRef.current;
const containerPadding = 16; // p-4 in Tailwind = 16px
container.scrollTop =
userMsg.offsetTop - container.offsetTop - containerPadding;
}
}, [messages]);
const suggestedActions: SuggestedAction[] = [
{
title: "Are there any exposed S3",
@@ -69,7 +69,8 @@ export const AddGroupForm = ({
if (data?.errors && data.errors.length > 0) {
data.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
switch (error.source.pointer) {
const pointer = error.source?.pointer;
switch (pointer) {
case "/data/attributes/name":
form.setError("name", {
type: "server",
@@ -105,7 +105,8 @@ export const EditGroupForm = ({
if (data?.errors && data.errors.length > 0) {
data.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
switch (error.source.pointer) {
const pointer = error.source?.pointer;
switch (pointer) {
case "/data/attributes/name":
form.setError("name", {
type: "server",
@@ -17,7 +17,7 @@ import {
CustomInput,
} from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import { permissionFormFields } from "@/lib";
import { getErrorMessage, permissionFormFields } from "@/lib";
import { addRoleFormSchema, ApiError } from "@/types";
type FormValues = z.infer<typeof addRoleFormSchema>;
@@ -113,7 +113,8 @@ export const AddRoleForm = ({
if (data?.errors && data.errors.length > 0) {
data.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
switch (error.source.pointer) {
const pointer = error.source?.pointer;
switch (pointer) {
case "/data/attributes/name":
form.setError("name", {
type: "server",
@@ -139,7 +140,7 @@ export const AddRoleForm = ({
toast({
variant: "destructive",
title: "Error",
description: "An unexpected error occurred. Please try again.",
description: getErrorMessage(error),
});
}
};
@@ -17,7 +17,7 @@ import {
CustomInput,
} from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import { permissionFormFields } from "@/lib";
import { getErrorMessage, permissionFormFields } from "@/lib";
import { ApiError, editRoleFormSchema } from "@/types";
type FormValues = z.infer<typeof editRoleFormSchema>;
@@ -133,7 +133,8 @@ export const EditRoleForm = ({
if (data?.errors && data.errors.length > 0) {
data.errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
switch (error.source.pointer) {
const pointer = error.source?.pointer;
switch (pointer) {
case "/data/attributes/name":
form.setError("name", {
type: "server",
@@ -159,7 +160,7 @@ export const EditRoleForm = ({
toast({
variant: "destructive",
title: "Error",
description: "An unexpected error occurred. Please try again.",
description: getErrorMessage(error),
});
}
};
+2 -1
View File
@@ -19,7 +19,8 @@ export const useFormServerErrors = <T extends Record<string, any>>(
) => {
errors.forEach((error: ApiError) => {
const errorMessage = error.detail;
const fieldName = errorMapping?.[error.source.pointer];
const pointer = error.source?.pointer;
const fieldName = pointer ? errorMapping?.[pointer] : undefined;
if (fieldName && fieldName in form.formState.defaultValues!) {
form.setError(fieldName as any, {
+49 -9
View File
@@ -348,10 +348,27 @@ export const handleApiResponse = async (
parse = true,
) => {
if (!response.ok) {
const errorData = await response.json().catch(() => null);
const errorDetail = errorData?.errors?.[0]?.detail;
// Read error body safely; prefer JSON, fallback to plain text
const rawErrorText = await response.text().catch(() => "");
let errorData: any = null;
try {
errorData = rawErrorText ? JSON.parse(rawErrorText) : null;
} catch {
errorData = null;
}
// Special handling for server errors (500+)
const errorsArray = Array.isArray(errorData?.errors)
? (errorData.errors as any[])
: undefined;
const errorDetail =
errorsArray?.[0]?.detail ||
errorData?.error ||
errorData?.message ||
(rawErrorText && rawErrorText.trim()) ||
response.statusText ||
"Oops! Something went wrong.";
//5XX errors
if (response.status >= 500) {
throw new Error(
errorDetail ||
@@ -359,14 +376,37 @@ export const handleApiResponse = async (
);
}
// Client errors (4xx)
throw new Error(
errorDetail ||
`Request failed (${response.status}): ${response.statusText}`,
);
return errorsArray
? { error: errorDetail, errors: errorsArray, status: response.status }
: ({ error: errorDetail, status: response.status } as any);
}
const data = await response.json();
// Handle empty or no-content responses gracefully (e.g., 204, empty body)
if (response.status === 204) {
if (pathToRevalidate && pathToRevalidate !== "") {
revalidatePath(pathToRevalidate);
}
return { success: true, status: response.status } as any;
}
// Read raw text to determine if there's a body to parse
const rawText = await response.text();
const hasBody = rawText && rawText.trim().length > 0;
if (!hasBody) {
if (pathToRevalidate && pathToRevalidate !== "") {
revalidatePath(pathToRevalidate);
}
return { success: true, status: response.status } as any;
}
let data: any;
try {
data = JSON.parse(rawText);
} catch (e) {
// If body isn't valid JSON, return as text payload
data = { data: rawText };
}
if (pathToRevalidate && pathToRevalidate !== "") {
revalidatePath(pathToRevalidate);