Merge pull request from usual2970/feat/pagination

Feat/pagination
This commit is contained in:
usual2970
2024-09-02 22:56:04 +08:00
committed by GitHub
12 changed files with 553 additions and 263 deletions

BIN
certimate Executable file

Binary file not shown.

1
ui/dist/assets/index-BPSHHpDP.css vendored Normal file

File diff suppressed because one or more lines are too long

254
ui/dist/assets/index-CglXs5Ou.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
ui/dist/index.html vendored

@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title> <title>Certimate - Your Trusted SSL Automation Partner</title>
<script type="module" crossorigin src="/assets/index-nGGiqZOp.js"></script> <script type="module" crossorigin src="/assets/index-CglXs5Ou.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DHLrqoj1.css"> <link rel="stylesheet" crossorigin href="/assets/index-BPSHHpDP.css">
</head> </head>
<body class="bg-background"> <body class="bg-background">
<div id="root"></div> <div id="root"></div>

@@ -0,0 +1,102 @@
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "../ui/pagination";
type PaginationProps = {
totalPages: number;
currentPage: number;
onPageChange: (page: number) => void;
};
type PageNumber = number | string;
const XPagination = ({
totalPages,
currentPage,
onPageChange,
}: PaginationProps) => {
const pageNeighbours = 1; // Number of page numbers to show on either side of the current page
const getPageNumbers = () => {
const totalNumbers = pageNeighbours * 2 + 3; // total pages to display (left + right neighbours + current + 2 for start and end)
const totalBlocks = totalNumbers + 2; // adding 2 for the start and end page numbers
if (totalPages > totalBlocks) {
let pages: PageNumber[] = [];
const leftBound = Math.max(2, currentPage - pageNeighbours);
const rightBound = Math.min(totalPages - 1, currentPage + pageNeighbours);
const beforeLastPage = totalPages - 1;
pages = range(leftBound, rightBound);
if (currentPage > pageNeighbours + 2) {
pages.unshift("...");
}
if (currentPage < beforeLastPage - pageNeighbours) {
pages.push("...");
}
pages.unshift(1);
pages.push(totalPages);
return pages;
}
return range(1, totalPages);
};
const range = (from: number, to: number, step = 1) => {
let i = from;
const range = [];
while (i <= to) {
range.push(i);
i += step;
}
return range;
};
const pages = getPageNumbers();
return (
<>
<Pagination className="dark:text-stone-200 justify-end mt-3">
<PaginationContent>
{pages.map((page, index) => {
if (page === "...") {
return (
<PaginationItem key={index}>
<PaginationEllipsis />
</PaginationItem>
);
}
return (
<PaginationItem key={index}>
<PaginationLink
href="#"
isActive={currentPage == page}
onClick={(e) => {
e.preventDefault();
onPageChange(page as number);
}}
>
{page}
</PaginationLink>
</PaginationItem>
);
})}
</PaginationContent>
</Pagination>
</>
);
};
export default XPagination;

@@ -0,0 +1,117 @@
import * as React from "react";
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "@/lib/utils";
import { ButtonProps, buttonVariants } from "@/components/ui/button";
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
);
Pagination.displayName = "Pagination";
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
));
PaginationContent.displayName = "PaginationContent";
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
));
PaginationItem.displayName = "PaginationItem";
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">;
const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props}
/>
);
PaginationLink.displayName = "PaginationLink";
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span></span>
</PaginationLink>
);
PaginationPrevious.displayName = "PaginationPrevious";
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span></span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
);
PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
);
PaginationEllipsis.displayName = "PaginationEllipsis";
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
};

@@ -194,7 +194,7 @@ export default function Dashboard() {
href="https://github.com/usual2970/certimate/releases" href="https://github.com/usual2970/certimate/releases"
target="_blank" target="_blank"
> >
Certimate v0.0.13 Certimate v0.0.14
</a> </a>
</div> </div>
</div> </div>

@@ -1,4 +1,5 @@
import { AccessEdit } from "@/components/certimate/AccessEdit"; import { AccessEdit } from "@/components/certimate/AccessEdit";
import XPagination from "@/components/certimate/XPagination";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Access as AccessType, accessTypeMap } from "@/domain/access"; import { Access as AccessType, accessTypeMap } from "@/domain/access";
@@ -6,11 +7,25 @@ import { convertZulu2Beijing } from "@/lib/time";
import { useConfig } from "@/providers/config"; import { useConfig } from "@/providers/config";
import { remove } from "@/repository/access"; import { remove } from "@/repository/access";
import { Key } from "lucide-react"; import { Key } from "lucide-react";
import { useLocation, useNavigate } from "react-router-dom";
const Access = () => { const Access = () => {
const { config, deleteAccess } = useConfig(); const { config, deleteAccess } = useConfig();
const { accesses } = config; const { accesses } = config;
const perPage = 10;
const totalPages = Math.ceil(accesses.length / perPage);
const navigate = useNavigate();
const location = useLocation();
const query = new URLSearchParams(location.search);
const page = query.get("page");
const pageNumber = page ? Number(page) : 1;
const startIndex = (pageNumber - 1) * perPage;
const endIndex = startIndex + perPage;
const handleDelete = async (data: AccessType) => { const handleDelete = async (data: AccessType) => {
const rs = await remove(data); const rs = await remove(data);
deleteAccess(rs.id); deleteAccess(rs.id);
@@ -50,7 +65,7 @@ const Access = () => {
<div className="sm:hidden flex text-sm text-muted-foreground"> <div className="sm:hidden flex text-sm text-muted-foreground">
</div> </div>
{accesses.map((access) => ( {accesses.slice(startIndex, endIndex).map((access) => (
<div <div
className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm" className="flex flex-col sm:flex-row text-secondary-foreground border-b dark:border-stone-500 sm:p-2 hover:bg-muted/50 text-sm"
key={access.id} key={access.id}
@@ -95,6 +110,14 @@ const Access = () => {
</div> </div>
</div> </div>
))} ))}
<XPagination
totalPages={totalPages}
currentPage={pageNumber}
onPageChange={(page) => {
query.set("page", page.toString());
navigate({ search: query.toString() });
}}
/>
</> </>
)} )}
</div> </div>

@@ -1,4 +1,5 @@
import DeployProgress from "@/components/certimate/DeployProgress"; import DeployProgress from "@/components/certimate/DeployProgress";
import XPagination from "@/components/certimate/XPagination";
import Show from "@/components/Show"; import Show from "@/components/Show";
import { import {
AlertDialogAction, AlertDialogAction,
@@ -32,16 +33,28 @@ import {
import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip"; import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip";
import { CircleCheck, CircleX, Earth } from "lucide-react"; import { CircleCheck, CircleX, Earth } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
const Home = () => { const Home = () => {
const toast = useToast(); const toast = useToast();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const query = new URLSearchParams(location.search);
const page = query.get("page");
const [totalPage, setTotalPage] = useState(0);
const handleCreateClick = () => { const handleCreateClick = () => {
navigate("/edit"); navigate("/edit");
}; };
const setPage = (newPage: number) => {
query.set("page", newPage.toString());
navigate(`?${query.toString()}`);
};
const handleEditClick = (id: string) => { const handleEditClick = (id: string) => {
navigate(`/edit?id=${id}`); navigate(`/edit?id=${id}`);
}; };
@@ -63,11 +76,16 @@ const Home = () => {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
const data = await list(); const data = await list({
setDomains(data); page: page ? Number(page) : 1,
perPage: 10,
});
setDomains(data.items);
setTotalPage(data.totalPages);
}; };
fetchData(); fetchData();
}, []); }, [page]);
const handelCheckedChange = async (id: string) => { const handelCheckedChange = async (id: string) => {
const checkedDomains = domains.filter((domain) => domain.id === id); const checkedDomains = domains.filter((domain) => domain.id === id);
@@ -323,6 +341,14 @@ const Home = () => {
</div> </div>
</div> </div>
))} ))}
<XPagination
totalPages={totalPage}
currentPage={page ? Number(page) : 1}
onPageChange={(page) => {
setPage(page);
}}
/>
</> </>
)} )}
</div> </div>

@@ -1,8 +1,25 @@
import { Domain } from "@/domain/domain"; import { Domain } from "@/domain/domain";
import { getPb } from "./api"; import { getPb } from "./api";
export const list = async () => { type DomainListReq = {
const response = getPb().collection("domains").getFullList<Domain>({ domain?: string;
page?: number;
perPage?: number;
};
export const list = async (req: DomainListReq) => {
let page = 1;
if (req.page) {
page = req.page;
}
let perPage = 2;
if (req.perPage) {
perPage = req.perPage;
}
const response = getPb()
.collection("domains")
.getList<Domain>(page, perPage, {
sort: "-created", sort: "-created",
expand: "lastDeployment", expand: "lastDeployment",
}); });