mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 14:27:59 +00:00
improvements: minor ui adjustments/additions and pagination for access request table
This commit is contained in:
@@ -1,12 +1,20 @@
|
|||||||
|
import { IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
||||||
|
import { faArrowRightToBracket, faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
import { PolicyType } from "@app/hooks/api/policies/enums";
|
import { PolicyType } from "@app/hooks/api/policies/enums";
|
||||||
|
|
||||||
export const policyDetails: Record<PolicyType, { name: string; className: string }> = {
|
export const policyDetails: Record<
|
||||||
|
PolicyType,
|
||||||
|
{ name: string; className: string; icon: IconDefinition }
|
||||||
|
> = {
|
||||||
[PolicyType.AccessPolicy]: {
|
[PolicyType.AccessPolicy]: {
|
||||||
className: "bg-green/20 text-green",
|
className: "bg-green/20 text-green",
|
||||||
name: "Access Policy"
|
name: "Access Policy",
|
||||||
|
icon: faArrowRightToBracket
|
||||||
},
|
},
|
||||||
[PolicyType.ChangePolicy]: {
|
[PolicyType.ChangePolicy]: {
|
||||||
className: "bg-yellow/20 text-yellow",
|
className: "bg-yellow/20 text-yellow",
|
||||||
name: "Change Policy"
|
name: "Change Policy",
|
||||||
|
icon: faEdit
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+56
-20
@@ -7,6 +7,7 @@ import {
|
|||||||
faCheck,
|
faCheck,
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
|
faClipboardCheck,
|
||||||
faLock,
|
faLock,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faPlus,
|
faPlus,
|
||||||
@@ -28,6 +29,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
Input,
|
Input,
|
||||||
|
Pagination,
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { Badge } from "@app/components/v2/Badge";
|
import { Badge } from "@app/components/v2/Badge";
|
||||||
@@ -39,7 +41,12 @@ import {
|
|||||||
useUser,
|
useUser,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { usePopUp } from "@app/hooks";
|
import {
|
||||||
|
getUserTablePreference,
|
||||||
|
PreferenceKey,
|
||||||
|
setUserTablePreference
|
||||||
|
} from "@app/helpers/userTablePreferences";
|
||||||
|
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||||
import { useGetWorkspaceUsers } from "@app/hooks/api";
|
import { useGetWorkspaceUsers } from "@app/hooks/api";
|
||||||
import {
|
import {
|
||||||
accessApprovalKeys,
|
accessApprovalKeys,
|
||||||
@@ -59,7 +66,7 @@ const generateRequestText = (request: TAccessApprovalRequest) => {
|
|||||||
const { isTemporary } = request;
|
const { isTemporary } = request;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between text-sm">
|
||||||
<div>
|
<div>
|
||||||
Requested {isTemporary ? "temporary" : "permanent"} access to{" "}
|
Requested {isTemporary ? "temporary" : "permanent"} access to{" "}
|
||||||
<code className="mx-1 rounded bg-mineshaft-600 px-1.5 py-0.5 font-mono text-[13px] text-mineshaft-200">
|
<code className="mx-1 rounded bg-mineshaft-600 px-1.5 py-0.5 font-mono text-[13px] text-mineshaft-200">
|
||||||
@@ -130,7 +137,14 @@ export const AccessApprovalRequest = ({
|
|||||||
envSlug: envFilter
|
envSlug: envFilter
|
||||||
});
|
});
|
||||||
|
|
||||||
const [searchFilter, setSearchFilter] = useState("");
|
const { search, setSearch, setPage, page, perPage, setPerPage, offset } = usePagination("", {
|
||||||
|
initPerPage: getUserTablePreference("accessRequestsTable", PreferenceKey.PerPage, 20)
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePerPageChange = (newPerPage: number) => {
|
||||||
|
setPerPage(newPerPage);
|
||||||
|
setUserTablePreference("accessRequestsTable", PreferenceKey.PerPage, newPerPage);
|
||||||
|
};
|
||||||
|
|
||||||
const filteredRequests = useMemo(() => {
|
const filteredRequests = useMemo(() => {
|
||||||
let accessRequests: typeof requests;
|
let accessRequests: typeof requests;
|
||||||
@@ -150,19 +164,27 @@ export const AccessApprovalRequest = ({
|
|||||||
request.reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED)
|
request.reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED)
|
||||||
);
|
);
|
||||||
|
|
||||||
return accessRequests?.filter((request) => {
|
return (
|
||||||
const { environmentName, requestedByUser } = request;
|
accessRequests?.filter((request) => {
|
||||||
|
const { environmentName, requestedByUser } = request;
|
||||||
|
|
||||||
const searchValue = searchFilter.trim().toLowerCase();
|
const searchValue = search.trim().toLowerCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
environmentName?.toLowerCase().includes(searchValue) ||
|
environmentName?.toLowerCase().includes(searchValue) ||
|
||||||
`${requestedByUser?.email ?? ""} ${requestedByUser?.firstName ?? ""} ${requestedByUser?.lastName ?? ""}`
|
`${requestedByUser?.email ?? ""} ${requestedByUser?.firstName ?? ""} ${requestedByUser?.lastName ?? ""}`
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(searchValue)
|
.includes(searchValue)
|
||||||
);
|
);
|
||||||
});
|
}) ?? []
|
||||||
}, [requests, statusFilter, requestedByFilter, envFilter, searchFilter]);
|
);
|
||||||
|
}, [requests, statusFilter, requestedByFilter, envFilter, search]);
|
||||||
|
|
||||||
|
useResetPageHelper({
|
||||||
|
totalCount: filteredRequests.length,
|
||||||
|
offset,
|
||||||
|
setPage
|
||||||
|
});
|
||||||
|
|
||||||
const generateRequestDetails = useCallback(
|
const generateRequestDetails = useCallback(
|
||||||
(request: TAccessApprovalRequest) => {
|
(request: TAccessApprovalRequest) => {
|
||||||
@@ -299,8 +321,8 @@ export const AccessApprovalRequest = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
value={searchFilter}
|
value={search}
|
||||||
onChange={(e) => setSearchFilter(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
placeholder="Search approval requests by requesting user or environment..."
|
placeholder="Search approval requests by requesting user or environment..."
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
@@ -423,7 +445,7 @@ export const AccessApprovalRequest = ({
|
|||||||
<EmptyState title="No Requests Match Search" icon={faSearch} />
|
<EmptyState title="No Requests Match Search" icon={faSearch} />
|
||||||
)}
|
)}
|
||||||
{!!filteredRequests?.length &&
|
{!!filteredRequests?.length &&
|
||||||
filteredRequests?.map((request) => {
|
filteredRequests?.slice(offset, perPage * page).map((request) => {
|
||||||
const details = generateRequestDetails(request);
|
const details = generateRequestDetails(request);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -463,21 +485,35 @@ export const AccessApprovalRequest = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
{request.requestedByUserId === user.id && (
|
{request.requestedByUserId === user.id && (
|
||||||
<div className="flex items-center gap-1.5 whitespace-nowrap text-xs text-bunker-300">
|
<div className="flex items-center gap-1.5 whitespace-nowrap text-xs text-bunker-300">
|
||||||
<FontAwesomeIcon icon={faUser} size="sm" />
|
<FontAwesomeIcon icon={faUser} size="sm" />
|
||||||
<span>Requested By You</span>
|
<span>Requested By You</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Badge className="whitespace-nowrap" variant={details.displayData.type}>
|
<Badge
|
||||||
{details.displayData.label}
|
className="flex items-center gap-1.5 whitespace-nowrap"
|
||||||
|
variant={details.displayData.type}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faClipboardCheck} />
|
||||||
|
<span>{details.displayData.label}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{Boolean(filteredRequests.length) && (
|
||||||
|
<Pagination
|
||||||
|
className="border-none"
|
||||||
|
count={filteredRequests.length}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={setPage}
|
||||||
|
onChangePerPage={handlePerPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!!policies && (
|
{!!policies && (
|
||||||
|
|||||||
+9
-2
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { faEdit, faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import { faEdit, faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import {
|
||||||
@@ -107,8 +108,14 @@ export const ApprovalPolicyRow = ({
|
|||||||
<Td>{policy.environment.name}</Td>
|
<Td>{policy.environment.name}</Td>
|
||||||
<Td>{policy.secretPath || "*"}</Td>
|
<Td>{policy.secretPath || "*"}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Badge className={policyDetails[policy.policyType].className}>
|
<Badge
|
||||||
{policyDetails[policy.policyType].name}
|
className={twMerge(
|
||||||
|
policyDetails[policy.policyType].className,
|
||||||
|
"flex w-min items-center gap-1.5 whitespace-nowrap"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={policyDetails[policy.policyType].icon} />
|
||||||
|
<span>{policyDetails[policy.policyType].name}</span>
|
||||||
</Badge>
|
</Badge>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
|
|||||||
+1
-1
@@ -316,7 +316,7 @@ export const SecretApprovalRequest = () => {
|
|||||||
if (evt.key === "Enter") setSelectedApprovalId(secretApproval.id);
|
if (evt.key === "Enter") setSelectedApprovalId(secretApproval.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="mb-1">
|
<div className="mb-1 text-sm">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faCodeBranch}
|
icon={faCodeBranch}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
Reference in New Issue
Block a user