mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 04:27:29 +00:00
move account filter to backend & optimize totalCount logic
This commit is contained in:
@@ -36,7 +36,16 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
limit: z.coerce.number().min(1).max(100).default(100),
|
limit: z.coerce.number().min(1).max(100).default(100),
|
||||||
orderBy: z.nativeEnum(PamAccountOrderBy).default(PamAccountOrderBy.Name),
|
orderBy: z.nativeEnum(PamAccountOrderBy).default(PamAccountOrderBy.Name),
|
||||||
orderDirection: z.nativeEnum(OrderByDirection).default(OrderByDirection.ASC),
|
orderDirection: z.nativeEnum(OrderByDirection).default(OrderByDirection.ASC),
|
||||||
search: z.string().trim().optional()
|
search: z.string().trim().optional(),
|
||||||
|
filterResourceIds: z
|
||||||
|
.string()
|
||||||
|
.transform((val) =>
|
||||||
|
val
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -50,7 +59,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { projectId, accountPath, accountView, limit, offset, search, orderBy, orderDirection } = req.query;
|
const { projectId, accountPath, accountView, limit, offset, search, orderBy, orderDirection, filterResourceIds } =
|
||||||
|
req.query;
|
||||||
|
|
||||||
const { accounts, folders, totalCount, folderId, folderPaths } = await server.services.pamAccount.list({
|
const { accounts, folders, totalCount, folderId, folderPaths } = await server.services.pamAccount.list({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -64,7 +74,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => {
|
|||||||
offset,
|
offset,
|
||||||
search,
|
search,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection
|
orderDirection,
|
||||||
|
filterResourceIds
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ export const pamAccountDALFactory = (db: TDbClient) => {
|
|||||||
limit,
|
limit,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
orderBy = PamAccountOrderBy.Name,
|
orderBy = PamAccountOrderBy.Name,
|
||||||
orderDirection = OrderByDirection.ASC
|
orderDirection = OrderByDirection.ASC,
|
||||||
|
filterResourceIds
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
folderId?: string | null;
|
folderId?: string | null;
|
||||||
@@ -32,6 +33,7 @@ export const pamAccountDALFactory = (db: TDbClient) => {
|
|||||||
offset?: number;
|
offset?: number;
|
||||||
orderBy?: PamAccountOrderBy;
|
orderBy?: PamAccountOrderBy;
|
||||||
orderDirection?: OrderByDirection;
|
orderDirection?: OrderByDirection;
|
||||||
|
filterResourceIds?: string[];
|
||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
@@ -61,6 +63,10 @@ export const pamAccountDALFactory = (db: TDbClient) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterResourceIds && filterResourceIds.length) {
|
||||||
|
void query.whereIn(`${TableName.PamAccount}.resourceId`, filterResourceIds);
|
||||||
|
}
|
||||||
|
|
||||||
const countQuery = query.clone().count("*", { as: "count" }).first();
|
const countQuery = query.clone().count("*", { as: "count" }).first();
|
||||||
|
|
||||||
void query.select(selectAllTableCols(TableName.PamAccount)).select(
|
void query.select(selectAllTableCols(TableName.PamAccount)).select(
|
||||||
|
|||||||
@@ -374,14 +374,6 @@ export const pamAccountServiceFactory = ({
|
|||||||
});
|
});
|
||||||
totalFolderCount = totalCount;
|
totalFolderCount = totalCount;
|
||||||
}
|
}
|
||||||
const { totalCount: totalAccountCount } = await pamAccountDAL.findByProjectIdWithResourceDetails({
|
|
||||||
projectId,
|
|
||||||
folderId,
|
|
||||||
accountView,
|
|
||||||
search: params.search
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalCount = totalFolderCount + totalAccountCount;
|
|
||||||
|
|
||||||
let folders: TPamFolders[] = [];
|
let folders: TPamFolders[] = [];
|
||||||
if (canReadFolders && accountView === PamAccountView.Nested && offset < totalFolderCount) {
|
if (canReadFolders && accountView === PamAccountView.Nested && offset < totalFolderCount) {
|
||||||
@@ -402,10 +394,12 @@ export const pamAccountServiceFactory = ({
|
|||||||
let accountsWithResourceDetails: Awaited<
|
let accountsWithResourceDetails: Awaited<
|
||||||
ReturnType<typeof pamAccountDAL.findByProjectIdWithResourceDetails>
|
ReturnType<typeof pamAccountDAL.findByProjectIdWithResourceDetails>
|
||||||
>["accounts"] = [];
|
>["accounts"] = [];
|
||||||
|
let totalAccountCount = 0;
|
||||||
|
|
||||||
const accountsToFetch = limit - folders.length;
|
const accountsToFetch = limit - folders.length;
|
||||||
if (accountsToFetch > 0) {
|
if (accountsToFetch > 0) {
|
||||||
const accountOffset = Math.max(0, offset - totalFolderCount);
|
const accountOffset = Math.max(0, offset - totalFolderCount);
|
||||||
const { accounts: accountsResp } = await pamAccountDAL.findByProjectIdWithResourceDetails({
|
const { accounts, totalCount } = await pamAccountDAL.findByProjectIdWithResourceDetails({
|
||||||
projectId,
|
projectId,
|
||||||
folderId,
|
folderId,
|
||||||
accountView,
|
accountView,
|
||||||
@@ -413,11 +407,25 @@ export const pamAccountServiceFactory = ({
|
|||||||
limit: accountsToFetch,
|
limit: accountsToFetch,
|
||||||
search: params.search,
|
search: params.search,
|
||||||
orderBy: params.orderBy,
|
orderBy: params.orderBy,
|
||||||
orderDirection: params.orderDirection
|
orderDirection: params.orderDirection,
|
||||||
|
filterResourceIds: params.filterResourceIds
|
||||||
});
|
});
|
||||||
accountsWithResourceDetails = accountsResp;
|
accountsWithResourceDetails = accounts;
|
||||||
|
totalAccountCount = totalCount;
|
||||||
|
} else {
|
||||||
|
// if no accounts are to be fetched for the current page, we still need the total count for pagination
|
||||||
|
const { totalCount } = await pamAccountDAL.findByProjectIdWithResourceDetails({
|
||||||
|
projectId,
|
||||||
|
folderId,
|
||||||
|
accountView,
|
||||||
|
search: params.search,
|
||||||
|
filterResourceIds: params.filterResourceIds
|
||||||
|
});
|
||||||
|
totalAccountCount = totalCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalCount = totalFolderCount + totalAccountCount;
|
||||||
|
|
||||||
const decryptedAndPermittedAccounts: Array<
|
const decryptedAndPermittedAccounts: Array<
|
||||||
TPamAccounts & {
|
TPamAccounts & {
|
||||||
resource: Pick<TPamResources, "id" | "name" | "resourceType"> & { rotationCredentialsConfigured: boolean };
|
resource: Pick<TPamResources, "id" | "name" | "resourceType"> & { rotationCredentialsConfigured: boolean };
|
||||||
|
|||||||
@@ -30,4 +30,5 @@ export type TListAccountsDTO = {
|
|||||||
orderDirection?: OrderByDirection;
|
orderDirection?: OrderByDirection;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
|
filterResourceIds?: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ export type TDeletePamResourceDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Account DTOs
|
// Account DTOs
|
||||||
|
export type PamAccountFilter = {
|
||||||
|
resourceIds: string[];
|
||||||
|
};
|
||||||
|
|
||||||
export type TListPamAccountsDTO = {
|
export type TListPamAccountsDTO = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
accountPath?: string | null;
|
accountPath?: string | null;
|
||||||
@@ -88,6 +92,7 @@ export type TListPamAccountsDTO = {
|
|||||||
orderBy?: PamAccountOrderBy;
|
orderBy?: PamAccountOrderBy;
|
||||||
orderDirection?: OrderByDirection;
|
orderDirection?: OrderByDirection;
|
||||||
search?: string;
|
search?: string;
|
||||||
|
filterResourceIds?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCreatePamAccountDTO = Pick<
|
export type TCreatePamAccountDTO = Pick<
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ import { usePagination, usePopUp } from "@app/hooks";
|
|||||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||||
import {
|
import {
|
||||||
PAM_RESOURCE_TYPE_MAP,
|
PAM_RESOURCE_TYPE_MAP,
|
||||||
|
PamAccountFilter,
|
||||||
PamAccountOrderBy,
|
PamAccountOrderBy,
|
||||||
PamAccountView,
|
PamAccountView,
|
||||||
TPamAccount,
|
|
||||||
TPamFolder
|
TPamFolder
|
||||||
} from "@app/hooks/api/pam";
|
} from "@app/hooks/api/pam";
|
||||||
import { useListPamAccounts } from "@app/hooks/api/pam/queries";
|
import { useListPamAccounts, useListPamResources } from "@app/hooks/api/pam/queries";
|
||||||
|
|
||||||
import { AccountViewToggle } from "./AccountViewToggle";
|
import { AccountViewToggle } from "./AccountViewToggle";
|
||||||
import { FolderBreadCrumbs } from "./FolderBreadCrumbs";
|
import { FolderBreadCrumbs } from "./FolderBreadCrumbs";
|
||||||
@@ -69,10 +69,6 @@ import { PamFolderRow } from "./PamFolderRow";
|
|||||||
import { PamUpdateAccountModal } from "./PamUpdateAccountModal";
|
import { PamUpdateAccountModal } from "./PamUpdateAccountModal";
|
||||||
import { PamUpdateFolderModal } from "./PamUpdateFolderModal";
|
import { PamUpdateFolderModal } from "./PamUpdateFolderModal";
|
||||||
|
|
||||||
type Filters = {
|
|
||||||
resource: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
@@ -103,8 +99,8 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
initAccountView ?? PamAccountView.Flat
|
initAccountView ?? PamAccountView.Flat
|
||||||
);
|
);
|
||||||
|
|
||||||
const [filters, setFilters] = useState<Filters>({
|
const [filter, setFilter] = useState<PamAccountFilter>({
|
||||||
resource: []
|
resourceIds: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -139,7 +135,8 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
limit: perPage,
|
limit: perPage,
|
||||||
search: debouncedSearch,
|
search: debouncedSearch,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection
|
orderDirection,
|
||||||
|
filterResourceIds: filter.resourceIds.length ? filter.resourceIds.join(",") : undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
const accounts = data?.accounts || [];
|
const accounts = data?.accounts || [];
|
||||||
@@ -166,7 +163,7 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
resource: { name: resourceName, id: resourceId }
|
resource: { name: resourceName, id: resourceId }
|
||||||
} = account;
|
} = account;
|
||||||
|
|
||||||
if (filters.resource.length && !filters.resource.includes(resourceId)) {
|
if (filter.resourceIds.length && !filter.resourceIds.includes(resourceId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +175,7 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
(description || "").toLowerCase().includes(searchValue)
|
(description || "").toLowerCase().includes(searchValue)
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
[accounts, search, filters]
|
[accounts, search, filter]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSort = (column: PamAccountOrderBy) => {
|
const handleSort = (column: PamAccountOrderBy) => {
|
||||||
@@ -197,7 +194,7 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
const getColSortIcon = (col: PamAccountOrderBy) =>
|
const getColSortIcon = (col: PamAccountOrderBy) =>
|
||||||
orderDirection === OrderByDirection.DESC && orderBy === col ? faArrowUp : faArrowDown;
|
orderDirection === OrderByDirection.DESC && orderBy === col ? faArrowUp : faArrowDown;
|
||||||
|
|
||||||
const isTableFiltered = Boolean(filters.resource.length);
|
const isTableFiltered = Boolean(filter.resourceIds.length);
|
||||||
|
|
||||||
const handleFolderClick = (folder: TPamFolder) => {
|
const handleFolderClick = (folder: TPamFolder) => {
|
||||||
if (accountView === PamAccountView.Flat) {
|
if (accountView === PamAccountView.Flat) {
|
||||||
@@ -210,13 +207,13 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
const isContentEmpty = !filteredAccounts.length && !foldersToRender.length;
|
const isContentEmpty = !filteredAccounts.length && !foldersToRender.length;
|
||||||
const isSearchEmpty = isContentEmpty && (Boolean(search) || isTableFiltered);
|
const isSearchEmpty = isContentEmpty && (Boolean(search) || isTableFiltered);
|
||||||
|
|
||||||
const uniqueResources = useMemo(() => {
|
const { data: resourcesData } = useListPamResources({
|
||||||
const resourceMap = new Map<string, TPamAccount["resource"]>();
|
projectId,
|
||||||
accounts.forEach((account) => {
|
// temporarily returning a large number until we rework table filtering
|
||||||
resourceMap.set(account.resource.id, account.resource);
|
limit: 100
|
||||||
});
|
});
|
||||||
return Array.from(resourceMap.values());
|
|
||||||
}, [accounts]);
|
const resources = resourcesData?.resources || [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -229,7 +226,7 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
value={accountView}
|
value={accountView}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setPage(1);
|
setPage(1);
|
||||||
setFilters({ resource: [] });
|
setFilter({ resourceIds: [] });
|
||||||
setAccountView(e);
|
setAccountView(e);
|
||||||
navigate({
|
navigate({
|
||||||
search: (prev) => ({
|
search: (prev) => ({
|
||||||
@@ -273,25 +270,25 @@ export const PamAccountsTable = ({ projectId }: Props) => {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="max-h-[70vh] thin-scrollbar overflow-y-auto" align="end">
|
<DropdownMenuContent className="max-h-[70vh] thin-scrollbar overflow-y-auto" align="end">
|
||||||
<DropdownMenuLabel>Resource</DropdownMenuLabel>
|
<DropdownMenuLabel>Resource</DropdownMenuLabel>
|
||||||
{uniqueResources.length ? (
|
{resources.length ? (
|
||||||
uniqueResources.map((resource) => {
|
resources.map((resource) => {
|
||||||
const { name, image } = PAM_RESOURCE_TYPE_MAP[resource.resourceType];
|
const { name, image } = PAM_RESOURCE_TYPE_MAP[resource.resourceType];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newResources = filters.resource.includes(resource.id)
|
const newResources = filter.resourceIds.includes(resource.id)
|
||||||
? filters.resource.filter((a) => a !== resource.id)
|
? filter.resourceIds.filter((a) => a !== resource.id)
|
||||||
: [...filters.resource, resource.id];
|
: [...filter.resourceIds, resource.id];
|
||||||
setFilters((prev) => ({
|
setFilter((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
resource: newResources
|
resourceIds: newResources
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
key={resource.id}
|
key={resource.id}
|
||||||
icon={
|
icon={
|
||||||
filters.resource.includes(resource.id) && (
|
filter.resourceIds.includes(resource.id) && (
|
||||||
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
<FontAwesomeIcon className="text-primary" icon={faCheckCircle} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user