mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-10 06:29:52 +00:00
commit
69671075fa
1
ui/dist/assets/index-BPSHHpDP.css
vendored
Normal file
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
254
ui/dist/assets/index-CglXs5Ou.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
ui/dist/assets/index-DHLrqoj1.css
vendored
1
ui/dist/assets/index-DHLrqoj1.css
vendored
File diff suppressed because one or more lines are too long
249
ui/dist/assets/index-nGGiqZOp.js
vendored
249
ui/dist/assets/index-nGGiqZOp.js
vendored
File diff suppressed because one or more lines are too long
4
ui/dist/index.html
vendored
4
ui/dist/index.html
vendored
@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Certimate - Your Trusted SSL Automation Partner</title>
|
||||
<script type="module" crossorigin src="/assets/index-nGGiqZOp.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DHLrqoj1.css">
|
||||
<script type="module" crossorigin src="/assets/index-CglXs5Ou.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BPSHHpDP.css">
|
||||
</head>
|
||||
<body class="bg-background">
|
||||
<div id="root"></div>
|
||||
|
102
ui/src/components/certimate/XPagination.tsx
Normal file
102
ui/src/components/certimate/XPagination.tsx
Normal file
@ -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;
|
117
ui/src/components/ui/pagination.tsx
Normal file
117
ui/src/components/ui/pagination.tsx
Normal file
@ -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"
|
||||
target="_blank"
|
||||
>
|
||||
Certimate v0.0.13
|
||||
Certimate v0.0.14
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { AccessEdit } from "@/components/certimate/AccessEdit";
|
||||
import XPagination from "@/components/certimate/XPagination";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Access as AccessType, accessTypeMap } from "@/domain/access";
|
||||
@ -6,11 +7,25 @@ import { convertZulu2Beijing } from "@/lib/time";
|
||||
import { useConfig } from "@/providers/config";
|
||||
import { remove } from "@/repository/access";
|
||||
import { Key } from "lucide-react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
const Access = () => {
|
||||
const { config, deleteAccess } = useConfig();
|
||||
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 rs = await remove(data);
|
||||
deleteAccess(rs.id);
|
||||
@ -50,7 +65,7 @@ const Access = () => {
|
||||
<div className="sm:hidden flex text-sm text-muted-foreground">
|
||||
授权列表
|
||||
</div>
|
||||
{accesses.map((access) => (
|
||||
{accesses.slice(startIndex, endIndex).map((access) => (
|
||||
<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"
|
||||
key={access.id}
|
||||
@ -95,6 +110,14 @@ const Access = () => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<XPagination
|
||||
totalPages={totalPages}
|
||||
currentPage={pageNumber}
|
||||
onPageChange={(page) => {
|
||||
query.set("page", page.toString());
|
||||
navigate({ search: query.toString() });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import DeployProgress from "@/components/certimate/DeployProgress";
|
||||
import XPagination from "@/components/certimate/XPagination";
|
||||
import Show from "@/components/Show";
|
||||
import {
|
||||
AlertDialogAction,
|
||||
@ -32,16 +33,28 @@ import {
|
||||
import { TooltipContent, TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { CircleCheck, CircleX, Earth } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
const Home = () => {
|
||||
const toast = useToast();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const location = useLocation();
|
||||
const query = new URLSearchParams(location.search);
|
||||
const page = query.get("page");
|
||||
|
||||
const [totalPage, setTotalPage] = useState(0);
|
||||
|
||||
const handleCreateClick = () => {
|
||||
navigate("/edit");
|
||||
};
|
||||
|
||||
const setPage = (newPage: number) => {
|
||||
query.set("page", newPage.toString());
|
||||
navigate(`?${query.toString()}`);
|
||||
};
|
||||
|
||||
const handleEditClick = (id: string) => {
|
||||
navigate(`/edit?id=${id}`);
|
||||
};
|
||||
@ -63,11 +76,16 @@ const Home = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const data = await list();
|
||||
setDomains(data);
|
||||
const data = await list({
|
||||
page: page ? Number(page) : 1,
|
||||
perPage: 10,
|
||||
});
|
||||
|
||||
setDomains(data.items);
|
||||
setTotalPage(data.totalPages);
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
}, [page]);
|
||||
|
||||
const handelCheckedChange = async (id: string) => {
|
||||
const checkedDomains = domains.filter((domain) => domain.id === id);
|
||||
@ -323,6 +341,14 @@ const Home = () => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<XPagination
|
||||
totalPages={totalPage}
|
||||
currentPage={page ? Number(page) : 1}
|
||||
onPageChange={(page) => {
|
||||
setPage(page);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,11 +1,28 @@
|
||||
import { Domain } from "@/domain/domain";
|
||||
import { getPb } from "./api";
|
||||
|
||||
export const list = async () => {
|
||||
const response = getPb().collection("domains").getFullList<Domain>({
|
||||
sort: "-created",
|
||||
expand: "lastDeployment",
|
||||
});
|
||||
type DomainListReq = {
|
||||
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",
|
||||
expand: "lastDeployment",
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user