mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
feat: 50X errors handler (#8621)
This commit is contained in:
@@ -4,9 +4,9 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
## [1.11.1] (Prowler v5.11.1)
|
||||
|
||||
### 🐞 Changed
|
||||
### 🐞 Added
|
||||
|
||||
- Markdown rendering in finding details page [(#8604)](https://github.com/prowler-cloud/prowler/pull/8604)
|
||||
- Handle API responses and errors consistently across the app [(#8621)](https://github.com/prowler-cloud/prowler/pull/8621)
|
||||
|
||||
## [1.11.0] (Prowler v5.11.0)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib";
|
||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
||||
|
||||
export const getCompliancesOverview = async ({
|
||||
scanId,
|
||||
@@ -25,15 +24,12 @@ export const getCompliancesOverview = async ({
|
||||
}
|
||||
|
||||
try {
|
||||
const compliances = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await compliances.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/compliance");
|
||||
return parsedData;
|
||||
|
||||
return handleApiResponse(response, "/compliance");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching providers:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -63,17 +59,8 @@ export const getComplianceOverviewMetadataInfo = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch compliance overview metadata info: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const parsedData = parseStringify(await response.json());
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching compliance overview metadata info:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -90,22 +77,11 @@ export const getComplianceAttributes = async (complianceId: string) => {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch compliance attributes: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const parsedData = parseStringify(data);
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching compliance attributes:", error);
|
||||
return undefined;
|
||||
}
|
||||
// */
|
||||
};
|
||||
|
||||
export const getComplianceRequirements = async ({
|
||||
@@ -135,20 +111,9 @@ export const getComplianceRequirements = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch compliance requirements: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching compliance requirements:", error);
|
||||
return undefined;
|
||||
}
|
||||
// */
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib";
|
||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
||||
|
||||
export const getFindings = async ({
|
||||
page = 1,
|
||||
@@ -33,12 +32,8 @@ export const getFindings = async ({
|
||||
const findings = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await findings.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/findings");
|
||||
return parsedData;
|
||||
return handleApiResponse(findings, "/findings");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching findings:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -74,12 +69,8 @@ export const getLatestFindings = async ({
|
||||
const findings = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await findings.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/findings");
|
||||
return parsedData;
|
||||
return handleApiResponse(findings, "/findings");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching findings:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -113,15 +104,8 @@ export const getMetadataInfo = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch metadata info: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const parsedData = parseStringify(await response.json());
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching metadata info:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -155,15 +139,8 @@ export const getLatestMetadataInfo = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch metadata info: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const parsedData = parseStringify(await response.json());
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching metadata info:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -176,14 +153,11 @@ export const getFindingById = async (findingId: string, include = "") => {
|
||||
if (include) url.searchParams.append("include", include);
|
||||
|
||||
try {
|
||||
const finding = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await finding.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching finding by ID:", error);
|
||||
return undefined;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export {
|
||||
createIntegration,
|
||||
deleteIntegration,
|
||||
getIntegration,
|
||||
getIntegrations,
|
||||
pollConnectionTestStatus,
|
||||
testIntegrationConnection,
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
import { getTask } from "@/actions/task";
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
parseStringify,
|
||||
} from "@/lib";
|
||||
|
||||
import { getTask } from "@/actions/task";
|
||||
import { IntegrationType } from "@/types/integrations";
|
||||
|
||||
export const getIntegrations = async (searchParams?: URLSearchParams) => {
|
||||
@@ -25,39 +25,13 @@ export const getIntegrations = async (searchParams?: URLSearchParams) => {
|
||||
try {
|
||||
const response = await fetch(url.toString(), { method: "GET", headers });
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
}
|
||||
|
||||
console.error(`Failed to fetch integrations: ${response.statusText}`);
|
||||
return { data: [], meta: { pagination: { count: 0 } } };
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching integrations:", error);
|
||||
return { data: [], meta: { pagination: { count: 0 } } };
|
||||
}
|
||||
};
|
||||
|
||||
export const getIntegration = async (id: string) => {
|
||||
const headers = await getAuthHeaders({ contentType: false });
|
||||
const url = new URL(`${apiBaseUrl}/integrations/${id}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), { method: "GET", headers });
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
}
|
||||
|
||||
console.error(`Failed to fetch integration: ${response.statusText}`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error fetching integration:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const createIntegration = async (
|
||||
formData: FormData,
|
||||
): Promise<{ success: string; integrationId?: string } | { error: string }> => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib/helper";
|
||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib/helper";
|
||||
import { samlConfigFormSchema } from "@/types/formSchemas";
|
||||
|
||||
export const createSamlConfig = async (_prevState: any, formData: FormData) => {
|
||||
@@ -39,16 +39,7 @@ export const createSamlConfig = async (_prevState: any, formData: FormData) => {
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.errors?.[0]?.detail ||
|
||||
`Failed to create SAML config: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
await response.json();
|
||||
revalidatePath("/integrations");
|
||||
handleApiResponse(response, "/integrations", false);
|
||||
return { success: "SAML configuration created successfully!" };
|
||||
} catch (error) {
|
||||
console.error("Error creating SAML config:", error);
|
||||
@@ -98,16 +89,7 @@ export const updateSamlConfig = async (_prevState: any, formData: FormData) => {
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(
|
||||
errorData.errors?.[0]?.detail ||
|
||||
`Failed to update SAML config: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
await response.json();
|
||||
revalidatePath("/integrations");
|
||||
handleApiResponse(response, "/integrations", false);
|
||||
return { success: "SAML configuration updated successfully!" };
|
||||
} catch (error) {
|
||||
console.error("Error updating SAML config:", error);
|
||||
@@ -132,13 +114,7 @@ export const getSamlConfig = async () => {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch SAML config: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching SAML config:", error);
|
||||
return undefined;
|
||||
|
||||
@@ -6,8 +6,8 @@ import { redirect } from "next/navigation";
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
getErrorMessage,
|
||||
parseStringify,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
} from "@/lib";
|
||||
|
||||
export const getInvitations = async ({
|
||||
@@ -36,15 +36,12 @@ export const getInvitations = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const invitations = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await invitations.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/invitations");
|
||||
return parsedData;
|
||||
|
||||
return handleApiResponse(response, "/invitations");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching invitations:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -84,13 +81,10 @@ export const sendInvite = async (formData: FormData) => {
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -145,13 +139,9 @@ export const updateInvite = async (formData: FormData) => {
|
||||
return { error };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/invitations");
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response, "/invitations");
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,12 +155,9 @@ export const getInvitationInfoById = async (invitationId: string) => {
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -209,8 +196,6 @@ export const revokeInvite = async (formData: FormData) => {
|
||||
revalidatePath("/invitations");
|
||||
return data || { success: true };
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error revoking invitation:", error);
|
||||
return { error: getErrorMessage(error) };
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
getErrorMessage,
|
||||
parseStringify,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
} from "@/lib";
|
||||
import { ManageGroupPayload, ProviderGroupsResponse } from "@/types/components";
|
||||
|
||||
@@ -47,16 +48,8 @@ export const getProviderGroups = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error fetching provider groups: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data: ProviderGroupsResponse = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/manage-groups");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/manage-groups");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching provider groups:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -72,18 +65,9 @@ export const getProviderGroupInfoById = async (providerGroupId: string) => {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch provider group info: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -131,13 +115,10 @@ export const createProviderGroup = async (formData: FormData) => {
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
const data = await response.json();
|
||||
revalidatePath("/manage-groups");
|
||||
return parseStringify(data);
|
||||
|
||||
return handleApiResponse(response, "/manage-groups");
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -180,19 +161,9 @@ export const updateProviderGroup = async (
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to update provider group: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/manage-groups");
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response, "/manage-groups");
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -233,9 +204,8 @@ export const deleteProviderGroup = async (formData: FormData) => {
|
||||
revalidatePath("/manage-groups");
|
||||
return data || { success: true };
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error deleting provider group:", error);
|
||||
const message = await getErrorMessage(error);
|
||||
const message = getErrorMessage(error);
|
||||
return { errors: [{ detail: message }] };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"use server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib";
|
||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
||||
|
||||
export const getProvidersOverview = async ({
|
||||
page = 1,
|
||||
@@ -32,12 +31,8 @@ export const getProvidersOverview = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching providers overview:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -71,16 +66,8 @@ export const getFindingsByStatus = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch findings severity: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching findings severity overview:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -114,16 +101,8 @@ export const getFindingsBySeverity = async ({
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch findings severity: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching findings severity overview:", error);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,9 @@ import { redirect } from "next/navigation";
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
getErrorMessage,
|
||||
getFormValue,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
parseStringify,
|
||||
wait,
|
||||
} from "@/lib";
|
||||
import { buildSecretConfig } from "@/lib/provider-credentials/build-crendentials";
|
||||
@@ -43,15 +41,12 @@ export const getProviders = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const providers = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await providers.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/providers");
|
||||
return parsedData;
|
||||
|
||||
return handleApiResponse(response, "/providers");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching providers:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -64,16 +59,13 @@ export const getProvider = async (formData: FormData) => {
|
||||
const url = new URL(`${apiBaseUrl}/providers/${providerId}`);
|
||||
|
||||
try {
|
||||
const providers = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await providers.json();
|
||||
const parsedData = parseStringify(data);
|
||||
return parsedData;
|
||||
|
||||
return handleApiResponse(response, "/providers");
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -129,15 +121,9 @@ export const addProvider = async (formData: FormData) => {
|
||||
body: JSON.stringify(bodyData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/providers");
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response, "/providers");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -204,11 +190,6 @@ export const updateCredentialsProvider = async (
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json();
|
||||
return parseStringify(data); // Return API errors for UI handling
|
||||
}
|
||||
|
||||
return handleApiResponse(response, "/providers");
|
||||
} catch (error) {
|
||||
return handleApiError(error);
|
||||
@@ -223,6 +204,7 @@ export const checkConnectionProvider = async (formData: FormData) => {
|
||||
try {
|
||||
const response = await fetch(url.toString(), { method: "POST", headers });
|
||||
await wait(2000);
|
||||
|
||||
return handleApiResponse(response, "/providers");
|
||||
} catch (error) {
|
||||
return handleApiError(error);
|
||||
@@ -263,9 +245,7 @@ export const deleteCredentials = async (secretId: string) => {
|
||||
revalidatePath("/providers");
|
||||
return data || { success: true };
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error deleting credentials:", error);
|
||||
return { error: getErrorMessage(error) };
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -302,8 +282,6 @@ export const deleteProvider = async (formData: FormData) => {
|
||||
revalidatePath("/providers");
|
||||
return data || { success: true };
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error deleting provider:", error);
|
||||
return { error: getErrorMessage(error) };
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib";
|
||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
||||
|
||||
export const getResources = async ({
|
||||
page = 1,
|
||||
@@ -43,15 +42,11 @@ export const getResources = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const resources = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await resources.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
revalidatePath("/resources");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/resources");
|
||||
} catch (error) {
|
||||
console.error("Error fetching resources:", error);
|
||||
return undefined;
|
||||
@@ -96,15 +91,11 @@ export const getLatestResources = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const resources = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await resources.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
revalidatePath("/resources");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/resources");
|
||||
} catch (error) {
|
||||
console.error("Error fetching latest resources:", error);
|
||||
return undefined;
|
||||
@@ -128,16 +119,12 @@ export const getMetadataInfo = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const metadata = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await metadata.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching metadata info:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -160,14 +147,11 @@ export const getLatestMetadataInfo = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const metadata = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
|
||||
const data = await metadata.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching latest metadata info:", error);
|
||||
return undefined;
|
||||
@@ -205,10 +189,7 @@ export const getResourceById = async (
|
||||
throw new Error(`Error fetching resource: ${resource.status}`);
|
||||
}
|
||||
|
||||
const data = await resource.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(resource);
|
||||
} catch (error) {
|
||||
console.error("Error fetching resource by ID:", error);
|
||||
return undefined;
|
||||
|
||||
@@ -6,8 +6,8 @@ import { redirect } from "next/navigation";
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
getErrorMessage,
|
||||
parseStringify,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
} from "@/lib";
|
||||
|
||||
export const getRoles = async ({
|
||||
@@ -36,15 +36,12 @@ export const getRoles = async ({
|
||||
});
|
||||
|
||||
try {
|
||||
const roles = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await roles.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/roles");
|
||||
return parsedData;
|
||||
|
||||
return handleApiResponse(response, "/roles");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching roles:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -60,16 +57,9 @@ export const getRoleInfoById = async (roleId: string) => {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch role info: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,14 +82,8 @@ export const getRolesByIds = async (roleIds: string[]) => {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch roles: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching roles by IDs:", error);
|
||||
return { data: [] };
|
||||
}
|
||||
@@ -153,15 +137,9 @@ export const addRole = async (formData: FormData) => {
|
||||
body,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/roles");
|
||||
return data;
|
||||
return handleApiResponse(response, "/roles", false);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error during API call:", error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,15 +193,9 @@ export const updateRole = async (formData: FormData, roleId: string) => {
|
||||
body,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/roles");
|
||||
return data;
|
||||
return handleApiResponse(response, "/roles", false);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error during API call:", error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -254,8 +226,6 @@ export const deleteRole = async (roleId: string) => {
|
||||
revalidatePath("/roles");
|
||||
return data || { success: true };
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error deleting role:", error);
|
||||
return { error: getErrorMessage(error) };
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
getErrorMessage,
|
||||
parseStringify,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
} from "@/lib";
|
||||
|
||||
export const getScans = async ({
|
||||
@@ -43,12 +43,9 @@ export const getScans = async ({
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), { headers });
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/scans");
|
||||
return parsedData;
|
||||
|
||||
return handleApiResponse(response, "/scans");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching scans:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -56,9 +53,7 @@ export const getScans = async ({
|
||||
|
||||
export const getScansByState = async () => {
|
||||
const headers = await getAuthHeaders({ contentType: false });
|
||||
|
||||
const url = new URL(`${apiBaseUrl}/scans`);
|
||||
|
||||
// Request only the necessary fields to optimize the response
|
||||
url.searchParams.append("fields[scans]", "state");
|
||||
|
||||
@@ -67,20 +62,8 @@ export const getScansByState = async () => {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData?.message || "Failed to fetch scans by state");
|
||||
} catch {
|
||||
throw new Error("Failed to fetch scans by state");
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching scans by state:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -92,17 +75,13 @@ export const getScan = async (scanId: string) => {
|
||||
const url = new URL(`${apiBaseUrl}/scans/${scanId}`);
|
||||
|
||||
try {
|
||||
const scan = await fetch(url.toString(), {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await scan.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
return parsedData;
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,20 +118,9 @@ export const scanOnDemand = async (formData: FormData) => {
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
|
||||
return { success: false, error: errorData.errors[0].detail };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/scans");
|
||||
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response, "/scans");
|
||||
} catch (error) {
|
||||
console.error("Error starting scan:", error);
|
||||
|
||||
return { error: getErrorMessage(error) };
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -177,19 +145,9 @@ export const scheduleDaily = async (formData: FormData) => {
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to schedule daily: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/scans");
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response, "/scans");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,15 +173,10 @@ export const updateScan = async (formData: FormData) => {
|
||||
},
|
||||
}),
|
||||
});
|
||||
const data = await response.json();
|
||||
revalidatePath("/scans");
|
||||
return parseStringify(data);
|
||||
|
||||
return handleApiResponse(response, "/scans");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
getErrorMessage,
|
||||
parseStringify,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
} from "@/lib";
|
||||
|
||||
export const getTask = async (taskId: string) => {
|
||||
@@ -16,9 +16,9 @@ export const getTask = async (taskId: string) => {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
return { error: getErrorMessage(error) };
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
|
||||
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib/helper";
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
} from "@/lib/helper";
|
||||
|
||||
export const getAllTenants = async () => {
|
||||
const headers = await getAuthHeaders({ contentType: false });
|
||||
@@ -19,12 +23,8 @@ export const getAllTenants = async () => {
|
||||
throw new Error(`Failed to fetch tenants data: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/profile");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/profile");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching tenants:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -80,16 +80,9 @@ export async function updateTenantName(prevState: any, formData: FormData) {
|
||||
throw new Error(`Failed to update tenant name: ${response.statusText}`);
|
||||
}
|
||||
|
||||
await response.json();
|
||||
revalidatePath("/profile");
|
||||
handleApiResponse(response, "/profile", false);
|
||||
return { success: "Tenant name updated successfully!" };
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error updating tenant name:", error);
|
||||
return {
|
||||
errors: {
|
||||
general: "Error updating tenant name. Please try again.",
|
||||
},
|
||||
};
|
||||
return handleApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import { redirect } from "next/navigation";
|
||||
import {
|
||||
apiBaseUrl,
|
||||
getAuthHeaders,
|
||||
getErrorMessage,
|
||||
parseStringify,
|
||||
handleApiError,
|
||||
handleApiResponse,
|
||||
} from "@/lib";
|
||||
|
||||
export const getUsers = async ({
|
||||
@@ -39,12 +39,9 @@ export const getUsers = async ({
|
||||
const users = await fetch(url.toString(), {
|
||||
headers,
|
||||
});
|
||||
const data = await users.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/users");
|
||||
return parsedData;
|
||||
|
||||
return handleApiResponse(users, "/users");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching users:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -88,15 +85,9 @@ export const updateUser = async (formData: FormData) => {
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
revalidatePath("/users");
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response, "/users");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -129,20 +120,9 @@ export const updateUserRole = async (formData: FormData) => {
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: data.errors || "An error occurred" };
|
||||
}
|
||||
|
||||
revalidatePath("/users"); // Update the path as needed
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response, "/users");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
};
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -178,9 +158,7 @@ export const deleteUser = async (formData: FormData) => {
|
||||
revalidatePath("/users");
|
||||
return data || { success: true };
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error deleting user:", error);
|
||||
return { error: getErrorMessage(error) };
|
||||
handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -198,12 +176,8 @@ export const getUserInfo = async () => {
|
||||
throw new Error(`Failed to fetch user data: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/profile");
|
||||
return parsedData;
|
||||
return handleApiResponse(response, "/profile");
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching profile:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -224,16 +198,8 @@ export const getUserMemberships = async (userId: string) => {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch user memberships: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return parseStringify(data);
|
||||
return handleApiResponse(response);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching user memberships:", error);
|
||||
return { data: [] };
|
||||
}
|
||||
|
||||
@@ -1,35 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { RocketIcon } from "@/components/icons";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui";
|
||||
import { CustomButton } from "@/components/ui/custom";
|
||||
import { CustomLink } from "@/components/ui/custom/custom-link";
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
// reset,
|
||||
reset,
|
||||
}: {
|
||||
error: Error;
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
/* eslint-disable no-console */
|
||||
console.error(error);
|
||||
// Check if it's a 500 error
|
||||
const is500Error =
|
||||
error.message?.includes("500") ||
|
||||
error.message?.includes("502") ||
|
||||
error.message?.includes("503") ||
|
||||
error.message?.toLowerCase().includes("server error");
|
||||
|
||||
if (is500Error) {
|
||||
// Log 500 errors specifically for monitoring
|
||||
console.error("Server error detected:", {
|
||||
message: error.message,
|
||||
digest: error.digest,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
// TODO: sent to sentry
|
||||
} else {
|
||||
console.error("Application error:", error);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const is500Error =
|
||||
error.message?.includes("500") ||
|
||||
error.message?.includes("502") ||
|
||||
error.message?.includes("503") ||
|
||||
error.message?.toLowerCase().includes("server error");
|
||||
|
||||
return (
|
||||
<Alert className="mx-auto mt-[35%] w-fit">
|
||||
<RocketIcon className="h-5 w-5" />
|
||||
<AlertTitle className="text-lg">An unexpected error occurred</AlertTitle>
|
||||
<AlertDescription className="mb-5">
|
||||
We're sorry for the inconvenience. Please try again or contact
|
||||
support if the problem persists.
|
||||
</AlertDescription>
|
||||
<CustomLink href="/" target="_self" className="font-bold">
|
||||
Go to the homepage
|
||||
</CustomLink>
|
||||
</Alert>
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<Alert className="w-full max-w-lg">
|
||||
<Icon
|
||||
icon={is500Error ? "tabler:server-off" : "tabler:rocket-off"}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
<AlertTitle className="text-lg">
|
||||
{is500Error
|
||||
? "Server temporarily unavailable"
|
||||
: "An unexpected error occurred"}
|
||||
</AlertTitle>
|
||||
<AlertDescription className="mb-5">
|
||||
{is500Error
|
||||
? "The server is experiencing issues. Our team has been notified and is working on it. Please try again in a few moments."
|
||||
: "We're sorry for the inconvenience. Please try again or contact support if the problem persists."}
|
||||
</AlertDescription>
|
||||
<div className="flex items-center justify-start gap-3">
|
||||
<CustomButton
|
||||
onPress={reset}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
startContent={<Icon icon="tabler:refresh" className="h-4 w-4" />}
|
||||
ariaLabel="Try Again"
|
||||
>
|
||||
Try Again
|
||||
</CustomButton>
|
||||
<CustomLink href="/" target="_self" className="font-bold">
|
||||
Go to Overview
|
||||
</CustomLink>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { Snippet } from "@nextui-org/react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { CustomSection } from "@/components/ui/custom";
|
||||
@@ -14,14 +13,6 @@ import { FindingProps, ProviderType } from "@/types";
|
||||
import { Muted } from "../muted";
|
||||
import { DeltaIndicator } from "./delta-indicator";
|
||||
|
||||
const MarkdownContainer = ({ children }: { children: string }) => {
|
||||
return (
|
||||
<div className="prose prose-sm max-w-none whitespace-normal break-words dark:prose-invert">
|
||||
<ReactMarkdown>{children}</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderValue = (value: string | null | undefined) => {
|
||||
return value && value.trim() !== "" ? value : "-";
|
||||
};
|
||||
@@ -131,17 +122,15 @@ export const FindingDetail = ({
|
||||
hideCopyButton
|
||||
hideSymbol
|
||||
>
|
||||
<MarkdownContainer>
|
||||
<p className="whitespace-pre-line">
|
||||
{attributes.check_metadata.risk}
|
||||
</MarkdownContainer>
|
||||
</p>
|
||||
</Snippet>
|
||||
</InfoField>
|
||||
)}
|
||||
|
||||
<InfoField label="Description">
|
||||
<MarkdownContainer>
|
||||
{attributes.check_metadata.description}
|
||||
</MarkdownContainer>
|
||||
{renderValue(attributes.check_metadata.description)}
|
||||
</InfoField>
|
||||
|
||||
<InfoField label="Status Extended">
|
||||
@@ -158,9 +147,9 @@ export const FindingDetail = ({
|
||||
{attributes.check_metadata.remediation.recommendation.text && (
|
||||
<InfoField label="Recommendation">
|
||||
<div className="flex flex-col gap-2">
|
||||
<MarkdownContainer>
|
||||
<p>
|
||||
{attributes.check_metadata.remediation.recommendation.text}
|
||||
</MarkdownContainer>
|
||||
</p>
|
||||
{attributes.check_metadata.remediation.recommendation.url && (
|
||||
<CustomLink
|
||||
href={
|
||||
@@ -186,12 +175,15 @@ export const FindingDetail = ({
|
||||
</InfoField>
|
||||
)}
|
||||
|
||||
{/* Remediation Steps section */}
|
||||
{/* Additional Resources section */}
|
||||
{attributes.check_metadata.remediation.code.other && (
|
||||
<InfoField label="Remediation Steps">
|
||||
<MarkdownContainer>
|
||||
{attributes.check_metadata.remediation.code.other}
|
||||
</MarkdownContainer>
|
||||
<InfoField label="Additional Resources">
|
||||
<CustomLink
|
||||
href={attributes.check_metadata.remediation.code.other}
|
||||
size="sm"
|
||||
>
|
||||
View documentation
|
||||
</CustomLink>
|
||||
</InfoField>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -21,17 +21,17 @@ export const EditTenantForm = ({
|
||||
const { toast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (state?.success) {
|
||||
if (state && "success" in state) {
|
||||
toast({
|
||||
title: "Changed successfully",
|
||||
description: state.success,
|
||||
});
|
||||
setIsOpen(false);
|
||||
} else if (state?.errors?.general) {
|
||||
} else if (state && "error" in state) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Oops! Something went wrong",
|
||||
description: state.errors.general,
|
||||
description: state.error,
|
||||
});
|
||||
}
|
||||
}, [state, toast, setIsOpen]);
|
||||
@@ -49,8 +49,8 @@ export const EditTenantForm = ({
|
||||
labelPlacement="outside"
|
||||
variant="bordered"
|
||||
isRequired={true}
|
||||
isInvalid={!!state?.errors?.name}
|
||||
errorMessage={state?.errors?.name}
|
||||
isInvalid={!!(state && "error" in state)}
|
||||
errorMessage={state && "error" in state ? state.error : undefined}
|
||||
/>
|
||||
|
||||
{/* Hidden inputs for Server Action */}
|
||||
|
||||
@@ -345,18 +345,38 @@ export const permissionFormFields: PermissionInfo[] = [
|
||||
export const handleApiResponse = async (
|
||||
response: Response,
|
||||
pathToRevalidate?: string,
|
||||
parse = true,
|
||||
) => {
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => null);
|
||||
const errorDetail = errorData?.errors?.[0]?.detail;
|
||||
|
||||
// Special handling for server errors (500+)
|
||||
if (response.status >= 500) {
|
||||
throw new Error(
|
||||
errorDetail ||
|
||||
`Server error (${response.status}): The server encountered an error. Please try again later.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Client errors (4xx)
|
||||
throw new Error(
|
||||
errorDetail ||
|
||||
`Request failed (${response.status}): ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (pathToRevalidate) {
|
||||
if (pathToRevalidate && pathToRevalidate !== "") {
|
||||
revalidatePath(pathToRevalidate);
|
||||
}
|
||||
|
||||
return parseStringify(data);
|
||||
return parse ? parseStringify(data) : data;
|
||||
};
|
||||
|
||||
// Helper function to handle API errors consistently
|
||||
export const handleApiError = (error: unknown) => {
|
||||
export const handleApiError = (error: unknown): { error: string } => {
|
||||
console.error(error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
|
||||
0
ui/types/server-actions.ts
Normal file
0
ui/types/server-actions.ts
Normal file
Reference in New Issue
Block a user