mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
feat: upgrade to React 19, Next.js 15, React Compiler, HeroUI and Tailwind 4 (#8748)
Co-authored-by: Alan Buscaglia <alanbuscaglia@MacBook-Pro.local> Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com> Co-authored-by: César Arroba <cesar@prowler.com> Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
This commit is contained in:
2
.env
2
.env
@@ -99,7 +99,7 @@ SENTRY_ENVIRONMENT=local
|
|||||||
SENTRY_RELEASE=local
|
SENTRY_RELEASE=local
|
||||||
|
|
||||||
#### Prowler release version ####
|
#### Prowler release version ####
|
||||||
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.10.0
|
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.12.2
|
||||||
|
|
||||||
# Social login credentials
|
# Social login credentials
|
||||||
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -45,3 +45,7 @@ pypi-upload: ## Upload package
|
|||||||
help: ## Show this help.
|
help: ## Show this help.
|
||||||
@echo "Prowler Makefile"
|
@echo "Prowler Makefile"
|
||||||
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
##@ Development Environment
|
||||||
|
run-api-dev: ## Start development environment with API, PostgreSQL, Valkey, and workers
|
||||||
|
docker compose -f docker-compose-dev.yml up api-dev postgres valkey worker-dev worker-beat --build
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ node_modules
|
|||||||
coverage
|
coverage
|
||||||
.next
|
.next
|
||||||
build
|
build
|
||||||
|
next-env.d.ts
|
||||||
!.commitlintrc.cjs
|
!.commitlintrc.cjs
|
||||||
!.lintstagedrc.cjs
|
!.lintstagedrc.cjs
|
||||||
!jest.config.js
|
!jest.config.js
|
||||||
|
|||||||
@@ -9,11 +9,18 @@ All notable changes to the **Prowler UI** are documented in this file.
|
|||||||
- Support for Markdown and AdditionalURLs in findings detail page [(#8704)](https://github.com/prowler-cloud/prowler/pull/8704)
|
- Support for Markdown and AdditionalURLs in findings detail page [(#8704)](https://github.com/prowler-cloud/prowler/pull/8704)
|
||||||
- `Prowler Hub` menu item with tooltip [(#8692)] (https://github.com/prowler-cloud/prowler/pull/8692)
|
- `Prowler Hub` menu item with tooltip [(#8692)] (https://github.com/prowler-cloud/prowler/pull/8692)
|
||||||
- Copy link button to finding detail page [(#8685)] (https://github.com/prowler-cloud/prowler/pull/8685)
|
- Copy link button to finding detail page [(#8685)] (https://github.com/prowler-cloud/prowler/pull/8685)
|
||||||
|
- React Compiler support for automatic optimization [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
|
- Turbopack support for faster development builds [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
- Add compliance name in compliance detail view [(#8775)](https://github.com/prowler-cloud/prowler/pull/8775)
|
- Add compliance name in compliance detail view [(#8775)](https://github.com/prowler-cloud/prowler/pull/8775)
|
||||||
|
|
||||||
### 🔄 Changed
|
### 🔄 Changed
|
||||||
|
|
||||||
### 🐞 Fixed
|
- Upgraded React to version 19.1.1 with async components support [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
|
- Upgraded Next.js to version 15.5.3 with enhanced App Router [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
|
- Updated from NextUI to HeroUI [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
|
- Updated LangChain to latest versions with API improvements [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
|
- Migrated all page components to async `params`/`searchParams` API [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
|
- Migrated from `useFormState` to `useActionState` for React 19 compatibility [(#8748)](https://github.com/prowler-cloud/prowler/pull/8748)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
|
import { handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
|
|
||||||
export const getCompliancesOverview = async ({
|
export const getCompliancesOverview = async ({
|
||||||
scanId,
|
scanId,
|
||||||
@@ -28,7 +29,7 @@ export const getCompliancesOverview = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/compliance");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching providers:", error);
|
console.error("Error fetching providers:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
|
import { handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
|
|
||||||
export const getFindings = async ({
|
export const getFindings = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
@@ -32,7 +33,7 @@ export const getFindings = async ({
|
|||||||
const findings = await fetch(url.toString(), {
|
const findings = await fetch(url.toString(), {
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
return handleApiResponse(findings, "/findings");
|
return handleApiResponse(findings);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching findings:", error);
|
console.error("Error fetching findings:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -69,7 +70,7 @@ export const getLatestFindings = async ({
|
|||||||
const findings = await fetch(url.toString(), {
|
const findings = await fetch(url.toString(), {
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
return handleApiResponse(findings, "/findings");
|
return handleApiResponse(findings);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching findings:", error);
|
console.error("Error fetching findings:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -3,13 +3,8 @@
|
|||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
|
||||||
import { pollTaskUntilSettled } from "@/actions/task/poll";
|
import { pollTaskUntilSettled } from "@/actions/task/poll";
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
parseStringify,
|
|
||||||
} from "@/lib";
|
|
||||||
import { IntegrationType } from "@/types/integrations";
|
import { IntegrationType } from "@/types/integrations";
|
||||||
import type { TaskState } from "@/types/tasks";
|
import type { TaskState } from "@/types/tasks";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { pollTaskUntilSettled } from "@/actions/task/poll";
|
import { pollTaskUntilSettled } from "@/actions/task/poll";
|
||||||
import { apiBaseUrl, getAuthHeaders, handleApiError } from "@/lib";
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
|
import { handleApiError } from "@/lib/server-actions-helper";
|
||||||
import type {
|
import type {
|
||||||
IntegrationProps,
|
IntegrationProps,
|
||||||
JiraDispatchRequest,
|
JiraDispatchRequest,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
|
||||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib/helper";
|
import { apiBaseUrl, getAuthHeaders } from "@/lib/helper";
|
||||||
|
import { handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
import { samlConfigFormSchema } from "@/types/formSchemas";
|
import { samlConfigFormSchema } from "@/types/formSchemas";
|
||||||
|
|
||||||
export const createSamlConfig = async (_prevState: any, formData: FormData) => {
|
export const createSamlConfig = async (_prevState: any, formData: FormData) => {
|
||||||
|
|||||||
@@ -3,12 +3,8 @@
|
|||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
} from "@/lib";
|
|
||||||
|
|
||||||
export const getInvitations = async ({
|
export const getInvitations = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
@@ -40,7 +36,7 @@ export const getInvitations = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/invitations");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching invitations:", error);
|
console.error("Error fetching invitations:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -3,13 +3,8 @@
|
|||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders, getErrorMessage } from "@/lib";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
getErrorMessage,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
} from "@/lib";
|
|
||||||
import { ManageGroupPayload, ProviderGroupsResponse } from "@/types/components";
|
import { ManageGroupPayload, ProviderGroupsResponse } from "@/types/components";
|
||||||
|
|
||||||
export const getProviderGroups = async ({
|
export const getProviderGroups = async ({
|
||||||
@@ -48,7 +43,7 @@ export const getProviderGroups = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/manage-groups");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching provider groups:", error);
|
console.error("Error fetching provider groups:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -161,7 +156,7 @@ export const updateProviderGroup = async (
|
|||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/manage-groups");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleApiError(error);
|
handleApiError(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
|
import { handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
|
|
||||||
export const getProvidersOverview = async ({
|
export const getProvidersOverview = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
@@ -31,7 +32,7 @@ export const getProvidersOverview = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching providers overview:", error);
|
console.error("Error fetching providers overview:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -72,7 +73,7 @@ export const getFindingsByStatus = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching findings severity overview:", error);
|
console.error("Error fetching findings severity overview:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -108,7 +109,7 @@ export const getFindingsBySeverity = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching findings severity overview:", error);
|
console.error("Error fetching findings severity overview:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -3,16 +3,10 @@
|
|||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders, getFormValue, wait } from "@/lib";
|
||||||
apiBaseUrl,
|
|
||||||
getAuthHeaders,
|
|
||||||
getFormValue,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
wait,
|
|
||||||
} from "@/lib";
|
|
||||||
import { buildSecretConfig } from "@/lib/provider-credentials/build-crendentials";
|
import { buildSecretConfig } from "@/lib/provider-credentials/build-crendentials";
|
||||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||||
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
import { ProvidersApiResponse, ProviderType } from "@/types/providers";
|
import { ProvidersApiResponse, ProviderType } from "@/types/providers";
|
||||||
|
|
||||||
export const getProviders = async ({
|
export const getProviders = async ({
|
||||||
@@ -45,7 +39,7 @@ export const getProviders = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/providers");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching providers:", error);
|
console.error("Error fetching providers:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -63,7 +57,7 @@ export const getProvider = async (formData: FormData) => {
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/providers");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return handleApiError(error);
|
return handleApiError(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { apiBaseUrl, getAuthHeaders, handleApiResponse } from "@/lib";
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
|
import { handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
|
|
||||||
export const getResources = async ({
|
export const getResources = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
@@ -46,7 +47,7 @@ export const getResources = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/resources");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching resources:", error);
|
console.error("Error fetching resources:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -95,7 +96,7 @@ export const getLatestResources = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/resources");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching latest resources:", error);
|
console.error("Error fetching latest resources:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -3,12 +3,8 @@
|
|||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
} from "@/lib";
|
|
||||||
|
|
||||||
export const getRoles = async ({
|
export const getRoles = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
@@ -40,7 +36,7 @@ export const getRoles = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(response, "/roles");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching roles:", error);
|
console.error("Error fetching roles:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -2,13 +2,8 @@
|
|||||||
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders, getErrorMessage } from "@/lib";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
getErrorMessage,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
} from "@/lib";
|
|
||||||
|
|
||||||
export const getScans = async ({
|
export const getScans = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
@@ -44,7 +39,7 @@ export const getScans = async ({
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(url.toString(), { headers });
|
const response = await fetch(url.toString(), { headers });
|
||||||
|
|
||||||
return handleApiResponse(response, "/scans");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching scans:", error);
|
console.error("Error fetching scans:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
} from "@/lib";
|
|
||||||
|
|
||||||
export const getTask = async (taskId: string) => {
|
export const getTask = async (taskId: string) => {
|
||||||
const headers = await getAuthHeaders({ contentType: false });
|
const headers = await getAuthHeaders({ contentType: false });
|
||||||
|
|||||||
@@ -2,12 +2,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders } from "@/lib/helper";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
} from "@/lib/helper";
|
|
||||||
|
|
||||||
export const getAllTenants = async () => {
|
export const getAllTenants = async () => {
|
||||||
const headers = await getAuthHeaders({ contentType: false });
|
const headers = await getAuthHeaders({ contentType: false });
|
||||||
@@ -23,7 +19,7 @@ export const getAllTenants = async () => {
|
|||||||
throw new Error(`Failed to fetch tenants data: ${response.statusText}`);
|
throw new Error(`Failed to fetch tenants data: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleApiResponse(response, "/profile");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching tenants:", error);
|
console.error("Error fetching tenants:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -41,7 +37,7 @@ const editTenantFormSchema = z
|
|||||||
path: ["name"],
|
path: ["name"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function updateTenantName(prevState: any, formData: FormData) {
|
export async function updateTenantName(_prevState: any, formData: FormData) {
|
||||||
const headers = await getAuthHeaders({ contentType: true });
|
const headers = await getAuthHeaders({ contentType: true });
|
||||||
const formDataObject = Object.fromEntries(formData);
|
const formDataObject = Object.fromEntries(formData);
|
||||||
const validatedData = editTenantFormSchema.safeParse(formDataObject);
|
const validatedData = editTenantFormSchema.safeParse(formDataObject);
|
||||||
|
|||||||
@@ -3,12 +3,8 @@
|
|||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import {
|
import { apiBaseUrl, getAuthHeaders } from "@/lib";
|
||||||
apiBaseUrl,
|
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
|
||||||
getAuthHeaders,
|
|
||||||
handleApiError,
|
|
||||||
handleApiResponse,
|
|
||||||
} from "@/lib";
|
|
||||||
|
|
||||||
export const getUsers = async ({
|
export const getUsers = async ({
|
||||||
page = 1,
|
page = 1,
|
||||||
@@ -40,7 +36,7 @@ export const getUsers = async ({
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return handleApiResponse(users, "/users");
|
return handleApiResponse(users);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching users:", error);
|
console.error("Error fetching users:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -178,7 +174,7 @@ export const getUserInfo = async () => {
|
|||||||
throw new Error(`Failed to fetch user data: ${response.statusText}`);
|
throw new Error(`Failed to fetch user data: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleApiResponse(response, "/profile");
|
return handleApiResponse(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching profile:", error);
|
console.error("Error fetching profile:", error);
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default async function RootLayout({
|
|||||||
<body
|
<body
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-h-screen bg-background font-sans antialiased",
|
"bg-background min-h-screen font-sans antialiased",
|
||||||
fontSans.variable,
|
fontSans.variable,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,10 +3,15 @@ import { getAuthUrl, isGithubOAuthEnabled } from "@/lib/helper";
|
|||||||
import { isGoogleOAuthEnabled } from "@/lib/helper";
|
import { isGoogleOAuthEnabled } from "@/lib/helper";
|
||||||
import { SearchParamsProps } from "@/types";
|
import { SearchParamsProps } from "@/types";
|
||||||
|
|
||||||
const SignUp = ({ searchParams }: { searchParams: SearchParamsProps }) => {
|
const SignUp = async ({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: Promise<SearchParamsProps>;
|
||||||
|
}) => {
|
||||||
|
const resolvedSearchParams = await searchParams;
|
||||||
const invitationToken =
|
const invitationToken =
|
||||||
typeof searchParams?.invitation_token === "string"
|
typeof resolvedSearchParams?.invitation_token === "string"
|
||||||
? searchParams.invitation_token
|
? resolvedSearchParams.invitation_token
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const GOOGLE_AUTH_URL = getAuthUrl("google");
|
const GOOGLE_AUTH_URL = getAuthUrl("google");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
|
|
||||||
@@ -48,12 +48,12 @@ const ComplianceIconSmall = ({
|
|||||||
title: string;
|
title: string;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="relative h-6 w-6 flex-shrink-0">
|
<div className="relative h-6 w-6 shrink-0">
|
||||||
<Image
|
<Image
|
||||||
src={logoPath}
|
src={logoPath}
|
||||||
alt={`${title} logo`}
|
alt={`${title} logo`}
|
||||||
fill
|
fill
|
||||||
className="h-8 w-8 min-w-8 rounded-md border-1 border-gray-300 bg-white object-contain p-[2px]"
|
className="h-8 w-8 min-w-8 rounded-md border border-gray-300 bg-white object-contain p-[2px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -76,18 +76,19 @@ export default async function ComplianceDetail({
|
|||||||
params,
|
params,
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
params: { compliancetitle: string };
|
params: Promise<{ compliancetitle: string }>;
|
||||||
searchParams: ComplianceDetailSearchParams;
|
searchParams: Promise<ComplianceDetailSearchParams>;
|
||||||
}) {
|
}) {
|
||||||
const { compliancetitle } = params;
|
const { compliancetitle } = await params;
|
||||||
const { complianceId, version, scanId, scanData } = searchParams;
|
const resolvedSearchParams = await searchParams;
|
||||||
const regionFilter = searchParams["filter[region__in]"];
|
const { complianceId, version, scanId, scanData } = resolvedSearchParams;
|
||||||
const cisProfileFilter = searchParams["filter[cis_profile_level]"];
|
const regionFilter = resolvedSearchParams["filter[region__in]"];
|
||||||
|
const cisProfileFilter = resolvedSearchParams["filter[cis_profile_level]"];
|
||||||
const logoPath = getComplianceIcon(compliancetitle);
|
const logoPath = getComplianceIcon(compliancetitle);
|
||||||
|
|
||||||
// Create a key that excludes pagination parameters to preserve accordion state avoiding reloads with pagination
|
// Create a key that excludes pagination parameters to preserve accordion state avoiding reloads with pagination
|
||||||
const paramsForKey = Object.fromEntries(
|
const paramsForKey = Object.fromEntries(
|
||||||
Object.entries(searchParams).filter(
|
Object.entries(resolvedSearchParams).filter(
|
||||||
([key]) => key !== "page" && key !== "pageSize",
|
([key]) => key !== "page" && key !== "pageSize",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -153,7 +154,7 @@ export default async function ComplianceDetail({
|
|||||||
<Suspense
|
<Suspense
|
||||||
key={searchParamsKey}
|
key={searchParamsKey}
|
||||||
fallback={
|
fallback={
|
||||||
<div className="space-y-8">
|
<div className="flex flex-col gap-8">
|
||||||
<ChartsWrapper logoPath={logoPath}>
|
<ChartsWrapper logoPath={logoPath}>
|
||||||
<PieChartSkeleton />
|
<PieChartSkeleton />
|
||||||
<BarChartSkeleton />
|
<BarChartSkeleton />
|
||||||
@@ -200,7 +201,7 @@ const SSRComplianceContent = async ({
|
|||||||
|
|
||||||
if (!scanId || type === "tasks") {
|
if (!scanId || type === "tasks") {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="flex flex-col gap-8">
|
||||||
<ChartsWrapper logoPath={logoPath}>
|
<ChartsWrapper logoPath={logoPath}>
|
||||||
<PieChart pass={0} fail={0} manual={0} />
|
<PieChart pass={0} fail={0} manual={0} />
|
||||||
<BarChart sections={[]} />
|
<BarChart sections={[]} />
|
||||||
@@ -231,7 +232,7 @@ const SSRComplianceContent = async ({
|
|||||||
const topFailedSections = mapper.getTopFailedSections(data);
|
const topFailedSections = mapper.getTopFailedSections(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="flex flex-col gap-8">
|
||||||
<ChartsWrapper logoPath={logoPath}>
|
<ChartsWrapper logoPath={logoPath}>
|
||||||
<PieChart
|
<PieChart
|
||||||
pass={totalRequirements.pass}
|
pass={totalRequirements.pass}
|
||||||
|
|||||||
@@ -22,12 +22,15 @@ import { ComplianceOverviewData } from "@/types/compliance";
|
|||||||
export default async function Compliance({
|
export default async function Compliance({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
|
|
||||||
const filters = Object.fromEntries(
|
const filters = Object.fromEntries(
|
||||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||||
|
key.startsWith("filter["),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const scansData = await getScans({
|
const scansData = await getScans({
|
||||||
@@ -72,7 +75,7 @@ export default async function Compliance({
|
|||||||
.filter(Boolean) as ExpandedScanData[];
|
.filter(Boolean) as ExpandedScanData[];
|
||||||
|
|
||||||
const selectedScanId =
|
const selectedScanId =
|
||||||
searchParams.scanId || expandedScansData[0]?.id || null;
|
resolvedSearchParams.scanId || expandedScansData[0]?.id || null;
|
||||||
const query = (filters["filter[search]"] as string) || "";
|
const query = (filters["filter[search]"] as string) || "";
|
||||||
|
|
||||||
// Find the selected scan
|
// Find the selected scan
|
||||||
@@ -103,7 +106,7 @@ export default async function Compliance({
|
|||||||
const uniqueRegions = metadataInfoData?.data?.attributes?.regions || [];
|
const uniqueRegions = metadataInfoData?.data?.attributes?.regions || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Compliance" icon="fluent-mdl2:compliance-audit">
|
<ContentLayout title="Compliance" icon="lucide:shield-check">
|
||||||
{selectedScanId ? (
|
{selectedScanId ? (
|
||||||
<>
|
<>
|
||||||
<ComplianceHeader
|
<ComplianceHeader
|
||||||
@@ -112,7 +115,7 @@ export default async function Compliance({
|
|||||||
/>
|
/>
|
||||||
<Suspense key={searchParamsKey} fallback={<ComplianceSkeletonGrid />}>
|
<Suspense key={searchParamsKey} fallback={<ComplianceSkeletonGrid />}>
|
||||||
<SSRComplianceGrid
|
<SSRComplianceGrid
|
||||||
searchParams={searchParams}
|
searchParams={resolvedSearchParams}
|
||||||
selectedScan={selectedScanData}
|
selectedScan={selectedScanData}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@@ -163,7 +166,7 @@ const SSRComplianceGrid = async ({
|
|||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center">
|
<div className="flex h-full items-center">
|
||||||
<div className="text-sm text-default-500">
|
<div className="text-default-500 text-sm">
|
||||||
No compliance data available for the selected scan.
|
No compliance data available for the selected scan.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,7 +177,7 @@ const SSRComplianceGrid = async ({
|
|||||||
if (compliancesData?.errors?.length > 0) {
|
if (compliancesData?.errors?.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center">
|
<div className="flex h-full items-center">
|
||||||
<div className="text-sm text-default-500">Provide a valid scan ID.</div>
|
<div className="text-default-500 text-sm">Provide a valid scan ID.</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -33,13 +33,15 @@ import { FindingProps, SearchParamsProps } from "@/types/components";
|
|||||||
export default async function Findings({
|
export default async function Findings({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const { searchParamsKey, encodedSort } = extractSortAndKey(searchParams);
|
const resolvedSearchParams = await searchParams;
|
||||||
const { filters, query } = extractFiltersAndQuery(searchParams);
|
const { searchParamsKey, encodedSort } =
|
||||||
|
extractSortAndKey(resolvedSearchParams);
|
||||||
|
const { filters, query } = extractFiltersAndQuery(resolvedSearchParams);
|
||||||
|
|
||||||
// Check if the searchParams contain any date or scan filter
|
// Check if the searchParams contain any date or scan filter
|
||||||
const hasDateOrScan = hasDateOrScanFilter(searchParams);
|
const hasDateOrScan = hasDateOrScanFilter(resolvedSearchParams);
|
||||||
|
|
||||||
const [metadataInfoData, providersData, scansData] = await Promise.all([
|
const [metadataInfoData, providersData, scansData] = await Promise.all([
|
||||||
(hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo)({
|
(hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo)({
|
||||||
@@ -81,7 +83,7 @@ export default async function Findings({
|
|||||||
) as { [uid: string]: ScanEntity }[];
|
) as { [uid: string]: ScanEntity }[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Findings" icon="carbon:data-view-alt">
|
<ContentLayout title="Findings" icon="lucide:tag">
|
||||||
<FindingsFilters
|
<FindingsFilters
|
||||||
providerUIDs={providerUIDs}
|
providerUIDs={providerUIDs}
|
||||||
providerDetails={providerDetails}
|
providerDetails={providerDetails}
|
||||||
@@ -94,7 +96,7 @@ export default async function Findings({
|
|||||||
/>
|
/>
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableFindings />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonTableFindings />}>
|
||||||
<SSRDataTable searchParams={searchParams} />
|
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
@@ -157,12 +159,13 @@ const SSRDataTable = async ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{findingsData?.errors && (
|
{findingsData?.errors && (
|
||||||
<div className="mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-small text-red-700">
|
<div className="text-small mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-red-700">
|
||||||
<p className="mr-2 font-semibold">Error:</p>
|
<p className="mr-2 font-semibold">Error:</p>
|
||||||
<p>{findingsData.errors[0].detail}</p>
|
<p>{findingsData.errors[0].detail}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={Date.now()}
|
||||||
columns={ColumnFindings}
|
columns={ColumnFindings}
|
||||||
data={expandedResponse?.data || []}
|
data={expandedResponse?.data || []}
|
||||||
metadata={findingsData?.meta}
|
metadata={findingsData?.meta}
|
||||||
|
|||||||
@@ -6,19 +6,25 @@ import { S3IntegrationsManager } from "@/components/integrations/s3/s3-integrati
|
|||||||
import { ContentLayout } from "@/components/ui";
|
import { ContentLayout } from "@/components/ui";
|
||||||
|
|
||||||
interface S3IntegrationsProps {
|
interface S3IntegrationsProps {
|
||||||
searchParams: { [key: string]: string | string[] | undefined };
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function S3Integrations({
|
export default async function S3Integrations({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: S3IntegrationsProps) {
|
}: S3IntegrationsProps) {
|
||||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
const resolvedSearchParams = await searchParams;
|
||||||
const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10);
|
const page = parseInt(resolvedSearchParams.page?.toString() || "1", 10);
|
||||||
const sort = searchParams.sort?.toString();
|
const pageSize = parseInt(
|
||||||
|
resolvedSearchParams.pageSize?.toString() || "10",
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
const sort = resolvedSearchParams.sort?.toString();
|
||||||
|
|
||||||
// Extract all filter parameters
|
// Extract all filter parameters
|
||||||
const filters = Object.fromEntries(
|
const filters = Object.fromEntries(
|
||||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||||
|
key.startsWith("filter["),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const urlSearchParams = new URLSearchParams();
|
const urlSearchParams = new URLSearchParams();
|
||||||
@@ -49,8 +55,8 @@ export default async function S3Integrations({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Amazon S3">
|
<ContentLayout title="Amazon S3">
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
Configure Amazon S3 integration to automatically export your scan
|
Configure Amazon S3 integration to automatically export your scan
|
||||||
results to S3 buckets.
|
results to S3 buckets.
|
||||||
@@ -60,7 +66,7 @@ export default async function S3Integrations({
|
|||||||
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||||
Features:
|
Features:
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 dark:text-gray-300 md:grid-cols-2">
|
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 md:grid-cols-2 dark:text-gray-300">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||||
Automated scan result exports
|
Automated scan result exports
|
||||||
|
|||||||
@@ -6,18 +6,24 @@ import { SecurityHubIntegrationsManager } from "@/components/integrations/securi
|
|||||||
import { ContentLayout } from "@/components/ui";
|
import { ContentLayout } from "@/components/ui";
|
||||||
|
|
||||||
interface SecurityHubIntegrationsProps {
|
interface SecurityHubIntegrationsProps {
|
||||||
searchParams: { [key: string]: string | string[] | undefined };
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function SecurityHubIntegrations({
|
export default async function SecurityHubIntegrations({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: SecurityHubIntegrationsProps) {
|
}: SecurityHubIntegrationsProps) {
|
||||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
const resolvedSearchParams = await searchParams;
|
||||||
const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10);
|
const page = parseInt(resolvedSearchParams.page?.toString() || "1", 10);
|
||||||
const sort = searchParams.sort?.toString();
|
const pageSize = parseInt(
|
||||||
|
resolvedSearchParams.pageSize?.toString() || "10",
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
const sort = resolvedSearchParams.sort?.toString();
|
||||||
|
|
||||||
const filters = Object.fromEntries(
|
const filters = Object.fromEntries(
|
||||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||||
|
key.startsWith("filter["),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const urlSearchParams = new URLSearchParams();
|
const urlSearchParams = new URLSearchParams();
|
||||||
@@ -47,8 +53,8 @@ export default async function SecurityHubIntegrations({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="AWS Security Hub">
|
<ContentLayout title="AWS Security Hub">
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
Configure AWS Security Hub integration to automatically send your
|
Configure AWS Security Hub integration to automatically send your
|
||||||
security findings for centralized monitoring and compliance.
|
security findings for centralized monitoring and compliance.
|
||||||
@@ -58,7 +64,7 @@ export default async function SecurityHubIntegrations({
|
|||||||
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||||
Features:
|
Features:
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 dark:text-gray-300 md:grid-cols-2">
|
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 md:grid-cols-2 dark:text-gray-300">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||||
Automated findings export
|
Automated findings export
|
||||||
|
|||||||
@@ -3,19 +3,25 @@ import { JiraIntegrationsManager } from "@/components/integrations/jira/jira-int
|
|||||||
import { ContentLayout } from "@/components/ui";
|
import { ContentLayout } from "@/components/ui";
|
||||||
|
|
||||||
interface JiraIntegrationsProps {
|
interface JiraIntegrationsProps {
|
||||||
searchParams: { [key: string]: string | string[] | undefined };
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function JiraIntegrations({
|
export default async function JiraIntegrations({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: JiraIntegrationsProps) {
|
}: JiraIntegrationsProps) {
|
||||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
const resolvedSearchParams = await searchParams;
|
||||||
const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10);
|
const page = parseInt(resolvedSearchParams.page?.toString() || "1", 10);
|
||||||
const sort = searchParams.sort?.toString();
|
const pageSize = parseInt(
|
||||||
|
resolvedSearchParams.pageSize?.toString() || "10",
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
const sort = resolvedSearchParams.sort?.toString();
|
||||||
|
|
||||||
// Extract all filter parameters
|
// Extract all filter parameters
|
||||||
const filters = Object.fromEntries(
|
const filters = Object.fromEntries(
|
||||||
Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")),
|
Object.entries(resolvedSearchParams).filter(([key]) =>
|
||||||
|
key.startsWith("filter["),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const urlSearchParams = new URLSearchParams();
|
const urlSearchParams = new URLSearchParams();
|
||||||
@@ -42,8 +48,8 @@ export default async function JiraIntegrations({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Jira">
|
<ContentLayout title="Jira">
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
Configure Jira integration to automatically create issues for
|
Configure Jira integration to automatically create issues for
|
||||||
security findings in your Jira projects.
|
security findings in your Jira projects.
|
||||||
@@ -53,7 +59,7 @@ export default async function JiraIntegrations({
|
|||||||
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
<h3 className="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||||
Features:
|
Features:
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 dark:text-gray-300 md:grid-cols-2">
|
<ul className="grid grid-cols-1 gap-2 text-sm text-gray-600 md:grid-cols-2 dark:text-gray-300">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
|
||||||
Automated issue creation
|
Automated issue creation
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import { ContentLayout } from "@/components/ui";
|
|||||||
export default async function Integrations() {
|
export default async function Integrations() {
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Integrations" icon="lucide:puzzle">
|
<ContentLayout title="Integrations" icon="lucide:puzzle">
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
Connect external services to enhance your security workflow and
|
Connect external services to enhance your security workflow and
|
||||||
automatically export your scan results.
|
automatically export your scan results.
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import { SearchParamsProps } from "@/types";
|
|||||||
export default async function CheckDetailsPage({
|
export default async function CheckDetailsPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonInvitationInfo />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonInvitationInfo />}>
|
||||||
<SSRDataInvitation searchParams={searchParams} />
|
<SSRDataInvitation searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { WorkflowSendInvite } from "@/components/invitations/workflow";
|
import { WorkflowSendInvite } from "@/components/invitations/workflow";
|
||||||
@@ -19,8 +19,8 @@ export default function InvitationLayout({ children }: InvitationLayoutProps) {
|
|||||||
href="/invitations"
|
href="/invitations"
|
||||||
/>
|
/>
|
||||||
<Spacer y={16} />
|
<Spacer y={16} />
|
||||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-12">
|
<div className="grid grid-cols-1 gap-8 px-4 lg:grid-cols-12 lg:px-0">
|
||||||
<div className="order-1 my-auto hidden h-full lg:col-span-4 lg:col-start-2 lg:block">
|
<div className="order-1 my-auto h-full lg:col-span-4 lg:col-start-2">
|
||||||
<WorkflowSendInvite />
|
<WorkflowSendInvite />
|
||||||
</div>
|
</div>
|
||||||
<div className="order-2 my-auto lg:col-span-5 lg:col-start-6">
|
<div className="order-2 my-auto lg:col-span-5 lg:col-start-6">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
|
|
||||||
import { getInvitations } from "@/actions/invitations/invitation";
|
import { getInvitations } from "@/actions/invitations/invitation";
|
||||||
@@ -17,12 +17,13 @@ import { InvitationProps, Role, SearchParamsProps } from "@/types";
|
|||||||
export default async function Invitations({
|
export default async function Invitations({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Invitations" icon="ci:users">
|
<ContentLayout title="Invitations" icon="lucide:mail">
|
||||||
<FilterControls search />
|
<FilterControls search />
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<SendInvitationButton />
|
<SendInvitationButton />
|
||||||
@@ -31,7 +32,7 @@ export default async function Invitations({
|
|||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
|
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableInvitation />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonTableInvitation />}>
|
||||||
<SSRDataTable searchParams={searchParams} />
|
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
@@ -110,6 +111,7 @@ const SSRDataTable = async ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={Date.now()}
|
||||||
columns={ColumnsInvitation}
|
columns={ColumnsInvitation}
|
||||||
data={expandedResponse?.data || []}
|
data={expandedResponse?.data || []}
|
||||||
metadata={invitationsData?.meta}
|
metadata={invitationsData?.meta}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default async function RootLayout({
|
|||||||
<body
|
<body
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
className={cn(
|
className={cn(
|
||||||
"min-h-screen bg-background font-sans antialiased",
|
"bg-background min-h-screen font-sans antialiased",
|
||||||
fontSans.variable,
|
fontSans.variable,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ interface ProviderLayoutProps {
|
|||||||
|
|
||||||
export default function ProviderLayout({ children }: ProviderLayoutProps) {
|
export default function ProviderLayout({ children }: ProviderLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<ContentLayout
|
<ContentLayout title="Manage Groups" icon="lucide:group">
|
||||||
title="Manage Groups"
|
|
||||||
icon="solar:users-group-two-rounded-outline"
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Divider, Spacer } from "@nextui-org/react";
|
import { Divider } from "@heroui/divider";
|
||||||
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
|
|
||||||
@@ -15,26 +16,27 @@ import { ColumnGroups } from "@/components/manage-groups/table";
|
|||||||
import { DataTable } from "@/components/ui/table";
|
import { DataTable } from "@/components/ui/table";
|
||||||
import { ProviderProps, Role, SearchParamsProps } from "@/types";
|
import { ProviderProps, Role, SearchParamsProps } from "@/types";
|
||||||
|
|
||||||
export default function ManageGroupsPage({
|
export default async function ManageGroupsPage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams);
|
const resolvedSearchParams = await searchParams;
|
||||||
const providerGroupId = searchParams.groupId;
|
const searchParamsKey = JSON.stringify(resolvedSearchParams);
|
||||||
|
const providerGroupId = resolvedSearchParams.groupId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid min-h-[70vh] grid-cols-1 items-center justify-center gap-4 md:grid-cols-12">
|
<div className="grid min-h-[70vh] grid-cols-1 items-center justify-center gap-4 md:grid-cols-12">
|
||||||
<div className="col-span-1 flex justify-end md:col-span-4">
|
<div className="col-span-1 flex justify-end md:col-span-4">
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonManageGroups />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonManageGroups />}>
|
||||||
{providerGroupId ? (
|
{providerGroupId ? (
|
||||||
<SSRDataEditGroup searchParams={searchParams} />
|
<SSRDataEditGroup searchParams={resolvedSearchParams} />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h1 className="mb-2 text-xl font-medium" id="getting-started">
|
<h1 className="mb-2 text-xl font-medium" id="getting-started">
|
||||||
Create a new provider group
|
Create a new provider group
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mb-5 text-small text-default-500">
|
<p className="text-small text-default-500 mb-5">
|
||||||
Create a new provider group to manage the providers and roles.
|
Create a new provider group to manage the providers and roles.
|
||||||
</p>
|
</p>
|
||||||
<SSRAddGroupForm />
|
<SSRAddGroupForm />
|
||||||
@@ -50,7 +52,7 @@ export default function ManageGroupsPage({
|
|||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<h3 className="mb-4 text-sm font-bold uppercase">Provider Groups</h3>
|
<h3 className="mb-4 text-sm font-bold uppercase">Provider Groups</h3>
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonManageGroups />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonManageGroups />}>
|
||||||
<SSRDataTable searchParams={searchParams} />
|
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -143,7 +145,7 @@ const SSRDataEditGroup = async ({
|
|||||||
<h1 className="mb-2 text-xl font-medium" id="getting-started">
|
<h1 className="mb-2 text-xl font-medium" id="getting-started">
|
||||||
Edit provider group
|
Edit provider group
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mb-5 text-small text-default-500">
|
<p className="text-small text-default-500 mb-5">
|
||||||
Edit the provider group to manage the providers and roles.
|
Edit the provider group to manage the providers and roles.
|
||||||
</p>
|
</p>
|
||||||
<EditGroupForm
|
<EditGroupForm
|
||||||
@@ -181,11 +183,15 @@ const SSRDataTable = async ({
|
|||||||
filters,
|
filters,
|
||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<>
|
||||||
columns={ColumnGroups}
|
<DataTable
|
||||||
data={providerGroupsData?.data || []}
|
key={`groups-${Date.now()}`}
|
||||||
metadata={providerGroupsData?.meta}
|
columns={ColumnGroups}
|
||||||
/>
|
data={providerGroupsData?.data || []}
|
||||||
|
metadata={providerGroupsData?.meta}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
import { getLatestFindings } from "@/actions/findings/findings";
|
import { getLatestFindings } from "@/actions/findings/findings";
|
||||||
@@ -37,14 +37,15 @@ function pickFilterParams(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Home({
|
export default async function Home({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Overview" icon="solar:pie-chart-2-outline">
|
<ContentLayout title="Overview" icon="lucide:square-chart-gantt">
|
||||||
<FilterControls providers mutedFindings showClearButton={false} />
|
<FilterControls providers mutedFindings showClearButton={false} />
|
||||||
|
|
||||||
<div className="grid grid-cols-12 gap-12 lg:gap-6">
|
<div className="grid grid-cols-12 gap-12 lg:gap-6">
|
||||||
@@ -56,13 +57,13 @@ export default function Home({
|
|||||||
|
|
||||||
<div className="col-span-12 lg:col-span-4">
|
<div className="col-span-12 lg:col-span-4">
|
||||||
<Suspense fallback={<SkeletonFindingsBySeverityChart />}>
|
<Suspense fallback={<SkeletonFindingsBySeverityChart />}>
|
||||||
<SSRFindingsBySeverity searchParams={searchParams} />
|
<SSRFindingsBySeverity searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-12 lg:col-span-4">
|
<div className="col-span-12 lg:col-span-4">
|
||||||
<Suspense fallback={<SkeletonFindingsByStatusChart />}>
|
<Suspense fallback={<SkeletonFindingsByStatusChart />}>
|
||||||
<SSRFindingsByStatus searchParams={searchParams} />
|
<SSRFindingsByStatus searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ export default function Home({
|
|||||||
key={searchParamsKey}
|
key={searchParamsKey}
|
||||||
fallback={<SkeletonTableNewFindings />}
|
fallback={<SkeletonTableNewFindings />}
|
||||||
>
|
>
|
||||||
<SSRDataNewFindingsTable searchParams={searchParams} />
|
<SSRDataNewFindingsTable searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,6 +206,7 @@ const SSRDataNewFindingsTable = async ({
|
|||||||
<LighthouseBanner />
|
<LighthouseBanner />
|
||||||
|
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={`dashboard-${Date.now()}`}
|
||||||
columns={ColumnNewFindingsToDate}
|
columns={ColumnNewFindingsToDate}
|
||||||
data={expandedResponse?.data || []}
|
data={expandedResponse?.data || []}
|
||||||
// metadata={findingsData?.meta}
|
// metadata={findingsData?.meta}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
|
|
||||||
export default async function Profile() {
|
export default async function Profile() {
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="User Profile" icon="ci:users">
|
<ContentLayout title="User Profile" icon="lucide:users">
|
||||||
<Suspense fallback={<SkeletonUserInfo />}>
|
<Suspense fallback={<SkeletonUserInfo />}>
|
||||||
<SSRDataUser />
|
<SSRDataUser />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@@ -96,10 +96,10 @@ const SSRDataUser = async () => {
|
|||||||
<div className="flex w-full flex-col gap-6">
|
<div className="flex w-full flex-col gap-6">
|
||||||
<UserBasicInfoCard user={userData} tenantId={userTenantId || ""} />
|
<UserBasicInfoCard user={userData} tenantId={userTenantId || ""} />
|
||||||
<div className="flex flex-col gap-6 xl:flex-row">
|
<div className="flex flex-col gap-6 xl:flex-row">
|
||||||
<div className="w-full lg:w-2/3 xl:w-1/2">
|
<div className="w-full">
|
||||||
<RolesCard roles={roleDetails} roleDetails={roleDetailsMap} />
|
<RolesCard roles={roleDetails} roleDetails={roleDetailsMap} />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full lg:w-2/3 xl:w-1/2">
|
<div className="w-full">
|
||||||
<MembershipsCard
|
<MembershipsCard
|
||||||
memberships={membershipsIncluded}
|
memberships={membershipsIncluded}
|
||||||
tenantsMap={tenantsMap}
|
tenantsMap={tenantsMap}
|
||||||
@@ -108,7 +108,7 @@ const SSRDataUser = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{hasManageIntegrations && (
|
{hasManageIntegrations && (
|
||||||
<div className="w-full pr-0 lg:w-2/3 xl:w-1/2 xl:pr-3">
|
<div className="w-full pr-0 xl:w-1/2 xl:pr-3">
|
||||||
<SamlIntegrationCard samlConfig={samlConfig?.data?.[0]} />
|
<SamlIntegrationCard samlConfig={samlConfig?.data?.[0]} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ import { getProviderFormType } from "@/lib/provider-helpers";
|
|||||||
import { ProviderType } from "@/types/providers";
|
import { ProviderType } from "@/types/providers";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchParams: { type: ProviderType; id: string; via?: string };
|
searchParams: Promise<{ type: ProviderType; id: string; via?: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AddCredentialsPage({ searchParams }: Props) {
|
export default async function AddCredentialsPage({ searchParams }: Props) {
|
||||||
const { type: providerType, via } = searchParams;
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const { type: providerType, via } = resolvedSearchParams;
|
||||||
const formType = getProviderFormType(providerType, via);
|
const formType = getProviderFormType(providerType, via);
|
||||||
|
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
@@ -30,13 +31,13 @@ export default function AddCredentialsPage({ searchParams }: Props) {
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
case "credentials":
|
case "credentials":
|
||||||
return <AddViaCredentialsForm searchParams={searchParams} />;
|
return <AddViaCredentialsForm searchParams={resolvedSearchParams} />;
|
||||||
|
|
||||||
case "role":
|
case "role":
|
||||||
return <AddViaRoleForm searchParams={searchParams} />;
|
return <AddViaRoleForm searchParams={resolvedSearchParams} />;
|
||||||
|
|
||||||
case "service-account":
|
case "service-account":
|
||||||
return <AddViaServiceAccountForm searchParams={searchParams} />;
|
return <AddViaServiceAccountForm searchParams={resolvedSearchParams} />;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { WorkflowAddProvider } from "@/components/providers/workflow";
|
import { WorkflowAddProvider } from "@/components/providers/workflow";
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import { SkeletonProviderWorkflow } from "@/components/providers/workflow";
|
|||||||
import { TestConnectionForm } from "@/components/providers/workflow/forms";
|
import { TestConnectionForm } from "@/components/providers/workflow/forms";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchParams: { type: string; id: string; updated: string };
|
searchParams: Promise<{ type: string; id: string; updated: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function TestConnectionPage({ searchParams }: Props) {
|
export default async function TestConnectionPage({ searchParams }: Props) {
|
||||||
const providerId = searchParams.id;
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const providerId = resolvedSearchParams.id;
|
||||||
|
|
||||||
if (!providerId) {
|
if (!providerId) {
|
||||||
redirect("/providers/connect-account");
|
redirect("/providers/connect-account");
|
||||||
@@ -18,7 +19,7 @@ export default async function TestConnectionPage({ searchParams }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<SkeletonProviderWorkflow />}>
|
<Suspense fallback={<SkeletonProviderWorkflow />}>
|
||||||
<SSRTestConnection searchParams={searchParams} />
|
<SSRTestConnection searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,17 @@ import { getProviderFormType } from "@/lib/provider-helpers";
|
|||||||
import { ProviderType } from "@/types/providers";
|
import { ProviderType } from "@/types/providers";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchParams: {
|
searchParams: Promise<{
|
||||||
type: ProviderType;
|
type: ProviderType;
|
||||||
id: string;
|
id: string;
|
||||||
via?: string;
|
via?: string;
|
||||||
secretId?: string;
|
secretId?: string;
|
||||||
};
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UpdateCredentialsPage({ searchParams }: Props) {
|
export default async function UpdateCredentialsPage({ searchParams }: Props) {
|
||||||
const { type: providerType, via } = searchParams;
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const { type: providerType, via } = resolvedSearchParams;
|
||||||
const formType = getProviderFormType(providerType, via);
|
const formType = getProviderFormType(providerType, via);
|
||||||
|
|
||||||
switch (formType) {
|
switch (formType) {
|
||||||
@@ -29,13 +30,15 @@ export default function UpdateCredentialsPage({ searchParams }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case "credentials":
|
case "credentials":
|
||||||
return <UpdateViaCredentialsForm searchParams={searchParams} />;
|
return <UpdateViaCredentialsForm searchParams={resolvedSearchParams} />;
|
||||||
|
|
||||||
case "role":
|
case "role":
|
||||||
return <UpdateViaRoleForm searchParams={searchParams} />;
|
return <UpdateViaRoleForm searchParams={resolvedSearchParams} />;
|
||||||
|
|
||||||
case "service-account":
|
case "service-account":
|
||||||
return <UpdateViaServiceAccountForm searchParams={searchParams} />;
|
return (
|
||||||
|
<UpdateViaServiceAccountForm searchParams={resolvedSearchParams} />
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
import { getProviders } from "@/actions/providers";
|
import { getProviders } from "@/actions/providers";
|
||||||
@@ -19,12 +19,13 @@ import { ProviderProps, SearchParamsProps } from "@/types";
|
|||||||
export default async function Providers({
|
export default async function Providers({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Cloud Providers" icon="fluent:cloud-sync-24-regular">
|
<ContentLayout title="Cloud Providers" icon="lucide:cloud-cog">
|
||||||
<FilterControls search customFilters={filterProviders || []} />
|
<FilterControls search customFilters={filterProviders || []} />
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<Suspense
|
<Suspense
|
||||||
@@ -45,7 +46,7 @@ export default async function Providers({
|
|||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ProvidersContent searchParams={searchParams} />
|
<ProvidersContent searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
@@ -106,6 +107,7 @@ const ProvidersContent = async ({
|
|||||||
<div className="grid grid-cols-12 gap-4">
|
<div className="grid grid-cols-12 gap-4">
|
||||||
<div className="col-span-12">
|
<div className="col-span-12">
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={`providers-${Date.now()}`}
|
||||||
columns={ColumnProviders}
|
columns={ColumnProviders}
|
||||||
data={enrichedProviders || []}
|
data={enrichedProviders || []}
|
||||||
metadata={providersData?.meta}
|
metadata={providersData?.meta}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -24,14 +24,16 @@ import { ResourceProps, SearchParamsProps } from "@/types";
|
|||||||
export default async function Resources({
|
export default async function Resources({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const { searchParamsKey, encodedSort } = extractSortAndKey(searchParams);
|
const resolvedSearchParams = await searchParams;
|
||||||
const { filters, query } = extractFiltersAndQuery(searchParams);
|
const { searchParamsKey, encodedSort } =
|
||||||
|
extractSortAndKey(resolvedSearchParams);
|
||||||
|
const { filters, query } = extractFiltersAndQuery(resolvedSearchParams);
|
||||||
const outputFilters = replaceFieldKey(filters, "inserted_at", "updated_at");
|
const outputFilters = replaceFieldKey(filters, "inserted_at", "updated_at");
|
||||||
|
|
||||||
// Check if the searchParams contain any date or scan filter
|
// Check if the searchParams contain any date or scan filter
|
||||||
const hasDateOrScan = hasDateOrScanFilter(searchParams);
|
const hasDateOrScan = hasDateOrScanFilter(resolvedSearchParams);
|
||||||
|
|
||||||
const metadataInfoData = await (
|
const metadataInfoData = await (
|
||||||
hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo
|
hasDateOrScan ? getMetadataInfo : getLatestMetadataInfo
|
||||||
@@ -47,7 +49,7 @@ export default async function Resources({
|
|||||||
const uniqueResourceTypes = metadataInfoData?.data?.attributes?.types || [];
|
const uniqueResourceTypes = metadataInfoData?.data?.attributes?.types || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Resources" icon="carbon:data-view">
|
<ContentLayout title="Resources" icon="lucide:warehouse">
|
||||||
<FilterControls search date />
|
<FilterControls search date />
|
||||||
<DataTableFilterCustom
|
<DataTableFilterCustom
|
||||||
filters={[
|
filters={[
|
||||||
@@ -71,7 +73,7 @@ export default async function Resources({
|
|||||||
/>
|
/>
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableResources />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonTableResources />}>
|
||||||
<SSRDataTable searchParams={searchParams} />
|
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
@@ -140,12 +142,13 @@ const SSRDataTable = async ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{resourcesData?.errors && (
|
{resourcesData?.errors && (
|
||||||
<div className="mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-small text-red-700">
|
<div className="text-small mb-4 flex rounded-lg border border-red-500 bg-red-100 p-2 text-red-700">
|
||||||
<p className="mr-2 font-semibold">Error:</p>
|
<p className="mr-2 font-semibold">Error:</p>
|
||||||
<p>{resourcesData.errors[0].detail}</p>
|
<p>{resourcesData.errors[0].detail}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={`resources-${Date.now()}`}
|
||||||
columns={ColumnResources}
|
columns={ColumnResources}
|
||||||
data={expandedResources || []}
|
data={expandedResources || []}
|
||||||
metadata={resourcesData?.meta}
|
metadata={resourcesData?.meta}
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import { SearchParamsProps } from "@/types";
|
|||||||
export default async function EditRolePage({
|
export default async function EditRolePage({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonRoleForm />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonRoleForm />}>
|
||||||
<SSRDataRole searchParams={searchParams} />
|
<SSRDataRole searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { WorkflowAddEditRole } from "@/components/roles/workflow";
|
import { WorkflowAddEditRole } from "@/components/roles/workflow";
|
||||||
@@ -19,7 +19,7 @@ export default function RoleLayout({ children }: RoleLayoutProps) {
|
|||||||
href="/roles"
|
href="/roles"
|
||||||
/>
|
/>
|
||||||
<Spacer y={16} />
|
<Spacer y={16} />
|
||||||
<div className="grid grid-cols-1 gap-8 lg:grid-cols-12">
|
<div className="grid grid-cols-1 gap-8 px-4 sm:px-6 lg:grid-cols-12 lg:px-0">
|
||||||
<div className="order-1 my-auto hidden h-full lg:col-span-4 lg:col-start-2 lg:block">
|
<div className="order-1 my-auto hidden h-full lg:col-span-4 lg:col-start-2 lg:block">
|
||||||
<WorkflowAddEditRole />
|
<WorkflowAddEditRole />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
import { getRoles } from "@/actions/roles";
|
import { getRoles } from "@/actions/roles";
|
||||||
@@ -14,12 +14,13 @@ import { SearchParamsProps } from "@/types";
|
|||||||
export default async function Roles({
|
export default async function Roles({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Roles" icon="mdi:account-key-outline">
|
<ContentLayout title="Roles" icon="lucide:user-cog">
|
||||||
<FilterControls search />
|
<FilterControls search />
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<AddRoleButton />
|
<AddRoleButton />
|
||||||
@@ -28,7 +29,7 @@ export default async function Roles({
|
|||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
|
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableRoles />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonTableRoles />}>
|
||||||
<SSRDataTable searchParams={searchParams} />
|
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
@@ -55,6 +56,7 @@ const SSRDataTable = async ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={`roles-${Date.now()}`}
|
||||||
columns={ColumnsRoles}
|
columns={ColumnsRoles}
|
||||||
data={rolesData?.data || []}
|
data={rolesData?.data || []}
|
||||||
metadata={rolesData?.meta}
|
metadata={rolesData?.meta}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
import { getProviders } from "@/actions/providers";
|
import { getProviders } from "@/actions/providers";
|
||||||
@@ -26,12 +26,12 @@ import { ProviderProps, ScanProps, SearchParamsProps } from "@/types";
|
|||||||
export default async function Scans({
|
export default async function Scans({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
const filteredParams = { ...searchParams };
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const filteredParams = { ...resolvedSearchParams };
|
||||||
delete filteredParams.scanId;
|
delete filteredParams.scanId;
|
||||||
const searchParamsKey = JSON.stringify(filteredParams);
|
|
||||||
|
|
||||||
const providersData = await getProviders({
|
const providersData = await getProviders({
|
||||||
pageSize: 50,
|
pageSize: 50,
|
||||||
@@ -77,14 +77,14 @@ export default async function Scans({
|
|||||||
|
|
||||||
if (thereIsNoProviders) {
|
if (thereIsNoProviders) {
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Scans" icon="lucide:scan-search">
|
<ContentLayout title="Scans" icon="lucide:timer">
|
||||||
<NoProvidersAdded />
|
<NoProvidersAdded />
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Scans" icon="lucide:scan-search">
|
<ContentLayout title="Scans" icon="lucide:timer">
|
||||||
<AutoRefresh hasExecutingScan={hasExecutingScan} />
|
<AutoRefresh hasExecutingScan={hasExecutingScan} />
|
||||||
<>
|
<>
|
||||||
{!hasManageScansPermission ? (
|
{!hasManageScansPermission ? (
|
||||||
@@ -111,8 +111,8 @@ export default async function Scans({
|
|||||||
<MutedFindingsConfigButton />
|
<MutedFindingsConfigButton />
|
||||||
</div>
|
</div>
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableScans />}>
|
<Suspense fallback={<SkeletonTableScans />}>
|
||||||
<SSRDataTableScans searchParams={searchParams} />
|
<SSRDataTableScans searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
@@ -178,6 +178,7 @@ const SSRDataTableScans = async ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={`scans-${Date.now()}`}
|
||||||
columns={ColumnGetScans}
|
columns={ColumnGetScans}
|
||||||
data={expandedScansData || []}
|
data={expandedScansData || []}
|
||||||
metadata={scansData?.meta}
|
metadata={scansData?.meta}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
|
|
||||||
import { FilterControls } from "@/components/filters";
|
import { FilterControls } from "@/components/filters";
|
||||||
import { ContentLayout } from "@/components/ui";
|
import { ContentLayout } from "@/components/ui";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
|
||||||
import { getUsers } from "@/actions/users/users";
|
import { getUsers } from "@/actions/users/users";
|
||||||
@@ -13,12 +13,13 @@ import { Role, SearchParamsProps, UserProps } from "@/types";
|
|||||||
export default async function Users({
|
export default async function Users({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: SearchParamsProps;
|
searchParams: Promise<SearchParamsProps>;
|
||||||
}) {
|
}) {
|
||||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
const resolvedSearchParams = await searchParams;
|
||||||
|
const searchParamsKey = JSON.stringify(resolvedSearchParams || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentLayout title="Users" icon="ci:users">
|
<ContentLayout title="Users" icon="lucide:user">
|
||||||
<FilterControls search />
|
<FilterControls search />
|
||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
<AddUserButton />
|
<AddUserButton />
|
||||||
@@ -27,7 +28,7 @@ export default async function Users({
|
|||||||
<Spacer y={8} />
|
<Spacer y={8} />
|
||||||
|
|
||||||
<Suspense key={searchParamsKey} fallback={<SkeletonTableUser />}>
|
<Suspense key={searchParamsKey} fallback={<SkeletonTableUser />}>
|
||||||
<SSRDataTable searchParams={searchParams} />
|
<SSRDataTable searchParams={resolvedSearchParams} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ContentLayout>
|
</ContentLayout>
|
||||||
);
|
);
|
||||||
@@ -91,6 +92,7 @@ const SSRDataTable = async ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={`scans-${Date.now()}`}
|
||||||
columns={ColumnsUser}
|
columns={ColumnsUser}
|
||||||
data={expandedUsers || []}
|
data={expandedUsers || []}
|
||||||
metadata={usersData?.meta}
|
metadata={usersData?.meta}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { NextUIProvider } from "@nextui-org/system";
|
import { HeroUIProvider } from "@heroui/system";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||||
@@ -17,9 +17,9 @@ export function Providers({ children, themeProps }: ProvidersProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<NextUIProvider navigate={router.push}>
|
<HeroUIProvider navigate={router.push}>
|
||||||
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
<NextThemesProvider {...themeProps}>{children}</NextThemesProvider>
|
||||||
</NextUIProvider>
|
</HeroUIProvider>
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,12 +32,13 @@ const refreshAccessToken = async (token: JwtPayload) => {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(bodyData),
|
body: JSON.stringify(bodyData),
|
||||||
});
|
});
|
||||||
const newTokens = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newTokens = await response.json();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...token,
|
...token,
|
||||||
accessToken: newTokens.data.attributes.access,
|
accessToken: newTokens.data.attributes.access,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { SwitchProps, useSwitch } from "@nextui-org/react";
|
import type { SwitchProps } from "@heroui/switch";
|
||||||
|
import { useSwitch } from "@heroui/switch";
|
||||||
import { useIsSSR } from "@react-aria/ssr";
|
import { useIsSSR } from "@react-aria/ssr";
|
||||||
import { VisuallyHidden } from "@react-aria/visually-hidden";
|
import { VisuallyHidden } from "@react-aria/visually-hidden";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@heroui/button";
|
||||||
|
import { Checkbox } from "@heroui/checkbox";
|
||||||
|
import { Divider } from "@heroui/divider";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { Button, Checkbox, Divider } from "@nextui-org/react";
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
@@ -190,9 +192,9 @@ export const AuthForm = ({
|
|||||||
{/* Auth Form */}
|
{/* Auth Form */}
|
||||||
<div className="relative flex w-full items-center justify-center lg:w-full">
|
<div className="relative flex w-full items-center justify-center lg:w-full">
|
||||||
{/* Background Pattern */}
|
{/* Background Pattern */}
|
||||||
<div className="absolute h-full w-full bg-[radial-gradient(#6af400_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_10%,transparent_80%)]"></div>
|
<div className="absolute h-full w-full bg-[radial-gradient(#6af400_1px,transparent_1px)] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_10%,transparent_80%)] bg-size-[16px_16px]"></div>
|
||||||
|
|
||||||
<div className="relative z-10 flex w-full max-w-sm flex-col gap-4 rounded-large border-1 border-divider bg-white/90 px-8 py-10 shadow-small dark:bg-background/85 md:max-w-md">
|
<div className="rounded-large border-divider shadow-small dark:bg-background/85 relative z-10 flex w-full max-w-sm flex-col gap-4 border bg-white/90 px-8 py-10 md:max-w-md">
|
||||||
{/* Prowler Logo */}
|
{/* Prowler Logo */}
|
||||||
<div className="absolute -top-[100px] left-1/2 z-10 flex h-fit w-fit -translate-x-1/2">
|
<div className="absolute -top-[100px] left-1/2 z-10 flex h-fit w-fit -translate-x-1/2">
|
||||||
<ProwlerExtended width={300} />
|
<ProwlerExtended width={300} />
|
||||||
@@ -350,7 +352,7 @@ export const AuthForm = ({
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-4 py-2">
|
<div className="flex items-center gap-4 py-2">
|
||||||
<Divider className="flex-1" />
|
<Divider className="flex-1" />
|
||||||
<p className="shrink-0 text-tiny text-default-500">OR</p>
|
<p className="text-tiny text-default-500 shrink-0">OR</p>
|
||||||
<Divider className="flex-1" />
|
<Divider className="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -387,7 +389,7 @@ export const AuthForm = ({
|
|||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-4 py-2">
|
<div className="flex items-center gap-4 py-2">
|
||||||
<Divider className="flex-1" />
|
<Divider className="flex-1" />
|
||||||
<p className="shrink-0 text-tiny text-default-500">OR</p>
|
<p className="text-tiny text-default-500 shrink-0">OR</p>
|
||||||
<Divider className="flex-1" />
|
<Divider className="flex-1" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
@@ -401,14 +403,14 @@ export const AuthForm = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{type === "sign-in" ? (
|
{type === "sign-in" ? (
|
||||||
<p className="text-center text-small">
|
<p className="text-small text-center">
|
||||||
Need to create an account?
|
Need to create an account?
|
||||||
<CustomLink size="base" href="/sign-up" target="_self">
|
<CustomLink size="base" href="/sign-up" target="_self">
|
||||||
Sign up
|
Sign up
|
||||||
</CustomLink>
|
</CustomLink>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-center text-small">
|
<p className="text-small text-center">
|
||||||
Already have an account?
|
Already have an account?
|
||||||
<CustomLink size="base" href="/sign-in" target="_self">
|
<CustomLink size="base" href="/sign-in" target="_self">
|
||||||
Log in
|
Log in
|
||||||
|
|||||||
@@ -69,25 +69,28 @@ export const PasswordRequirementsMessage = ({
|
|||||||
{allRequirementsMet ? (
|
{allRequirementsMet ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCircle
|
<CheckCircle
|
||||||
className="h-4 w-4 flex-shrink-0 text-system-success"
|
className="text-system-success h-4 w-4 shrink-0"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs font-medium leading-tight text-system-success">
|
<p className="text-system-success text-xs leading-tight font-medium">
|
||||||
Password meets all requirements
|
Password meets all requirements
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<AlertCircle
|
<AlertCircle
|
||||||
className="h-4 w-4 flex-shrink-0 text-red-600"
|
className="h-4 w-4 shrink-0 text-red-600"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs font-medium leading-tight text-red-700">
|
<p className="text-xs leading-tight font-medium text-red-700">
|
||||||
Password must include:
|
Password must include:
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ul className="ml-6 space-y-0.5" aria-label="Password requirements">
|
<ul
|
||||||
|
className="ml-6 flex flex-col gap-0.5"
|
||||||
|
aria-label="Password requirements"
|
||||||
|
>
|
||||||
{results.map((req) => (
|
{results.map((req) => (
|
||||||
<li
|
<li
|
||||||
key={req.key}
|
key={req.key}
|
||||||
@@ -95,7 +98,7 @@ export const PasswordRequirementsMessage = ({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`h-2 w-2 flex-shrink-0 rounded-full ${
|
className={`h-2 w-2 shrink-0 rounded-full ${
|
||||||
req.isMet ? "bg-system-success" : "bg-red-400"
|
req.isMet ? "bg-system-success" : "bg-red-400"
|
||||||
}`}
|
}`}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { Button } from "@heroui/button";
|
||||||
|
import { Tooltip } from "@heroui/tooltip";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import { Button, Tooltip } from "@nextui-org/react";
|
|
||||||
|
|
||||||
import { CustomLink } from "@/components/ui/custom/custom-link";
|
import { CustomLink } from "@/components/ui/custom/custom-link";
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ export const SocialButtons = ({
|
|||||||
</CustomLink>
|
</CustomLink>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
placement="right-start"
|
placement="top"
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
isDisabled={isGoogleOAuthEnabled}
|
isDisabled={isGoogleOAuthEnabled}
|
||||||
className="w-96"
|
className="w-96"
|
||||||
@@ -51,7 +52,7 @@ export const SocialButtons = ({
|
|||||||
</CustomLink>
|
</CustomLink>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
placement="right-start"
|
placement="top"
|
||||||
shadow="sm"
|
shadow="sm"
|
||||||
isDisabled={isGithubOAuthEnabled}
|
isDisabled={isGithubOAuthEnabled}
|
||||||
className="w-96"
|
className="w-96"
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export const ClientAccordionContent = ({
|
|||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{renderDetails()}
|
{renderDetails()}
|
||||||
<p className="mb-1 mt-3 text-sm font-medium text-gray-800 dark:text-gray-200">
|
<p className="mt-3 mb-1 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||||
⚠️ This requirement has no checks; therefore, there are no findings.
|
⚠️ This requirement has no checks; therefore, there are no findings.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -127,7 +127,7 @@ export const ClientAccordionContent = ({
|
|||||||
const checksList = (
|
const checksList = (
|
||||||
<div className="flex items-center px-2 text-sm">
|
<div className="flex items-center px-2 text-sm">
|
||||||
<div className="w-full flex-col">
|
<div className="w-full flex-col">
|
||||||
<div className="mb-1 mt-[-8px] h-1 w-full border-b border-gray-200 dark:border-gray-800" />
|
<div className="mt-[-8px] mb-1 h-1 w-full border-b border-gray-200 dark:border-gray-800" />
|
||||||
<span className="text-gray-600 dark:text-gray-200" aria-label="Checks">
|
<span className="text-gray-600 dark:text-gray-200" aria-label="Checks">
|
||||||
{checks.join(", ")}
|
{checks.join(", ")}
|
||||||
</span>
|
</span>
|
||||||
@@ -170,7 +170,7 @@ export const ClientAccordionContent = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-1 mt-3 text-sm font-medium text-gray-800 dark:text-gray-200">
|
<div className="mt-3 mb-1 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||||
⚠️ There are no findings for these regions
|
⚠️ There are no findings for these regions
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -186,7 +186,7 @@ export const ClientAccordionContent = ({
|
|||||||
items={accordionChecksItems}
|
items={accordionChecksItems}
|
||||||
variant="light"
|
variant="light"
|
||||||
defaultExpandedKeys={[""]}
|
defaultExpandedKeys={[""]}
|
||||||
className="rounded-lg bg-gray-50 dark:bg-prowler-blue-400"
|
className="dark:bg-prowler-blue-400 rounded-lg bg-gray-50"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tooltip } from "@nextui-org/react";
|
import { Tooltip } from "@heroui/tooltip";
|
||||||
|
|
||||||
interface ComplianceAccordionTitleProps {
|
interface ComplianceAccordionTitleProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -24,7 +24,7 @@ export const ComplianceAccordionTitle = ({
|
|||||||
<div className="flex flex-col items-start justify-between gap-1 md:flex-row md:items-center md:gap-2">
|
<div className="flex flex-col items-start justify-between gap-1 md:flex-row md:items-center md:gap-2">
|
||||||
<div className="overflow-hidden md:min-w-0 md:flex-1">
|
<div className="overflow-hidden md:min-w-0 md:flex-1">
|
||||||
<span
|
<span
|
||||||
className="block max-w-[600px] overflow-hidden truncate text-ellipsis text-sm"
|
className="block max-w-[600px] truncate overflow-hidden text-sm text-ellipsis"
|
||||||
title={label}
|
title={label}
|
||||||
>
|
>
|
||||||
{label.charAt(0).toUpperCase() + label.slice(1)}
|
{label.charAt(0).toUpperCase() + label.slice(1)}
|
||||||
@@ -33,7 +33,7 @@ export const ComplianceAccordionTitle = ({
|
|||||||
<div className="mr-4 flex items-center gap-2">
|
<div className="mr-4 flex items-center gap-2">
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
{total > 0 && isParentLevel && (
|
{total > 0 && isParentLevel && (
|
||||||
<span className="whitespace-nowrap text-xs font-medium text-gray-600">
|
<span className="text-xs font-medium whitespace-nowrap text-gray-600">
|
||||||
Requirements:
|
Requirements:
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -127,7 +127,7 @@ export const ComplianceAccordionTitle = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<div className="min-w-[32px] text-center text-xs font-medium text-default-600">
|
<div className="text-default-600 min-w-[32px] text-center text-xs font-medium">
|
||||||
{total > 0 ? total : "—"}
|
{total > 0 ? total : "—"}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, Progress } from "@nextui-org/react";
|
import { Card, CardBody } from "@heroui/card";
|
||||||
|
import { Progress } from "@heroui/progress";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
@@ -94,6 +95,11 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regionFilter = searchParams.get("filter[region__in]");
|
||||||
|
if (regionFilter) {
|
||||||
|
params.set("filter[region__in]", regionFilter);
|
||||||
|
}
|
||||||
|
|
||||||
router.push(`${path}?${params.toString()}`);
|
router.push(`${path}?${params.toString()}`);
|
||||||
};
|
};
|
||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
@@ -106,22 +112,19 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card fullWidth isHoverable shadow="sm">
|
||||||
fullWidth
|
<CardBody
|
||||||
isHoverable
|
className="dark:bg-prowler-blue-800 flex cursor-pointer flex-row items-center justify-between gap-4"
|
||||||
shadow="sm"
|
onClick={navigateToDetail}
|
||||||
isPressable
|
>
|
||||||
onPress={navigateToDetail}
|
<div className="flex w-full items-center gap-4">
|
||||||
>
|
|
||||||
<CardBody className="flex flex-row items-center justify-between space-x-4 dark:bg-prowler-blue-800">
|
|
||||||
<div className="flex w-full items-center space-x-4">
|
|
||||||
<Image
|
<Image
|
||||||
src={getComplianceIcon(title)}
|
src={getComplianceIcon(title)}
|
||||||
alt={`${title} logo`}
|
alt={`${title} logo`}
|
||||||
className="h-10 w-10 min-w-10 rounded-md border-1 border-gray-300 bg-white object-contain p-1"
|
className="h-10 w-10 min-w-10 rounded-md border border-gray-300 bg-white object-contain p-1"
|
||||||
/>
|
/>
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<h4 className="mb-1 text-small font-bold leading-5">
|
<h4 className="text-small mb-1 leading-5 font-bold">
|
||||||
{formatTitle(title)}
|
{formatTitle(title)}
|
||||||
{version ? ` - ${version}` : ""}
|
{version ? ` - ${version}` : ""}
|
||||||
</h4>
|
</h4>
|
||||||
@@ -146,13 +149,24 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
|||||||
Passing Requirements
|
Passing Requirements
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<DownloadIconButton
|
<div
|
||||||
paramId={complianceId}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onDownload={handleDownload}
|
onKeyDown={(e) => {
|
||||||
textTooltip="Download compliance CSV report"
|
if (e.key === "Enter" || e.key === " ") {
|
||||||
isDisabled={hasRegionFilter}
|
e.stopPropagation();
|
||||||
isDownloading={isDownloading}
|
}
|
||||||
/>
|
}}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<DownloadIconButton
|
||||||
|
paramId={complianceId}
|
||||||
|
onDownload={handleDownload}
|
||||||
|
textTooltip="Download compliance CSV report"
|
||||||
|
isDisabled={hasRegionFilter}
|
||||||
|
isDownloading={isDownloading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/* <small>{getScanChange()}</small> */}
|
{/* <small>{getScanChange()}</small> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ interface FailedSectionsListProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const title = (
|
const title = (
|
||||||
<h3 className="mb-2 whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
<h3 className="mb-2 text-xs font-semibold tracking-wide whitespace-nowrap uppercase">
|
||||||
Top Failed Sections
|
Top Failed Sections
|
||||||
</h3>
|
</h3>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@nextui-org/react";
|
import { cn } from "@heroui/theme";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ const capitalizeFirstLetter = (text: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const title = (
|
const title = (
|
||||||
<h3 className="mb-2 whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
<h3 className="mb-2 text-xs font-semibold tracking-wide whitespace-nowrap uppercase">
|
||||||
Sections Failure Rate
|
Sections Failure Rate
|
||||||
</h3>
|
</h3>
|
||||||
);
|
);
|
||||||
@@ -135,13 +135,30 @@ export const HeatmapChart = ({ categories = [] }: HeatmapChartProps) => {
|
|||||||
color: theme === "dark" ? "white" : "black",
|
color: theme === "dark" ? "white" : "black",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-1 font-semibold">
|
<div
|
||||||
|
className="mb-1 font-semibold"
|
||||||
|
style={{ color: theme === "dark" ? "white" : "black" }}
|
||||||
|
>
|
||||||
{capitalizeFirstLetter(hoveredItem.name)}
|
{capitalizeFirstLetter(hoveredItem.name)}
|
||||||
</div>
|
</div>
|
||||||
<div>Failure Rate: {hoveredItem.failurePercentage}%</div>
|
|
||||||
<div>
|
<div>
|
||||||
Failed: {hoveredItem.failedRequirements}/
|
<span
|
||||||
{hoveredItem.totalRequirements}
|
style={{
|
||||||
|
color: getHeatmapColor(hoveredItem.failurePercentage),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Failure Rate: {hoveredItem.failurePercentage}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: getHeatmapColor(hoveredItem.failurePercentage),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Failed: {hoveredItem.failedRequirements}/
|
||||||
|
{hoveredItem.totalRequirements}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export const PieChart = ({ pass, fail, manual }: PieChartProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[320px] flex-col items-center justify-between">
|
<div className="flex h-[320px] flex-col items-center justify-between">
|
||||||
<h3 className="whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
<h3 className="text-xs font-semibold tracking-wide whitespace-nowrap uppercase">
|
||||||
Requirements Status
|
Requirements Status
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@@ -176,15 +176,15 @@ export const PieChart = ({ pass, fail, manual }: PieChartProps) => {
|
|||||||
<div className="mt-2 grid grid-cols-3 gap-4">
|
<div className="mt-2 grid grid-cols-3 gap-4">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="text-muted-foreground text-sm">Pass</div>
|
<div className="text-muted-foreground text-sm">Pass</div>
|
||||||
<div className="font-semibold text-system-success-medium">{pass}</div>
|
<div className="text-system-success-medium font-semibold">{pass}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="text-muted-foreground text-sm">Fail</div>
|
<div className="text-muted-foreground text-sm">Fail</div>
|
||||||
<div className="font-semibold text-system-error-medium">{fail}</div>
|
<div className="text-system-error-medium font-semibold">{fail}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="text-muted-foreground text-sm">Manual</div>
|
<div className="text-muted-foreground text-sm">Manual</div>
|
||||||
<div className="font-semibold text-prowler-grey-light">{manual}</div>
|
<div className="text-prowler-grey-light font-semibold">{manual}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export const CISCustomDetails = ({ requirement }: CISDetailsProps) => {
|
|||||||
|
|
||||||
{requirement.references && (
|
{requirement.references && (
|
||||||
<ComplianceDetailSection title="References">
|
<ComplianceDetailSection title="References">
|
||||||
<div className="space-y-1">
|
<div className="flex flex-col gap-1">
|
||||||
{processReferences(requirement.references).map(
|
{processReferences(requirement.references).map(
|
||||||
(url: string, index: number) => (
|
(url: string, index: number) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
|
|||||||
@@ -71,9 +71,12 @@ export const MITRECustomDetails = ({
|
|||||||
|
|
||||||
{cloudServices && cloudServices.length > 0 && (
|
{cloudServices && cloudServices.length > 0 && (
|
||||||
<ComplianceDetailSection title="Cloud Security Mappings">
|
<ComplianceDetailSection title="Cloud Security Mappings">
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
{cloudServices.map((service, index) => (
|
{cloudServices.map((service, index) => (
|
||||||
<div key={index} className="space-y-3 rounded-lg border p-4">
|
<div
|
||||||
|
key={index}
|
||||||
|
className="flex flex-col gap-3 rounded-lg border p-4"
|
||||||
|
>
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<ComplianceBadge
|
<ComplianceBadge
|
||||||
label="Service"
|
label="Service"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const ComplianceDetailContainer = ({
|
|||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
return <div className="space-y-4">{children}</div>;
|
return <div className="flex flex-col gap-4">{children}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ComplianceDetailSection = ({
|
export const ComplianceDetailSection = ({
|
||||||
@@ -107,7 +107,7 @@ export const ComplianceBulletList = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ComplianceDetailSection title={title}>
|
<ComplianceDetailSection title={title}>
|
||||||
<div className="space-y-2">
|
<div className="flex flex-col gap-2">
|
||||||
{items.map((item: string, index: number) => (
|
{items.map((item: string, index: number) => (
|
||||||
<div key={index} className="flex items-start gap-2">
|
<div key={index} className="flex items-start gap-2">
|
||||||
<span className="text-muted-foreground mt-1 text-xs">•</span>
|
<span className="text-muted-foreground mt-1 text-xs">•</span>
|
||||||
@@ -134,7 +134,7 @@ export const ComplianceChipContainer = ({
|
|||||||
{items.map((item: string, index: number) => (
|
{items.map((item: string, index: number) => (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20"
|
className="inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-gray-500/10 ring-inset dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20"
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
|
|
||||||
import { FilterControls } from "@/components/filters";
|
import { FilterControls } from "@/components/filters";
|
||||||
import { DataTableFilterCustom } from "@/components/ui/table/data-table-filter-custom";
|
import { DataTableFilterCustom } from "@/components/ui/table/data-table-filter-custom";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Divider, Tooltip } from "@nextui-org/react";
|
import { Divider } from "@heroui/divider";
|
||||||
|
import { Tooltip } from "@heroui/tooltip";
|
||||||
|
|
||||||
import { DateWithTime, EntityInfoShort } from "@/components/ui/entities";
|
import { DateWithTime, EntityInfoShort } from "@/components/ui/entities";
|
||||||
import { ProviderType } from "@/types";
|
import { ProviderType } from "@/types";
|
||||||
@@ -34,7 +35,7 @@ export const ComplianceScanInfo = ({ scan }: ComplianceScanInfoProps) => {
|
|||||||
placement="top"
|
placement="top"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<p className="text-xs text-default-500">
|
<p className="text-default-500 text-xs">
|
||||||
{scan.attributes.name || "- -"}
|
{scan.attributes.name || "- -"}
|
||||||
</p>
|
</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Select, SelectItem } from "@nextui-org/react";
|
import { Select, SelectItem } from "@heroui/select";
|
||||||
|
|
||||||
import { ProviderType, ScanProps } from "@/types";
|
import { ProviderType, ScanProps } from "@/types";
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const NoScansAvailable = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-full min-h-[calc(100vh-56px)] items-center justify-center">
|
<div className="flex h-full min-h-[calc(100vh-56px)] items-center justify-center">
|
||||||
<div className="mx-auto w-full max-w-2xl">
|
<div className="mx-auto w-full max-w-2xl">
|
||||||
<div className="flex items-center justify-start rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-prowler-blue-400">
|
<div className="dark:bg-prowler-blue-400 flex items-center justify-start rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700">
|
||||||
<div className="flex w-full items-center justify-between gap-6">
|
<div className="flex w-full items-center justify-between gap-6">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<InfoIcon className="mt-1 h-5 w-5 text-gray-400 dark:text-gray-300" />
|
<InfoIcon className="mt-1 h-5 w-5 text-gray-400 dark:text-gray-300" />
|
||||||
@@ -25,7 +25,7 @@ export const NoScansAvailable = () => {
|
|||||||
</div>
|
</div>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
asLink="/scans"
|
asLink="/scans"
|
||||||
className="flex-shrink-0"
|
className="shrink-0"
|
||||||
ariaLabel="Go to Scans page"
|
ariaLabel="Go to Scans page"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
color="action"
|
color="action"
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Skeleton } from "@nextui-org/react";
|
import { Skeleton } from "@heroui/skeleton";
|
||||||
|
|
||||||
export const BarChartSkeleton = () => {
|
export const BarChartSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-[400px] flex-col items-center justify-between">
|
<div className="flex w-[400px] flex-col items-center justify-between">
|
||||||
{/* Title skeleton */}
|
{/* Title skeleton */}
|
||||||
<Skeleton className="h-4 w-40 rounded-lg">
|
<Skeleton className="h-4 w-40 rounded-lg">
|
||||||
<div className="h-4 bg-default-200" />
|
<div className="bg-default-200 h-4" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
||||||
{/* Chart area skeleton */}
|
{/* Chart area skeleton */}
|
||||||
<div className="ml-24 flex h-full flex-col justify-center space-y-2 p-4">
|
<div className="ml-24 flex h-full flex-col justify-center gap-2 p-4">
|
||||||
{/* Bar chart skeleton - 5 horizontal bars */}
|
{/* Bar chart skeleton - 5 horizontal bars */}
|
||||||
{Array.from({ length: 5 }).map((_, index) => (
|
{Array.from({ length: 5 }).map((_, index) => (
|
||||||
<div key={index} className="flex items-center space-x-4">
|
<div key={index} className="flex items-center gap-4">
|
||||||
{/* Bar skeleton with varying widths */}
|
{/* Bar skeleton with varying widths */}
|
||||||
<Skeleton
|
<Skeleton
|
||||||
className={`h-10 rounded-lg ${
|
className={`h-10 rounded-lg ${
|
||||||
@@ -29,20 +29,20 @@ export const BarChartSkeleton = () => {
|
|||||||
: "w-16"
|
: "w-16"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="h-6 bg-default-200" />
|
<div className="bg-default-200 h-6" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Legend skeleton */}
|
{/* Legend skeleton */}
|
||||||
<div className="flex justify-center space-x-4 pt-2">
|
<div className="flex justify-center gap-4 pt-2">
|
||||||
{Array.from({ length: 3 }).map((_, index) => (
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
<div key={index} className="flex items-center space-x-1">
|
<div key={index} className="flex items-center gap-1">
|
||||||
<Skeleton className="h-3 w-3 rounded-full">
|
<Skeleton className="h-3 w-3 rounded-full">
|
||||||
<div className="h-3 w-3 bg-default-200" />
|
<div className="bg-default-200 h-3 w-3" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<Skeleton className="h-3 w-16 rounded-lg">
|
<Skeleton className="h-3 w-16 rounded-lg">
|
||||||
<div className="h-3 bg-default-200" />
|
<div className="bg-default-200 h-3" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Skeleton } from "@nextui-org/react";
|
import { Skeleton } from "@heroui/skeleton";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface SkeletonAccordionProps {
|
interface SkeletonAccordionProps {
|
||||||
@@ -16,7 +16,7 @@ export const SkeletonAccordion = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full space-y-2 ${className} rounded-xl border border-gray-300 p-2 dark:border-gray-700`}
|
className={`flex w-full flex-col gap-2 ${className} rounded-xl border border-gray-300 p-2 dark:border-gray-700`}
|
||||||
>
|
>
|
||||||
{[...Array(itemCount)].map((_, index) => (
|
{[...Array(itemCount)].map((_, index) => (
|
||||||
<Skeleton key={index} className="rounded-lg">
|
<Skeleton key={index} className="rounded-lg">
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Card, Skeleton } from "@nextui-org/react";
|
import { Card } from "@heroui/card";
|
||||||
|
import { Skeleton } from "@heroui/skeleton";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export const ComplianceSkeletonGrid = () => {
|
export const ComplianceSkeletonGrid = () => {
|
||||||
return (
|
return (
|
||||||
<Card className="h-fit w-full p-4">
|
<Card className="h-fit w-full p-4">
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 3xl:grid-cols-4">
|
<div className="3xl:grid-cols-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3">
|
||||||
{[...Array(28)].map((_, index) => (
|
{[...Array(28)].map((_, index) => (
|
||||||
<div key={index} className="flex flex-col space-y-4">
|
<div key={index} className="flex flex-col gap-4">
|
||||||
<Skeleton className="h-28 rounded-lg">
|
<Skeleton className="h-28 rounded-lg">
|
||||||
<div className="h-full bg-default-300"></div>
|
<div className="bg-default-300 h-full"></div>
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Skeleton } from "@nextui-org/react";
|
import { Skeleton } from "@heroui/skeleton";
|
||||||
|
|
||||||
export const HeatmapChartSkeleton = () => {
|
export const HeatmapChartSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[320px] w-[400px] flex-col items-center justify-between lg:w-[400px]">
|
<div className="flex h-[320px] w-[400px] flex-col items-center justify-between lg:w-[400px]">
|
||||||
{/* Title skeleton */}
|
{/* Title skeleton */}
|
||||||
<Skeleton className="h-4 w-36 rounded-lg">
|
<Skeleton className="h-4 w-36 rounded-lg">
|
||||||
<div className="h-4 bg-default-200" />
|
<div className="bg-default-200 h-4" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
||||||
{/* Heatmap area skeleton - 3x3 grid like the real component */}
|
{/* Heatmap area skeleton - 3x3 grid like the real component */}
|
||||||
@@ -18,7 +18,7 @@ export const HeatmapChartSkeleton = () => {
|
|||||||
key={index}
|
key={index}
|
||||||
className="flex items-center justify-center rounded border"
|
className="flex items-center justify-center rounded border"
|
||||||
>
|
>
|
||||||
<div className="h-full w-full bg-default-200" />
|
<div className="bg-default-200 h-full w-full" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Skeleton } from "@nextui-org/react";
|
import { Skeleton } from "@heroui/skeleton";
|
||||||
|
|
||||||
export const PieChartSkeleton = () => {
|
export const PieChartSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[320px] flex-col items-center justify-between">
|
<div className="flex h-[320px] flex-col items-center justify-between">
|
||||||
{/* Title skeleton */}
|
{/* Title skeleton */}
|
||||||
<Skeleton className="h-4 w-32 rounded-lg">
|
<Skeleton className="h-4 w-32 rounded-lg">
|
||||||
<div className="h-4 bg-default-200" />
|
<div className="bg-default-200 h-4" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
||||||
{/* Pie chart skeleton */}
|
{/* Pie chart skeleton */}
|
||||||
<div className="relative flex aspect-square w-[200px] min-w-[200px] items-center justify-center">
|
<div className="relative flex aspect-square w-[200px] min-w-[200px] items-center justify-center">
|
||||||
{/* Outer circle */}
|
{/* Outer circle */}
|
||||||
<Skeleton className="absolute h-[200px] w-[200px] rounded-full">
|
<Skeleton className="absolute h-[200px] w-[200px] rounded-full">
|
||||||
<div className="h-[200px] w-[200px] bg-default-200" />
|
<div className="bg-default-200 h-[200px] w-[200px]" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
||||||
{/* Inner circle (donut hole) */}
|
{/* Inner circle (donut hole) */}
|
||||||
<div className="absolute h-[140px] w-[140px] rounded-full bg-background"></div>
|
<div className="bg-background absolute h-[140px] w-[140px] rounded-full"></div>
|
||||||
|
|
||||||
{/* Center text skeleton */}
|
{/* Center text skeleton */}
|
||||||
<div className="absolute flex flex-col items-center">
|
<div className="absolute flex flex-col items-center">
|
||||||
<Skeleton className="h-6 w-8 rounded-lg">
|
<Skeleton className="h-6 w-8 rounded-lg">
|
||||||
<div className="h-6 bg-default-300" />
|
<div className="bg-default-300 h-6" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<Skeleton className="mt-1 h-3 w-6 rounded-lg">
|
<Skeleton className="mt-1 h-3 w-6 rounded-lg">
|
||||||
<div className="h-3 bg-default-300" />
|
<div className="bg-default-300 h-3" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -35,26 +35,26 @@ export const PieChartSkeleton = () => {
|
|||||||
<div className="mt-2 grid grid-cols-3 gap-4">
|
<div className="mt-2 grid grid-cols-3 gap-4">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<Skeleton className="h-4 w-8 rounded-lg">
|
<Skeleton className="h-4 w-8 rounded-lg">
|
||||||
<div className="h-4 bg-default-200" />
|
<div className="bg-default-200 h-4" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
|
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
|
||||||
<div className="h-5 bg-default-200" />
|
<div className="bg-default-200 h-5" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<Skeleton className="h-4 w-6 rounded-lg">
|
<Skeleton className="h-4 w-6 rounded-lg">
|
||||||
<div className="h-4 bg-default-200" />
|
<div className="bg-default-200 h-4" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
|
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
|
||||||
<div className="h-5 bg-default-200" />
|
<div className="bg-default-200 h-5" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<Skeleton className="h-4 w-12 rounded-lg">
|
<Skeleton className="h-4 w-12 rounded-lg">
|
||||||
<div className="h-4 bg-default-200" />
|
<div className="bg-default-200 h-4" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
|
<Skeleton className="mt-1 h-5 w-6 rounded-lg">
|
||||||
<div className="h-5 bg-default-200" />
|
<div className="bg-default-200 h-5" />
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const FeedsDetail = () => {
|
|||||||
<Icon size={18} />
|
<Icon size={18} />
|
||||||
{/* TODO: Update this condition once the RSS data response structure is finalized */}
|
{/* TODO: Update this condition once the RSS data response structure is finalized */}
|
||||||
{feed.length > 0 && (
|
{feed.length > 0 && (
|
||||||
<span className="absolute right-0 top-0 h-2 w-2 rounded-full bg-red-500 dark:bg-gray-400"></span>
|
<span className="absolute top-0 right-0 h-2 w-2 rounded-full bg-red-500 dark:bg-gray-400"></span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@@ -72,7 +72,7 @@ export const FeedsDetail = () => {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
>
|
>
|
||||||
<h3 className="text-small font-medium leading-none">
|
<h3 className="text-small leading-none font-medium">
|
||||||
{item.title}
|
{item.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-gray-500">{item.description}</p>
|
<p className="text-sm text-gray-500">{item.description}</p>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Select, SelectItem } from "@nextui-org/react";
|
import { Select, SelectItem } from "@heroui/select";
|
||||||
|
|
||||||
const accounts = [
|
const accounts = [
|
||||||
{ key: "audit-test-1", label: "740350143844" },
|
{ key: "audit-test-1", label: "740350143844" },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Checkbox } from "@nextui-org/react";
|
import { Checkbox } from "@heroui/checkbox";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Button, ButtonGroup } from "@heroui/button";
|
||||||
|
import { DatePicker } from "@heroui/date-picker";
|
||||||
import {
|
import {
|
||||||
getLocalTimeZone,
|
getLocalTimeZone,
|
||||||
startOfMonth,
|
startOfMonth,
|
||||||
startOfWeek,
|
startOfWeek,
|
||||||
today,
|
today,
|
||||||
} from "@internationalized/date";
|
} from "@internationalized/date";
|
||||||
import { Button, ButtonGroup, DatePicker } from "@nextui-org/react";
|
|
||||||
import { useLocale } from "@react-aria/i18n";
|
import { useLocale } from "@react-aria/i18n";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import React, { useCallback, useEffect, useRef } from "react";
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
@@ -66,10 +67,10 @@ export const CustomDatePicker = () => {
|
|||||||
CalendarTopContent={
|
CalendarTopContent={
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
fullWidth
|
fullWidth
|
||||||
className="bg-content1 px-3 pb-2 pt-3 dark:bg-prowler-blue-400 [&>button]:border-default-200/60 [&>button]:text-default-500"
|
className="bg-content1 dark:bg-prowler-blue-400 [&>button]:border-default-200/60 [&>button]:text-default-500 px-3 pt-3 pb-2"
|
||||||
radius="full"
|
radius="full"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="bordered"
|
variant="flat"
|
||||||
>
|
>
|
||||||
<Button onPress={() => handleDateChange(now)}>Today</Button>
|
<Button onPress={() => handleDateChange(now)}>Today</Button>
|
||||||
<Button onPress={() => handleDateChange(nextWeek)}>
|
<Button onPress={() => handleDateChange(nextWeek)}>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Select, SelectItem } from "@nextui-org/react";
|
import { Select, SelectItem } from "@heroui/select";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import React, { useCallback, useMemo } from "react";
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Input } from "@nextui-org/react";
|
import { Input } from "@heroui/input";
|
||||||
import debounce from "lodash.debounce";
|
|
||||||
import { SearchIcon, XCircle } from "lucide-react";
|
import { SearchIcon, XCircle } from "lucide-react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
import { useUrlFilters } from "@/hooks/use-url-filters";
|
import { useUrlFilters } from "@/hooks/use-url-filters";
|
||||||
|
|
||||||
@@ -10,6 +9,7 @@ export const CustomSearchInput: React.FC = () => {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const { updateFilter } = useUrlFilters();
|
const { updateFilter } = useUrlFilters();
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
const applySearch = useCallback(
|
const applySearch = useCallback(
|
||||||
(query: string) => {
|
(query: string) => {
|
||||||
@@ -24,7 +24,12 @@ export const CustomSearchInput: React.FC = () => {
|
|||||||
|
|
||||||
const debouncedChangeHandler = useCallback(
|
const debouncedChangeHandler = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
debounce((val) => applySearch(val), 300)(value);
|
if (debounceTimeoutRef.current) {
|
||||||
|
clearTimeout(debounceTimeoutRef.current);
|
||||||
|
}
|
||||||
|
debounceTimeoutRef.current = setTimeout(() => {
|
||||||
|
applySearch(value);
|
||||||
|
}, 300);
|
||||||
},
|
},
|
||||||
[applySearch],
|
[applySearch],
|
||||||
);
|
);
|
||||||
@@ -39,11 +44,19 @@ export const CustomSearchInput: React.FC = () => {
|
|||||||
setSearchQuery(searchFromUrl);
|
setSearchQuery(searchFromUrl);
|
||||||
}, [searchParams]);
|
}, [searchParams]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (debounceTimeoutRef.current) {
|
||||||
|
clearTimeout(debounceTimeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
variant="flat"
|
variant="flat"
|
||||||
classNames={{
|
classNames={{
|
||||||
label: "tracking-tight font-light !text-default-600 text-sm !z-0 pb-1",
|
label: "tracking-tight font-light !text-default-600 text-sm z-0! pb-1",
|
||||||
}}
|
}}
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
label="Search"
|
label="Search"
|
||||||
@@ -59,7 +72,7 @@ export const CustomSearchInput: React.FC = () => {
|
|||||||
endContent={
|
endContent={
|
||||||
searchQuery && (
|
searchQuery && (
|
||||||
<button onClick={clearIconSearch} className="focus:outline-none">
|
<button onClick={clearIconSearch} className="focus:outline-none">
|
||||||
<XCircle className="h-4 w-4 text-default-400" />
|
<XCircle className="text-default-400 h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Select, SelectItem } from "@nextui-org/react";
|
import { Select, SelectItem } from "@heroui/select";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import React, { useCallback, useMemo } from "react";
|
import React, { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ export const CustomSelectProvider: React.FC = () => {
|
|||||||
placeholder="Select a provider"
|
placeholder="Select a provider"
|
||||||
classNames={{
|
classNames={{
|
||||||
selectorIcon: "right-2",
|
selectorIcon: "right-2",
|
||||||
label: "!z-0 mb-2",
|
label: "z-0! mb-2",
|
||||||
}}
|
}}
|
||||||
label="Provider"
|
label="Provider"
|
||||||
labelPlacement="inside"
|
labelPlacement="inside"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Spacer } from "@nextui-org/react";
|
import { Spacer } from "@heroui/spacer";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tooltip } from "@nextui-org/react";
|
import { Tooltip } from "@heroui/tooltip";
|
||||||
|
|
||||||
import { MutedIcon } from "../icons";
|
import { MutedIcon } from "../icons";
|
||||||
|
|
||||||
@@ -15,8 +15,8 @@ export const Muted = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={mutedReason} className="text-xs">
|
<Tooltip content={mutedReason} className="text-xs">
|
||||||
<div className="w-fit rounded-full border border-system-severity-critical/40 p-1">
|
<div className="border-system-severity-critical/40 w-fit rounded-full border p-1">
|
||||||
<MutedIcon className="h-4 w-4 text-system-severity-critical" />
|
<MutedIcon className="text-system-severity-critical h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Input } from "@heroui/input";
|
||||||
|
import { Select, SelectItem } from "@heroui/select";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Input, Select, type Selection, SelectItem } from "@nextui-org/react";
|
import type { Selection } from "@react-types/shared";
|
||||||
import { Search, Send } from "lucide-react";
|
import { Search, Send } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
type Dispatch,
|
type Dispatch,
|
||||||
@@ -222,7 +224,10 @@ export const SendToJiraModal = ({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
<form
|
||||||
|
onSubmit={form.handleSubmit(handleSubmit)}
|
||||||
|
className="flex flex-col gap-4"
|
||||||
|
>
|
||||||
{/* Integration Selection */}
|
{/* Integration Selection */}
|
||||||
{integrations.length > 1 && (
|
{integrations.length > 1 && (
|
||||||
<FormField
|
<FormField
|
||||||
@@ -256,7 +261,7 @@ export const SendToJiraModal = ({
|
|||||||
trigger: "min-h-12",
|
trigger: "min-h-12",
|
||||||
popoverContent: "dark:bg-gray-800",
|
popoverContent: "dark:bg-gray-800",
|
||||||
label:
|
label:
|
||||||
"tracking-tight font-light !text-default-500 text-xs !z-0",
|
"tracking-tight font-light !text-default-500 text-xs z-0!",
|
||||||
value: "text-default-500 text-small dark:text-gray-300",
|
value: "text-default-500 text-small dark:text-gray-300",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -277,7 +282,7 @@ export const SendToJiraModal = ({
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage className="text-xs text-system-error" />
|
<FormMessage className="text-system-error text-xs" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -312,13 +317,13 @@ export const SendToJiraModal = ({
|
|||||||
popoverContent: "dark:bg-gray-800",
|
popoverContent: "dark:bg-gray-800",
|
||||||
listboxWrapper: "max-h-[300px] dark:bg-gray-800",
|
listboxWrapper: "max-h-[300px] dark:bg-gray-800",
|
||||||
label:
|
label:
|
||||||
"tracking-tight font-light !text-default-500 text-xs !z-0",
|
"tracking-tight font-light !text-default-500 text-xs z-0!",
|
||||||
value: "text-default-500 text-small dark:text-gray-300",
|
value: "text-default-500 text-small dark:text-gray-300",
|
||||||
}}
|
}}
|
||||||
listboxProps={{
|
listboxProps={{
|
||||||
topContent:
|
topContent:
|
||||||
filteredProjects.length > 5 ? (
|
filteredProjects.length > 5 ? (
|
||||||
<div className="sticky top-0 z-10 bg-content1 py-2 dark:bg-gray-800">
|
<div className="bg-content1 sticky top-0 z-10 py-2 dark:bg-gray-800">
|
||||||
<Input
|
<Input
|
||||||
isClearable
|
isClearable
|
||||||
placeholder="Search projects..."
|
placeholder="Search projects..."
|
||||||
@@ -350,7 +355,7 @@ export const SendToJiraModal = ({
|
|||||||
<span className="text-tiny text-default-500">
|
<span className="text-tiny text-default-500">
|
||||||
-
|
-
|
||||||
</span>
|
</span>
|
||||||
<span className="truncate text-small">
|
<span className="text-small truncate">
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -361,7 +366,7 @@ export const SendToJiraModal = ({
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage className="text-xs text-system-error" />
|
<FormMessage className="text-system-error text-xs" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -393,7 +398,7 @@ export const SendToJiraModal = ({
|
|||||||
popoverContent: "dark:bg-gray-800",
|
popoverContent: "dark:bg-gray-800",
|
||||||
listboxWrapper: "max-h-[300px] dark:bg-gray-800",
|
listboxWrapper: "max-h-[300px] dark:bg-gray-800",
|
||||||
label:
|
label:
|
||||||
"tracking-tight font-light !text-default-500 text-xs !z-0",
|
"tracking-tight font-light !text-default-500 text-xs z-0!",
|
||||||
value: "text-default-500 text-small dark:text-gray-300",
|
value: "text-default-500 text-small dark:text-gray-300",
|
||||||
}}
|
}}
|
||||||
listboxProps={{
|
listboxProps={{
|
||||||
|
|||||||
@@ -109,18 +109,18 @@ export const ColumnFindings: ColumnDef<FindingProps>[] = [
|
|||||||
const { delta } = row.original.attributes;
|
const { delta } = row.original.attributes;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex max-w-[410px] flex-row items-center gap-2 3xl:max-w-[660px]">
|
<div className="3xl:max-w-[660px] relative flex max-w-[410px] flex-row items-center gap-2">
|
||||||
<div className="flex flex-row items-center gap-4">
|
<div className="flex flex-row items-center gap-4">
|
||||||
{delta === "new" || delta === "changed" ? (
|
{delta === "new" || delta === "changed" ? (
|
||||||
<DeltaIndicator delta={delta} />
|
<DeltaIndicator delta={delta} />
|
||||||
) : (
|
) : (
|
||||||
<div className="w-2" />
|
<div className="w-2" />
|
||||||
)}
|
)}
|
||||||
<p className="mr-7 whitespace-normal break-words text-sm">
|
<p className="mr-7 text-sm break-words whitespace-normal">
|
||||||
{checktitle}
|
{checktitle}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="absolute -right-2 top-1/2 -translate-y-1/2">
|
<span className="absolute top-1/2 -right-2 -translate-y-1/2">
|
||||||
<Muted isMuted={muted} mutedReason={muted_reason || ""} />
|
<Muted isMuted={muted} mutedReason={muted_reason || ""} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@heroui/button";
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Dropdown,
|
Dropdown,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownSection,
|
DropdownSection,
|
||||||
DropdownTrigger,
|
DropdownTrigger,
|
||||||
} from "@nextui-org/react";
|
} from "@heroui/dropdown";
|
||||||
import { Row } from "@tanstack/react-table";
|
import { Row } from "@tanstack/react-table";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
|||||||
|
|
||||||
<div className="relative flex items-center justify-end gap-2">
|
<div className="relative flex items-center justify-end gap-2">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
className="shadow-xl dark:bg-prowler-blue-800"
|
className="dark:bg-prowler-blue-800 shadow-xl"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
<DropdownTrigger>
|
<DropdownTrigger>
|
||||||
@@ -60,7 +60,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
|
|||||||
startContent={
|
startContent={
|
||||||
<JiraIcon
|
<JiraIcon
|
||||||
size={20}
|
size={20}
|
||||||
className="pointer-events-none flex-shrink-0 text-default-500"
|
className="text-default-500 pointer-events-none shrink-0"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onPress={() => setIsJiraModalOpen(true)}
|
onPress={() => setIsJiraModalOpen(true)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tooltip } from "@nextui-org/react";
|
import { Tooltip } from "@heroui/tooltip";
|
||||||
|
|
||||||
import { CustomButton } from "@/components/ui/custom/custom-button";
|
import { CustomButton } from "@/components/ui/custom/custom-button";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -22,7 +22,7 @@ export const DeltaIndicator = ({ delta }: DeltaIndicatorProps) => {
|
|||||||
ariaLabel="Learn more about findings"
|
ariaLabel="Learn more about findings"
|
||||||
color="transparent"
|
color="transparent"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="h-auto min-w-0 p-0 text-primary"
|
className="text-primary h-auto min-w-0 p-0"
|
||||||
asLink="https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app/#step-8-analyze-the-findings"
|
asLink="https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/prowler-app/#step-8-analyze-the-findings"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Snippet } from "@nextui-org/react";
|
import { Snippet } from "@heroui/snippet";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
|
|
||||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||||
@@ -20,7 +20,7 @@ import { DeltaIndicator } from "./delta-indicator";
|
|||||||
|
|
||||||
const MarkdownContainer = ({ children }: { children: string }) => {
|
const MarkdownContainer = ({ children }: { children: string }) => {
|
||||||
return (
|
return (
|
||||||
<div className="prose prose-sm max-w-none whitespace-normal break-words dark:prose-invert">
|
<div className="prose prose-sm dark:prose-invert max-w-none break-words whitespace-normal">
|
||||||
<ReactMarkdown>{children}</ReactMarkdown>
|
<ReactMarkdown>{children}</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -65,7 +65,7 @@ export const FindingDetail = ({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="line-clamp-2 text-lg font-medium leading-tight text-gray-800 dark:text-prowler-theme-pale/90">
|
<h2 className="dark:text-prowler-theme-pale/90 line-clamp-2 text-lg leading-tight font-medium text-gray-800">
|
||||||
{renderValue(attributes.check_metadata.checktitle)}
|
{renderValue(attributes.check_metadata.checktitle)}
|
||||||
<CopyLinkButton url={url} />
|
<CopyLinkButton url={url} />
|
||||||
</h2>
|
</h2>
|
||||||
@@ -82,7 +82,7 @@ export const FindingDetail = ({
|
|||||||
? "bg-green-100 text-green-600"
|
? "bg-green-100 text-green-600"
|
||||||
: attributes.status === "MANUAL"
|
: attributes.status === "MANUAL"
|
||||||
? "bg-gray-100 text-gray-600"
|
? "bg-gray-100 text-gray-600"
|
||||||
: "bg-red-100 text-system-severity-critical"
|
: "text-system-severity-critical bg-red-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{renderValue(attributes.status)}
|
{renderValue(attributes.status)}
|
||||||
@@ -159,7 +159,7 @@ export const FindingDetail = ({
|
|||||||
|
|
||||||
{attributes.check_metadata.remediation && (
|
{attributes.check_metadata.remediation && (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<h4 className="text-sm font-bold text-gray-700 dark:text-prowler-theme-pale/90">
|
<h4 className="dark:text-prowler-theme-pale/90 text-sm font-bold text-gray-700">
|
||||||
Remediation Details
|
Remediation Details
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ export const FindingDetail = ({
|
|||||||
{attributes.check_metadata.remediation.code.cli && (
|
{attributes.check_metadata.remediation.code.cli && (
|
||||||
<InfoField label="CLI Command" variant="simple">
|
<InfoField label="CLI Command" variant="simple">
|
||||||
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800">
|
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800">
|
||||||
<span className="whitespace-pre-line text-xs">
|
<span className="text-xs whitespace-pre-line">
|
||||||
{attributes.check_metadata.remediation.code.cli}
|
{attributes.check_metadata.remediation.code.cli}
|
||||||
</span>
|
</span>
|
||||||
</Snippet>
|
</Snippet>
|
||||||
@@ -216,7 +216,7 @@ export const FindingDetail = ({
|
|||||||
key={idx}
|
key={idx}
|
||||||
href={link}
|
href={link}
|
||||||
size="sm"
|
size="sm"
|
||||||
className="!whitespace-normal break-all"
|
className="break-all whitespace-normal!"
|
||||||
>
|
>
|
||||||
{link}
|
{link}
|
||||||
</CustomLink>
|
</CustomLink>
|
||||||
@@ -237,7 +237,7 @@ export const FindingDetail = ({
|
|||||||
<CustomSection title="Resource Details">
|
<CustomSection title="Resource Details">
|
||||||
<InfoField label="Resource ID" variant="simple">
|
<InfoField label="Resource ID" variant="simple">
|
||||||
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800" hideSymbol>
|
<Snippet className="bg-gray-50 py-1 dark:bg-slate-800" hideSymbol>
|
||||||
<span className="whitespace-pre-line text-xs">
|
<span className="text-xs whitespace-pre-line">
|
||||||
{renderValue(resource.uid)}
|
{renderValue(resource.uid)}
|
||||||
</span>
|
</span>
|
||||||
</Snippet>
|
</Snippet>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
import { SettingsIcon } from "lucide-react";
|
import { SettingsIcon } from "lucide-react";
|
||||||
|
|
||||||
import { JiraIcon } from "@/components/icons/services/IconServices";
|
import { JiraIcon } from "@/components/icons/services/IconServices";
|
||||||
@@ -19,7 +19,7 @@ export const JiraIntegrationCard = () => {
|
|||||||
Jira
|
Jira
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
||||||
<p className="text-nowrap text-xs text-gray-500 dark:text-gray-300">
|
<p className="text-xs text-nowrap text-gray-500 dark:text-gray-300">
|
||||||
Create and manage security issues in Jira.
|
Create and manage security issues in Jira.
|
||||||
</p>
|
</p>
|
||||||
<CustomLink
|
<CustomLink
|
||||||
|
|||||||
@@ -222,11 +222,11 @@ export const JiraIntegrationForm = ({
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="flex flex-col space-y-6"
|
className="flex flex-col gap-6"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
||||||
<p className="flex items-center gap-2 text-sm text-default-500">
|
<p className="text-default-500 flex items-center gap-2 text-sm">
|
||||||
Need help configuring your Jira integration?
|
Need help configuring your Jira integration?
|
||||||
</p>
|
</p>
|
||||||
<CustomLink
|
<CustomLink
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -214,7 +214,7 @@ export const JiraIntegrationsManager = ({
|
|||||||
title="Delete Jira Integration"
|
title="Delete Jira Integration"
|
||||||
description="This action cannot be undone. This will permanently delete your Jira integration."
|
description="This action cannot be undone. This will permanently delete your Jira integration."
|
||||||
>
|
>
|
||||||
<div className="flex w-full justify-center space-x-6">
|
<div className="flex w-full justify-center gap-6">
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
ariaLabel="Cancel"
|
ariaLabel="Cancel"
|
||||||
@@ -265,7 +265,7 @@ export const JiraIntegrationsManager = ({
|
|||||||
/>
|
/>
|
||||||
</CustomAlertModal>
|
</CustomAlertModal>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-6">
|
||||||
{/* Header with Add Button */}
|
{/* Header with Add Button */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
import { SettingsIcon } from "lucide-react";
|
import { SettingsIcon } from "lucide-react";
|
||||||
|
|
||||||
import { AmazonS3Icon } from "@/components/icons/services/IconServices";
|
import { AmazonS3Icon } from "@/components/icons/services/IconServices";
|
||||||
@@ -19,7 +19,7 @@ export const S3IntegrationCard = () => {
|
|||||||
Amazon S3
|
Amazon S3
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
||||||
<p className="text-nowrap text-xs text-gray-500 dark:text-gray-300">
|
<p className="text-xs text-nowrap text-gray-500 dark:text-gray-300">
|
||||||
Export security findings to Amazon S3 buckets.
|
Export security findings to Amazon S3 buckets.
|
||||||
</p>
|
</p>
|
||||||
<CustomLink
|
<CustomLink
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Divider } from "@heroui/divider";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Divider } from "@nextui-org/react";
|
|
||||||
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
|
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -275,7 +275,7 @@ export const S3IntegrationForm = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Provider Selection */}
|
{/* Provider Selection */}
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<EnhancedProviderSelector
|
<EnhancedProviderSelector
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="providers"
|
name="providers"
|
||||||
@@ -291,7 +291,7 @@ export const S3IntegrationForm = ({
|
|||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{/* S3 Configuration */}
|
{/* S3 Configuration */}
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<CustomInput
|
<CustomInput
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="bucket_name"
|
name="bucket_name"
|
||||||
@@ -386,11 +386,11 @@ export const S3IntegrationForm = ({
|
|||||||
? handleNext
|
? handleNext
|
||||||
: form.handleSubmit(onSubmit)
|
: form.handleSubmit(onSubmit)
|
||||||
}
|
}
|
||||||
className="flex flex-col space-y-6"
|
className="flex flex-col gap-6"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
||||||
<p className="flex items-center gap-2 text-sm text-default-500">
|
<p className="text-default-500 flex items-center gap-2 text-sm">
|
||||||
Need help configuring your Amazon S3 integration?
|
Need help configuring your Amazon S3 integration?
|
||||||
</p>
|
</p>
|
||||||
<CustomLink
|
<CustomLink
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -214,7 +214,7 @@ export const S3IntegrationsManager = ({
|
|||||||
title="Delete S3 Integration"
|
title="Delete S3 Integration"
|
||||||
description="This action cannot be undone. This will permanently delete your S3 integration."
|
description="This action cannot be undone. This will permanently delete your S3 integration."
|
||||||
>
|
>
|
||||||
<div className="flex w-full justify-center space-x-6">
|
<div className="flex w-full justify-center gap-6">
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
ariaLabel="Cancel"
|
ariaLabel="Cancel"
|
||||||
@@ -271,7 +271,7 @@ export const S3IntegrationsManager = ({
|
|||||||
/>
|
/>
|
||||||
</CustomAlertModal>
|
</CustomAlertModal>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-6">
|
||||||
{/* Header with Add Button */}
|
{/* Header with Add Button */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
import {
|
||||||
import { useFormState } from "react-dom";
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useActionState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createSamlConfig, updateSamlConfig } from "@/actions/integrations";
|
import { createSamlConfig, updateSamlConfig } from "@/actions/integrations";
|
||||||
@@ -110,7 +116,7 @@ export const SamlConfigForm = ({
|
|||||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
samlConfig?: any;
|
samlConfig?: any;
|
||||||
}) => {
|
}) => {
|
||||||
const [state, formAction, isPending] = useFormState(
|
const [state, formAction, isPending] = useActionState(
|
||||||
samlConfig?.id ? updateSamlConfig : createSamlConfig,
|
samlConfig?.id ? updateSamlConfig : createSamlConfig,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -251,7 +257,7 @@ export const SamlConfigForm = ({
|
|||||||
: `${apiBaseUrl}/accounts/saml/your-domain.com/acs/`;
|
: `${apiBaseUrl}/accounts/saml/your-domain.com/acs/`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form ref={formRef} action={formAction} className="flex flex-col space-y-2">
|
<form ref={formRef} action={formAction} className="flex flex-col gap-2">
|
||||||
<div className="py-1 text-xs">
|
<div className="py-1 text-xs">
|
||||||
Need help configuring SAML SSO?{" "}
|
Need help configuring SAML SSO?{" "}
|
||||||
<CustomLink
|
<CustomLink
|
||||||
@@ -287,14 +293,14 @@ export const SamlConfigForm = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="space-y-4 rounded-lg bg-gray-50 p-4 dark:bg-gray-800">
|
<div className="flex flex-col gap-4 rounded-lg bg-gray-50 p-4 dark:bg-gray-800">
|
||||||
<h3 className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
Identity Provider Configuration
|
Identity Provider Configuration
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div>
|
<div>
|
||||||
<span className="mb-2 block text-sm font-medium text-default-500">
|
<span className="text-default-500 mb-2 block text-sm font-medium">
|
||||||
ACS URL:
|
ACS URL:
|
||||||
</span>
|
</span>
|
||||||
<SnippetChip
|
<SnippetChip
|
||||||
@@ -305,7 +311,7 @@ export const SamlConfigForm = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="mb-2 block text-sm font-medium text-default-500">
|
<span className="text-default-500 mb-2 block text-sm font-medium">
|
||||||
Audience:
|
Audience:
|
||||||
</span>
|
</span>
|
||||||
<SnippetChip
|
<SnippetChip
|
||||||
@@ -316,19 +322,19 @@ export const SamlConfigForm = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="mb-2 block text-sm font-medium text-default-500">
|
<span className="text-default-500 mb-2 block text-sm font-medium">
|
||||||
Name ID Format:
|
Name ID Format:
|
||||||
</span>
|
</span>
|
||||||
<span className="w-full text-sm text-default-600">
|
<span className="text-default-600 w-full text-sm">
|
||||||
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span className="mb-2 block text-sm font-medium text-default-500">
|
<span className="text-default-500 mb-2 block text-sm font-medium">
|
||||||
Supported Assertion Attributes:
|
Supported Assertion Attributes:
|
||||||
</span>
|
</span>
|
||||||
<ul className="ml-4 space-y-1 text-sm text-default-600">
|
<ul className="text-default-600 ml-4 flex flex-col gap-1 text-sm">
|
||||||
<li>• firstName</li>
|
<li>• firstName</li>
|
||||||
<li>• lastName</li>
|
<li>• lastName</li>
|
||||||
<li>• userType</li>
|
<li>• userType</li>
|
||||||
@@ -347,8 +353,8 @@ export const SamlConfigForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-start space-y-2">
|
<div className="flex flex-col items-start gap-2">
|
||||||
<span className="text-xs text-default-500">
|
<span className="text-default-500 text-xs">
|
||||||
Metadata XML File <span className="text-red-500">*</span>
|
Metadata XML File <span className="text-red-500">*</span>
|
||||||
</span>
|
</span>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
@@ -364,7 +370,7 @@ export const SamlConfigForm = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
startContent={<AddIcon size={20} />}
|
startContent={<AddIcon size={20} />}
|
||||||
className={`h-10 justify-start rounded-medium border-2 text-default-500 ${
|
className={`rounded-medium text-default-500 h-10 justify-start border-2 ${
|
||||||
(
|
(
|
||||||
clientErrors.metadata_xml === null
|
clientErrors.metadata_xml === null
|
||||||
? undefined
|
? undefined
|
||||||
@@ -378,7 +384,7 @@ export const SamlConfigForm = ({
|
|||||||
>
|
>
|
||||||
<span className="text-small">
|
<span className="text-small">
|
||||||
{uploadedFile ? (
|
{uploadedFile ? (
|
||||||
<span className="flex items-center space-x-2">
|
<span className="flex items-center gap-2">
|
||||||
<span className="max-w-36 truncate">{uploadedFile.name}</span>
|
<span className="max-w-36 truncate">{uploadedFile.name}</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
import { CheckIcon, Trash2Icon } from "lucide-react";
|
import { CheckIcon, Trash2Icon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
import { SettingsIcon } from "lucide-react";
|
import { SettingsIcon } from "lucide-react";
|
||||||
|
|
||||||
import { AWSSecurityHubIcon } from "@/components/icons/services/IconServices";
|
import { AWSSecurityHubIcon } from "@/components/icons/services/IconServices";
|
||||||
@@ -19,7 +19,7 @@ export const SecurityHubIntegrationCard = () => {
|
|||||||
AWS Security Hub
|
AWS Security Hub
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
||||||
<p className="text-nowrap text-xs text-gray-500 dark:text-gray-300">
|
<p className="text-xs text-nowrap text-gray-500 dark:text-gray-300">
|
||||||
Send security findings to AWS Security Hub.
|
Send security findings to AWS Security Hub.
|
||||||
</p>
|
</p>
|
||||||
<CustomLink
|
<CustomLink
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { Checkbox } from "@heroui/checkbox";
|
||||||
|
import { Divider } from "@heroui/divider";
|
||||||
|
import { Radio, RadioGroup } from "@heroui/radio";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Checkbox, Divider, Radio, RadioGroup } from "@nextui-org/react";
|
|
||||||
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
|
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
@@ -250,7 +252,7 @@ export const SecurityHubIntegrationForm = ({
|
|||||||
if (isEditingCredentials) {
|
if (isEditingCredentials) {
|
||||||
// When editing credentials, show the credential type selector first
|
// When editing credentials, show the credential type selector first
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="Credential type"
|
aria-label="Credential type"
|
||||||
@@ -319,7 +321,7 @@ export const SecurityHubIntegrationForm = ({
|
|||||||
<>
|
<>
|
||||||
{!isEditingConfig && (
|
{!isEditingConfig && (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<EnhancedProviderSelector
|
<EnhancedProviderSelector
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="provider_id"
|
name="provider_id"
|
||||||
@@ -473,11 +475,11 @@ export const SecurityHubIntegrationForm = ({
|
|||||||
? handleNext
|
? handleNext
|
||||||
: form.handleSubmit(onSubmit)
|
: form.handleSubmit(onSubmit)
|
||||||
}
|
}
|
||||||
className="flex flex-col space-y-6"
|
className="flex flex-col gap-6"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
|
||||||
<p className="flex items-center gap-2 text-sm text-default-500">
|
<p className="text-default-500 flex items-center gap-2 text-sm">
|
||||||
Need help configuring your AWS Security Hub integration?
|
Need help configuring your AWS Security Hub integration?
|
||||||
</p>
|
</p>
|
||||||
<CustomLink
|
<CustomLink
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader, Chip } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
|
import { Chip } from "@heroui/chip";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -263,7 +264,7 @@ export const SecurityHubIntegrationsManager = ({
|
|||||||
title="Delete Security Hub Integration"
|
title="Delete Security Hub Integration"
|
||||||
description="This action cannot be undone. This will permanently delete your Security Hub integration."
|
description="This action cannot be undone. This will permanently delete your Security Hub integration."
|
||||||
>
|
>
|
||||||
<div className="flex w-full justify-center space-x-6">
|
<div className="flex w-full justify-center gap-6">
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
ariaLabel="Cancel"
|
ariaLabel="Cancel"
|
||||||
@@ -321,7 +322,7 @@ export const SecurityHubIntegrationsManager = ({
|
|||||||
/>
|
/>
|
||||||
</CustomAlertModal>
|
</CustomAlertModal>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold">
|
<h3 className="text-lg font-semibold">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Chip } from "@nextui-org/react";
|
import { Chip } from "@heroui/chip";
|
||||||
import { ExternalLink } from "lucide-react";
|
import { ExternalLink } from "lucide-react";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react";
|
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||||
|
import { Skeleton } from "@heroui/skeleton";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
interface IntegrationSkeletonProps {
|
interface IntegrationSkeletonProps {
|
||||||
@@ -75,7 +76,7 @@ export const IntegrationSkeleton = ({
|
|||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
<Skeleton className="h-6 w-16 rounded-full" />
|
<Skeleton className="h-6 w-16 rounded-full" />
|
||||||
<Skeleton className="h-6 w-20 rounded-full" />
|
<Skeleton className="h-6 w-20 rounded-full" />
|
||||||
<Skeleton className="w-18 h-6 rounded-full" />
|
<Skeleton className="h-6 w-18 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<Skeleton className="h-3 w-32 rounded" />
|
<Skeleton className="h-3 w-32 rounded" />
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export const DeleteForm = ({
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmitClient)}>
|
<form onSubmit={form.handleSubmit(onSubmitClient)}>
|
||||||
<input type="hidden" name="id" value={invitationId} />
|
<input type="hidden" name="id" value={invitationId} />
|
||||||
<div className="flex w-full justify-center sm:space-x-6">
|
<div className="flex w-full justify-center sm:gap-6">
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
ariaLabel="Cancel"
|
ariaLabel="Cancel"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user