mirror of https://github.com/usual2970/certimate
commit
69671075fa
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
File diff suppressed because one or more lines are too long
|
@ -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",
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue