mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 13:26:16 +00:00
Add pagination for certificates table
This commit is contained in:
@@ -356,15 +356,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe("The slug of the project to list certificates.")
|
slug: slugSchema.describe("The slug of the project to list certificates.")
|
||||||
}),
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
offset: z.coerce.number().min(0).max(100).default(0),
|
||||||
|
limit: z.coerce.number().min(1).max(100).default(25)
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
certificates: z.array(CertificatesSchema)
|
certificates: z.array(CertificatesSchema),
|
||||||
|
totalCount: z.number()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const certificates = await server.services.project.listProjectCertificates({
|
const { certificates, totalCount } = await server.services.project.listProjectCertificates({
|
||||||
filter: {
|
filter: {
|
||||||
slug: req.params.slug,
|
slug: req.params.slug,
|
||||||
orgId: req.permission.orgId,
|
orgId: req.permission.orgId,
|
||||||
@@ -373,9 +378,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actor: req.permission.type
|
actor: req.permission.type,
|
||||||
|
...req.query
|
||||||
});
|
});
|
||||||
return { certificates };
|
return { certificates, totalCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,34 @@
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TCertificateDALFactory = ReturnType<typeof certificateDALFactory>;
|
export type TCertificateDALFactory = ReturnType<typeof certificateDALFactory>;
|
||||||
|
|
||||||
export const certificateDALFactory = (db: TDbClient) => {
|
export const certificateDALFactory = (db: TDbClient) => {
|
||||||
const certificateOrm = ormify(db, TableName.Certificate);
|
const certificateOrm = ormify(db, TableName.Certificate);
|
||||||
return certificateOrm;
|
|
||||||
|
const countCertificatesInProject = async (projectId: string) => {
|
||||||
|
try {
|
||||||
|
interface CountResult {
|
||||||
|
count: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = await db(TableName.Certificate)
|
||||||
|
.join(TableName.CertificateAuthority, `${TableName.Certificate}.caId`, `${TableName.CertificateAuthority}.id`)
|
||||||
|
.join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`)
|
||||||
|
.where(`${TableName.Project}.id`, projectId)
|
||||||
|
.count("*")
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return parseInt((count as unknown as CountResult).count || "0", 10);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Count all project certificates" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...certificateOrm,
|
||||||
|
countCertificatesInProject
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
TDeleteProjectDTO,
|
TDeleteProjectDTO,
|
||||||
TGetProjectDTO,
|
TGetProjectDTO,
|
||||||
TListProjectCasDTO,
|
TListProjectCasDTO,
|
||||||
|
TListProjectCertsDTO,
|
||||||
TToggleProjectAutoCapitalizationDTO,
|
TToggleProjectAutoCapitalizationDTO,
|
||||||
TUpdateProjectDTO,
|
TUpdateProjectDTO,
|
||||||
TUpdateProjectNameDTO,
|
TUpdateProjectNameDTO,
|
||||||
@@ -68,7 +69,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "create">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "create">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
@@ -569,12 +570,14 @@ export const projectServiceFactory = ({
|
|||||||
* Return list of certificates for project
|
* Return list of certificates for project
|
||||||
*/
|
*/
|
||||||
const listProjectCertificates = async ({
|
const listProjectCertificates = async ({
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
filter,
|
filter,
|
||||||
actor
|
actor
|
||||||
}: TListProjectCasDTO) => {
|
}: TListProjectCertsDTO) => {
|
||||||
const project = await projectDAL.findProjectByFilter(filter);
|
const project = await projectDAL.findProjectByFilter(filter);
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -589,12 +592,21 @@ export const projectServiceFactory = ({
|
|||||||
|
|
||||||
const cas = await certificateAuthorityDAL.find({ projectId: project.id });
|
const cas = await certificateAuthorityDAL.find({ projectId: project.id });
|
||||||
|
|
||||||
const certificates = await certificateDAL.find({
|
const certificates = await certificateDAL.find(
|
||||||
$in: {
|
{
|
||||||
caId: cas.map((ca) => ca.id)
|
$in: {
|
||||||
}
|
caId: cas.map((ca) => ca.id)
|
||||||
});
|
}
|
||||||
return certificates;
|
},
|
||||||
|
{ offset, limit, sort: [["updatedAt", "desc"]] }
|
||||||
|
);
|
||||||
|
|
||||||
|
const count = await certificateDAL.countCertificatesInProject(project.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificates,
|
||||||
|
totalCount: count
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -86,3 +86,9 @@ export type TListProjectCasDTO = {
|
|||||||
status?: CaStatus;
|
status?: CaStatus;
|
||||||
filter: Filter;
|
filter: Filter;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TListProjectCertsDTO = {
|
||||||
|
filter: Filter;
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const useCreateCertificate = () => {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectSlug }) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceCertificates(projectSlug));
|
queryClient.invalidateQueries(workspaceKeys.forWorkspaceCertificates(projectSlug));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const useDeleteCert = () => {
|
|||||||
return certificate;
|
return certificate;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectSlug }) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceCertificates(projectSlug));
|
queryClient.invalidateQueries(workspaceKeys.forWorkspaceCertificates(projectSlug));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -37,7 +37,7 @@ export const useRevokeCert = () => {
|
|||||||
return certificate;
|
return certificate;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { projectSlug }) => {
|
onSuccess: (_, { projectSlug }) => {
|
||||||
queryClient.invalidateQueries(workspaceKeys.getWorkspaceCertificates(projectSlug));
|
queryClient.invalidateQueries(workspaceKeys.forWorkspaceCertificates(projectSlug));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,8 +48,18 @@ export const workspaceKeys = {
|
|||||||
[{ projectSlug }, "workspace-cas"] as const,
|
[{ projectSlug }, "workspace-cas"] as const,
|
||||||
specificWorkspaceCas: ({ projectSlug, status }: { projectSlug: string; status?: CaStatus }) =>
|
specificWorkspaceCas: ({ projectSlug, status }: { projectSlug: string; status?: CaStatus }) =>
|
||||||
[...workspaceKeys.getWorkspaceCas({ projectSlug }), { status }] as const,
|
[...workspaceKeys.getWorkspaceCas({ projectSlug }), { status }] as const,
|
||||||
getWorkspaceCertificates: (projectSlug: string) =>
|
allWorkspaceCertificates: () => ["workspace-certificates"] as const,
|
||||||
[{ projectSlug }, "workspace-certificates"] as const
|
forWorkspaceCertificates: (slug: string) =>
|
||||||
|
[...workspaceKeys.allWorkspaceCertificates(), slug] as const,
|
||||||
|
specificWorkspaceCertificates: ({
|
||||||
|
slug,
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
}: {
|
||||||
|
slug: string;
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
}) => [...workspaceKeys.forWorkspaceCertificates(slug), { offset, limit }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchWorkspaceById = async (workspaceId: string) => {
|
const fetchWorkspaceById = async (workspaceId: string) => {
|
||||||
@@ -526,16 +536,37 @@ export const useListWorkspaceCas = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useListWorkspaceCertificates = (projectSlug: string) => {
|
export const useListWorkspaceCertificates = ({
|
||||||
|
projectSlug,
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
}: {
|
||||||
|
projectSlug: string;
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
}) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: workspaceKeys.getWorkspaceCertificates(projectSlug),
|
queryKey: workspaceKeys.specificWorkspaceCertificates({
|
||||||
|
slug: projectSlug,
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
}),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
offset: String(offset),
|
||||||
|
limit: String(limit)
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { certificates }
|
data: { certificates, totalCount }
|
||||||
} = await apiRequest.get<{ certificates: TCertificate[] }>(
|
} = await apiRequest.get<{ certificates: TCertificate[]; totalCount: number }>(
|
||||||
`/api/v2/workspace/${projectSlug}/certificates`
|
`/api/v2/workspace/${projectSlug}/certificates`,
|
||||||
|
{
|
||||||
|
params
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return certificates;
|
|
||||||
|
return { certificates, totalCount };
|
||||||
},
|
},
|
||||||
enabled: Boolean(projectSlug)
|
enabled: Boolean(projectSlug)
|
||||||
});
|
});
|
||||||
|
|||||||
+23
-7
@@ -1,3 +1,4 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
faBan,
|
faBan,
|
||||||
faCertificate,
|
faCertificate,
|
||||||
@@ -17,6 +18,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
|
Pagination,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableSkeleton,
|
TableSkeleton,
|
||||||
@@ -25,8 +27,7 @@ import {
|
|||||||
Th,
|
Th,
|
||||||
THead,
|
THead,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Tr
|
Tr} from "@app/components/v2";
|
||||||
} from "@app/components/v2";
|
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
|
||||||
import { useListWorkspaceCertificates } from "@app/hooks/api";
|
import { useListWorkspaceCertificates } from "@app/hooks/api";
|
||||||
import { certStatusToNameMap } from "@app/hooks/api/certificates/constants";
|
import { certStatusToNameMap } from "@app/hooks/api/certificates/constants";
|
||||||
@@ -45,8 +46,16 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [perPage, setPerPage] = useState(25);
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data, isLoading } = useListWorkspaceCertificates(currentWorkspace?.slug ?? "");
|
const { data, isLoading } = useListWorkspaceCertificates({
|
||||||
|
projectSlug: currentWorkspace?.slug ?? "",
|
||||||
|
offset: (page - 1) * perPage,
|
||||||
|
limit: perPage
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
@@ -62,9 +71,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<TBody>
|
<TBody>
|
||||||
{isLoading && <TableSkeleton columns={3} innerKey="project-cas" />}
|
{isLoading && <TableSkeleton columns={3} innerKey="project-cas" />}
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
data &&
|
data?.certificates.map((certificate) => {
|
||||||
data.length > 0 &&
|
|
||||||
data.map((certificate) => {
|
|
||||||
return (
|
return (
|
||||||
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
<Tr className="h-10" key={`certificate-${certificate.id}`}>
|
||||||
<Td>{certificate.friendlyName}</Td>
|
<Td>{certificate.friendlyName}</Td>
|
||||||
@@ -177,7 +184,16 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
})}
|
})}
|
||||||
</TBody>
|
</TBody>
|
||||||
</Table>
|
</Table>
|
||||||
{!isLoading && data?.length === 0 && (
|
{!isLoading && data?.totalCount !== undefined && (
|
||||||
|
<Pagination
|
||||||
|
count={data.totalCount}
|
||||||
|
page={page}
|
||||||
|
perPage={perPage}
|
||||||
|
onChangePage={(newPage) => setPage(newPage)}
|
||||||
|
onChangePerPage={(newPerPage) => setPerPage(newPerPage)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isLoading && !data?.certificates?.length && (
|
||||||
<EmptyState title="No certificates have been created" icon={faCertificate} />
|
<EmptyState title="No certificates have been created" icon={faCertificate} />
|
||||||
)}
|
)}
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user