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:
Pablo Lara
2024-09-11 16:54:24 +02:00
committed by GitHub
11 changed files with 219 additions and 49 deletions
+13 -14
View File
@@ -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 && (
+111
View File
@@ -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>
);
};
+27 -13
View File
@@ -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>
);
};
+1
View File
@@ -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";
+18 -3
View File
@@ -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} />
}
+16
View File
@@ -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
View File
@@ -1,3 +1,4 @@
export * from "./CustomBox";
export * from "./CustomButtonClientAction";
export * from "./CustomInput";
export * from "./CustomLoader";
-1
View File
@@ -1,2 +1 @@
export * from "./useDebounce";
export * from "./useLocalStorage";
-15
View File
@@ -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;
}