mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-15 17:09:51 +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" />
|
<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>
|
||||||
|
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"
|
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,11 +1,28 @@
|
|||||||
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;
|
||||||
sort: "-created",
|
page?: number;
|
||||||
expand: "lastDeployment",
|
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;
|
return response;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user