mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
Merge pull request #56 from prowler-cloud/PRWLR-4776-Implementing-check-connection-real-CASE
Starting implementing check connection and style tweaks for the Provider's table
This commit is contained in:
@@ -1,17 +1,14 @@
|
||||
import { Input } from "@nextui-org/react";
|
||||
import debounce from "lodash.debounce";
|
||||
import { XCircle } from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { useDebounce } from "../../hooks/useDebounce";
|
||||
|
||||
export const CustomSearchInput: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [searchQuery, setSearchQuery] = useState(() => {
|
||||
return searchParams.get("filter[search]") || "";
|
||||
});
|
||||
const debouncedSearchQuery = useDebounce(searchQuery, 300);
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const applySearch = useCallback(
|
||||
(query: string) => {
|
||||
@@ -21,19 +18,22 @@ export const CustomSearchInput: React.FC = () => {
|
||||
} else {
|
||||
params.delete("filter[search]");
|
||||
}
|
||||
router.replace(`?${params.toString()}`, { scroll: false });
|
||||
router.push(`?${params.toString()}`, { scroll: false });
|
||||
},
|
||||
[router, searchParams],
|
||||
);
|
||||
|
||||
const debouncedChangeHandler = useCallback(debounce(applySearch, 300), []);
|
||||
|
||||
const clearIconSearch = () => {
|
||||
setSearchQuery("");
|
||||
applySearch("");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
applySearch(debouncedSearchQuery);
|
||||
}, [debouncedSearchQuery, applySearch]);
|
||||
const searchFromUrl = searchParams.get("filter[search]") || "";
|
||||
setSearchQuery(searchFromUrl);
|
||||
}, [searchParams]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
@@ -42,11 +42,10 @@ export const CustomSearchInput: React.FC = () => {
|
||||
label="Search"
|
||||
labelPlacement="inside"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
applySearch(searchQuery);
|
||||
}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
setSearchQuery(value);
|
||||
debouncedChangeHandler(value);
|
||||
}}
|
||||
endContent={
|
||||
searchQuery && (
|
||||
|
||||
@@ -464,6 +464,117 @@ export const NotificationIcon: React.FC<IconSvgProps> = ({
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const IdIcon: React.FC<IconSvgProps> = ({
|
||||
size = 24,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size || height || 24}
|
||||
viewBox="0 0 16 16"
|
||||
width={size || width || 24}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.854 2.146a.5.5 0 0 0-.708.708L13.293 5H9.5a2 2 0 0 0-2 2v2a1 1 0 0 1-1 1h-.55a2.5 2.5 0 1 0 0 1h.55a2 2 0 0 0 2-2V7a1 1 0 0 1 1-1h3.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708zM5 10.5a1.5 1.5 0 1 1-3 0a1.5 1.5 0 0 1 3 0"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const DoneIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
width={size || width || 24}
|
||||
height={size || height || 24}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path d="m2.394 13.742 4.743 3.62 7.616-8.704-1.506-1.316-6.384 7.296-3.257-2.486zm19.359-5.084-1.506-1.316-6.369 7.279-.753-.602-1.25 1.562 2.247 1.798z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const CopyIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
fill="none"
|
||||
height={size || height || 20}
|
||||
shapeRendering="geometricPrecision"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 24 24"
|
||||
width={size || width || 20}
|
||||
{...props}
|
||||
>
|
||||
<path d="M6 17C4.89543 17 4 16.1046 4 15V5C4 3.89543 4.89543 3 6 3H13C13.7403 3 14.3866 3.4022 14.7324 4M11 21H18C19.1046 21 20 20.1046 20 19V9C20 7.89543 19.1046 7 18 7H11C9.89543 7 9 7.89543 9 9V19C9 20.1046 9.89543 21 11 21Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const FlowIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size || height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
width={size || width || 20}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M16.4 4a2.4 2.4 0 1 0-4.8 0c0 .961.568 1.784 1.384 2.167c-.082 1.584-1.27 2.122-3.335 2.896c-.87.327-1.829.689-2.649 1.234V6.176A2.396 2.396 0 0 0 6 1.6a2.397 2.397 0 0 0-1 4.576v7.649A2.39 2.39 0 0 0 3.6 16a2.4 2.4 0 1 0 4.8 0c0-.961-.568-1.784-1.384-2.167c.082-1.583 1.271-2.122 3.335-2.896c2.03-.762 4.541-1.711 4.64-4.756A2.4 2.4 0 0 0 16.4 4M6 2.615a1.384 1.384 0 1 1 0 2.768a1.384 1.384 0 0 1 0-2.768m0 14.77a1.385 1.385 0 1 1 0-2.77a1.385 1.385 0 0 1 0 2.77m8-12a1.385 1.385 0 1 1 0-2.77a1.385 1.385 0 0 1 0 2.77"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const ConnectionIcon: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
height,
|
||||
width,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
height={size || height || 20}
|
||||
viewBox="0 0 20 20"
|
||||
width={size || width || 20}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M18 14.824V12.5A3.5 3.5 0 0 0 14.5 9h-2A1.5 1.5 0 0 1 11 7.5V5.176A2.4 2.4 0 0 0 12.4 3a2.4 2.4 0 1 0-4.8 0c0 .967.576 1.796 1.4 2.176V7.5A1.5 1.5 0 0 1 7.5 9h-2A3.5 3.5 0 0 0 2 12.5v2.324A2.396 2.396 0 0 0 3 19.4a2.397 2.397 0 0 0 1-4.576V12.5A1.5 1.5 0 0 1 5.5 11h2c.539 0 1.044-.132 1.5-.35v4.174a2.396 2.396 0 0 0 1 4.576a2.397 2.397 0 0 0 1-4.576V10.65c.456.218.961.35 1.5.35h2a1.5 1.5 0 0 1 1.5 1.5v2.324A2.4 2.4 0 0 0 14.6 17a2.4 2.4 0 1 0 4.8 0c0-.967-.575-1.796-1.4-2.176M10 1.615a1.384 1.384 0 1 1 0 2.768a1.384 1.384 0 0 1 0-2.768m-7 16.77a1.385 1.385 0 1 1 0-2.77a1.385 1.385 0 0 1 0 2.77m7 0a1.385 1.385 0 1 1 0-2.77a1.385 1.385 0 0 1 0 2.77m7 0a1.385 1.385 0 1 1 0-2.77a1.385 1.385 0 0 1 0 2.77"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const SuccessIcon: React.FC<IconSvgProps> = ({
|
||||
size = 24,
|
||||
width,
|
||||
|
||||
@@ -27,15 +27,16 @@ export const CheckConnectionProvider = ({ id }: { id: string }) => {
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "Success!",
|
||||
description: "The connection was updated successfully.",
|
||||
title: "Checking",
|
||||
description: "The task was launched successfully",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form ref={ref} action={clientAction} className="flex gap-x-2">
|
||||
<input type="hidden" name="id" value={id} />
|
||||
<CustomButtonClientAction buttonLabel="Check Connection" />
|
||||
<CustomButtonClientAction buttonLabel="Check connection" />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,35 +1,47 @@
|
||||
import React from "react";
|
||||
|
||||
import { WifiIcon, WifiOffIcon, WifiPendingIcon } from "../icons";
|
||||
import { ConnectionIcon } from "../icons";
|
||||
import {
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
} from "../icons/providers-badge";
|
||||
import { CustomLoader } from "../ui/custom";
|
||||
|
||||
interface ProviderInfoProps {
|
||||
connected: boolean | null;
|
||||
provider: "aws" | "azure" | "gcp" | "kubernetes";
|
||||
providerAlias: string;
|
||||
providerId: string;
|
||||
}
|
||||
|
||||
export const ProviderInfo: React.FC<ProviderInfoProps> = ({
|
||||
connected,
|
||||
provider,
|
||||
providerAlias,
|
||||
providerId,
|
||||
}) => {
|
||||
const getIcon = () => {
|
||||
switch (connected) {
|
||||
case true:
|
||||
return <WifiIcon size={22} />;
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-medium border p-1 bg-system-success-lighter border-system-success">
|
||||
<ConnectionIcon className="text-system-success" size={24} />
|
||||
</div>
|
||||
);
|
||||
case false:
|
||||
return <WifiOffIcon size={22} />;
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-medium border p-1 bg-system-error-lighter border-danger">
|
||||
<ConnectionIcon className="text-danger" size={24} />
|
||||
</div>
|
||||
);
|
||||
case null:
|
||||
return (
|
||||
<div className="flex items-center justify-center rounded-medium border p-1 bg-info-lighter border-info-lighter">
|
||||
<ConnectionIcon className="text-info" size={24} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <WifiPendingIcon size={22} />;
|
||||
return <ConnectionIcon size={24} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -49,13 +61,15 @@ export const ProviderInfo: React.FC<ProviderInfoProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-fit">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0">{getIcon()}</div>
|
||||
<div className="flex-shrink-0 mx-2">{getProviderLogo()}</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-md font-semibold">{providerAlias}</span>
|
||||
<span className="text-sm text-gray-500">{providerId}</span>
|
||||
<div className="max-w-96">
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0">{getProviderLogo()}</div>
|
||||
<div className="flex-shrink-0">{getIcon()}</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-md font-semibold">{providerAlias}</span>
|
||||
{/* <CustomLoader size="small" /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Snippet } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { CopyIcon, DoneIcon, IdIcon } from "../icons";
|
||||
|
||||
interface SnippetIdProviderProps {
|
||||
providerId: string;
|
||||
}
|
||||
export const SnippetIdProvider: React.FC<SnippetIdProviderProps> = ({
|
||||
providerId,
|
||||
}) => {
|
||||
return (
|
||||
<Snippet
|
||||
className="flex items-center py-0"
|
||||
size="sm"
|
||||
variant="flat"
|
||||
radius="lg"
|
||||
hideSymbol
|
||||
copyIcon={<CopyIcon size={16} />}
|
||||
checkIcon={<DoneIcon size={16} />}
|
||||
>
|
||||
<p className="flex items-center space-x-2">
|
||||
<IdIcon size={16} />
|
||||
<span className="text-sm max-w-24 overflow-x-scroll">{providerId}</span>
|
||||
</p>
|
||||
</Snippet>
|
||||
);
|
||||
};
|
||||
@@ -7,6 +7,7 @@ export * from "./DateWithTime";
|
||||
export * from "./DeleteProvider";
|
||||
export * from "./ProviderInfo";
|
||||
export * from "./ScanStatus";
|
||||
export * from "./SnippetIdProvider";
|
||||
export * from "./table/ColumnsProvider";
|
||||
export * from "./table/DataTableColumnHeader";
|
||||
export * from "./table/DataTablePagination";
|
||||
|
||||
@@ -31,12 +31,14 @@ import {
|
||||
EditDocumentBulkIcon,
|
||||
} from "@nextui-org/shared-icons";
|
||||
|
||||
import { SnippetIdProvider } from "../SnippetIdProvider";
|
||||
|
||||
const iconClasses =
|
||||
"text-2xl text-default-500 pointer-events-none flex-shrink-0";
|
||||
|
||||
export const ColumnsProvider: ColumnDef<ProviderProps>[] = [
|
||||
{
|
||||
header: "ID",
|
||||
header: "n",
|
||||
cell: ({ row }) => <p className="text-medium">{row.index + 1}</p>,
|
||||
},
|
||||
{
|
||||
@@ -46,18 +48,29 @@ export const ColumnsProvider: ColumnDef<ProviderProps>[] = [
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const {
|
||||
attributes: { connection, provider, alias, provider_id },
|
||||
attributes: { connection, provider, alias },
|
||||
} = getProviderData(row);
|
||||
return (
|
||||
<ProviderInfo
|
||||
connected={connection.connected}
|
||||
provider={provider}
|
||||
providerAlias={alias}
|
||||
providerId={provider_id}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={"Id"} param="provider_id" />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const {
|
||||
attributes: { provider_id },
|
||||
} = getProviderData(row);
|
||||
return <SnippetIdProvider providerId={provider_id} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Scan Status",
|
||||
@@ -144,6 +157,7 @@ export const ColumnsProvider: ColumnDef<ProviderProps>[] = [
|
||||
key="new"
|
||||
description="Check the connection to the provider"
|
||||
shortcut="⌘N"
|
||||
textValue="Check Connection"
|
||||
startContent={<AddNoteBulkIcon className={iconClasses} />}
|
||||
>
|
||||
<CheckConnectionProvider id={id} />
|
||||
@@ -152,6 +166,7 @@ export const ColumnsProvider: ColumnDef<ProviderProps>[] = [
|
||||
key="edit"
|
||||
description="Allows you to edit the provider"
|
||||
shortcut="⌘⇧E"
|
||||
textValue="Edit Provider"
|
||||
startContent={
|
||||
<EditDocumentBulkIcon className={iconClasses} />
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const CustomLoader = (props: { size?: "small" }) => {
|
||||
const [loadingSpinner, setloadingSpinner] = useState(0);
|
||||
const loadingChars = "|/-\\";
|
||||
|
||||
const textClasses = `w-xs px-xs ${props.size === "small" ? "!text-s" : ""}`;
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => setloadingSpinner(loadingSpinner + 1), 150);
|
||||
}, [loadingSpinner]);
|
||||
|
||||
return <p className={textClasses}>{loadingChars[loadingSpinner % 4]}</p>;
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./CustomBox";
|
||||
export * from "./CustomButtonClientAction";
|
||||
export * from "./CustomInput";
|
||||
export * from "./CustomLoader";
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./useDebounce";
|
||||
export * from "./useLocalStorage";
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useDebounce<T>(value: T, delay?: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedValue(value), delay ?? 900);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
Reference in New Issue
Block a user