Compare commits

..

5 Commits

Author SHA1 Message Date
yoan
2efe0de0cf UI optimization 2024-09-17 08:59:39 +08:00
yoan
34e40e5e54 UI Optimization 2024-09-16 08:44:36 +08:00
usual2970
e7e269dfb0 Merge pull request #37 from usual2970/feat/access_ui
Merge authorization group into authorization management
2024-09-15 21:59:03 +08:00
yoan
fa85580e35 Merge authorization group into authorization management 2024-09-15 21:58:08 +08:00
usual2970
500fce6180 Update README.md 2024-09-14 22:02:40 +08:00
16 changed files with 542 additions and 426 deletions

View File

@@ -13,6 +13,11 @@ Certimate 就是为了解决上述问题而产生的,它具有以下特点:
2. 支持私有部署部署方法简单只需下载二进制文件执行即可。二进制文件、docker 镜像全部用 github actions 生成,过程透明,可自行审计。
3. 数据安全:由于是私有部署,所有数据均存储在本地,不会保存在服务商的服务器,确保数据的安全性。
相关文章:
* [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
* [域名变量及部署授权组介绍](https://docs.certimate.me/blog/multi-deployer)
Certimate 旨在为用户提供一个安全、简便的 SSL 证书管理解决方案。使用文档请访问[https://docs.certimate.me](https://docs.certimate.me)

1
ui/dist/assets/index-ChWRjRip.css vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

4
ui/dist/index.html vendored
View File

@@ -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-pPAQ4idS.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DXJTf3ck.css">
<script type="module" crossorigin src="/assets/index-DoSOua_N.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ChWRjRip.css">
</head>
<body class="bg-background">
<div id="root"></div>

30
ui/package-lock.json generated
View File

@@ -20,6 +20,7 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
@@ -1762,6 +1763,35 @@
}
}
},
"node_modules/@radix-ui/react-tabs": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-tabs/-/react-tabs-1.1.0.tgz",
"integrity": "sha512-bZgOKB/LtZIij75FSuPzyEti/XBhJH52ExgtdVqjCIh+Nx/FW+LhnbXtbCzIi34ccyMsyOja8T0thCzoHFXNKA==",
"dependencies": {
"@radix-ui/primitive": "1.1.0",
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-roving-focus": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toast": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/@radix-ui/react-toast/-/react-toast-1.2.1.tgz",

View File

@@ -22,6 +22,7 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",

View File

@@ -0,0 +1,217 @@
import AccessGroupEdit from "@/components/certimate/AccessGroupEdit";
import Show from "@/components/Show";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getProviderInfo } from "@/domain/access";
import { getErrMessage } from "@/lib/error";
import { useConfig } from "@/providers/config";
import { remove } from "@/repository/access_group";
import { Group } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { useNavigate } from "react-router-dom";
const AccessGroupList = () => {
const {
config: { accessGroups },
reloadAccessGroups,
} = useConfig();
const { toast } = useToast();
const navigate = useNavigate();
const handleRemoveClick = async (id: string) => {
try {
await remove(id);
reloadAccessGroups();
} catch (e) {
toast({
title: "删除失败",
description: getErrMessage(e),
variant: "destructive",
});
return;
}
};
const handleAddAccess = () => {
navigate("/access");
};
return (
<div className="mt-10">
<Show when={accessGroups.length == 0}>
<>
<div className="flex flex-col items-center mt-10">
<span className="bg-orange-100 p-5 rounded-full">
<Group size={40} className="text-primary" />
</span>
<div className="text-center text-sm text-muted-foreground mt-3">
</div>
<AccessGroupEdit
trigger={<Button></Button>}
className="mt-3"
/>
</div>
</>
</Show>
<ScrollArea className="h-[75vh] overflow-hidden">
<div className="flex gap-5 flex-wrap">
{accessGroups.map((accessGroup) => (
<Card className="w-full md:w-[350px]">
<CardHeader>
<CardTitle>{accessGroup.name}</CardTitle>
<CardDescription>
{accessGroup.expand ? accessGroup.expand.access.length : 0}
</CardDescription>
</CardHeader>
<CardContent className="min-h-[180px]">
{accessGroup.expand ? (
<>
{accessGroup.expand.access.slice(0, 3).map((access) => (
<div key={access.id} className="flex flex-col mb-3">
<div className="flex items-center">
<div className="">
<img
src={getProviderInfo(access.configType)![1]}
alt="provider"
className="w-8 h-8"
></img>
</div>
<div className="ml-3">
<div className="text-sm font-semibold text-gray-700 dark:text-gray-200">
{access.name}
</div>
<div className="text-xs text-muted-foreground">
{getProviderInfo(access.configType)![0]}
</div>
</div>
</div>
</div>
))}
</>
) : (
<>
<div className="flex text-gray-700 dark:text-gray-200 items-center">
<div>
<Group size={40} />
</div>
<div className="ml-2">
使
</div>
</div>
</>
)}
</CardContent>
<CardFooter>
<div className="flex justify-end w-full">
<Show
when={
accessGroup.expand && accessGroup.expand.access.length > 0
? true
: false
}
>
<div>
<Button
size="sm"
variant={"link"}
onClick={() => {
navigate(
`/access?accessGroupId=${accessGroup.id}&tab=access`,
{
replace: true,
}
);
}}
>
</Button>
</div>
</Show>
<Show
when={
!accessGroup.expand ||
accessGroup.expand.access.length == 0
? true
: false
}
>
<div>
<Button size="sm" onClick={handleAddAccess}>
</Button>
</div>
</Show>
<div className="ml-3">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={"destructive"} size={"sm"}>
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200">
</AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200">
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
handleRemoveClick(
accessGroup.id ? accessGroup.id : ""
);
}}
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
</CardFooter>
</Card>
))}
</div>
</ScrollArea>
</div>
);
};
export default AccessGroupList;

View File

@@ -0,0 +1,31 @@
import { BookOpen } from "lucide-react";
import { Separator } from "../ui/separator";
import { version } from "@/domain/version";
const Version = () => {
return (
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
<div className=""></div>
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
<a
href="https://docs.certimate.me"
target="_blank"
className="flex items-center"
>
<BookOpen size={16} />
<div className="ml-1"></div>
</a>
<Separator orientation="vertical" className="mx-2" />
<a
href="https://github.com/usual2970/certimate/releases"
target="_blank"
>
{version}
</a>
</div>
</div>
);
};
export default Version;

View File

@@ -0,0 +1,53 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

1
ui/src/domain/version.ts Normal file
View File

@@ -0,0 +1 @@
export const version = "Certimate v0.1.7";

View File

@@ -5,16 +5,7 @@ import {
useLocation,
useNavigate,
} from "react-router-dom";
import {
BookOpen,
CircleUser,
Earth,
Group,
History,
Home,
Menu,
Server,
} from "lucide-react";
import { CircleUser, Earth, History, Home, Menu, Server } from "lucide-react";
import { Button } from "@/components/ui/button";
@@ -31,7 +22,8 @@ import { cn } from "@/lib/utils";
import { ConfigProvider } from "@/providers/config";
import { getPb } from "@/repository/api";
import { ThemeToggle } from "@/components/ThemeToggle";
import { Separator } from "@/components/ui/separator";
import Version from "@/components/certimate/Version";
export default function Dashboard() {
const navigate = useNavigate();
@@ -101,17 +93,6 @@ export default function Dashboard() {
</Link>
<Link
to="/access_groups"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 transition-all hover:text-primary",
getClass("/access_groups")
)}
>
<Group className="h-4 w-4" />
</Link>
<Link
to="/history"
className={cn(
@@ -180,17 +161,6 @@ export default function Dashboard() {
</Link>
<Link
to="/access_groups"
className={cn(
"mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 hover:text-foreground",
getClass("/access_groups")
)}
>
<Group className="h-5 w-5" />
</Link>
<Link
to="/history"
className={cn(
@@ -234,26 +204,7 @@ export default function Dashboard() {
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6 relative">
<Outlet />
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
<div className=""></div>
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
<a
href="https://docs.certimate.me"
target="_blank"
className="flex items-center"
>
<BookOpen size={16} />
<div className="ml-1"></div>
</a>
<Separator orientation="vertical" className="mx-2" />
<a
href="https://github.com/usual2970/certimate/releases"
target="_blank"
>
Certimate v0.1.6
</a>
</div>
</div>
<Version />
</main>
</div>
</div>

View File

@@ -1,4 +1,7 @@
import Version from "@/components/certimate/Version";
import { getPb } from "@/repository/api";
import { Navigate, Outlet } from "react-router-dom";
const LoginLayout = () => {
@@ -8,6 +11,8 @@ const LoginLayout = () => {
return (
<div className="container">
<Outlet />
<Version />
</div>
);
};

View File

@@ -1,7 +1,10 @@
import { AccessEdit } from "@/components/certimate/AccessEdit";
import AccessGroupEdit from "@/components/certimate/AccessGroupEdit";
import AccessGroupList from "@/components/certimate/AccessGroupList";
import XPagination from "@/components/certimate/XPagination";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Access as AccessType, accessTypeMap } from "@/domain/access";
import { convertZulu2Beijing } from "@/lib/time";
import { useConfig } from "@/providers/config";
@@ -23,6 +26,8 @@ const Access = () => {
const page = query.get("page");
const pageNumber = page ? Number(page) : 1;
const tab = query.get("tab");
const accessGroupId = query.get("accessGroupId");
const startIndex = (pageNumber - 1) * perPage;
@@ -33,100 +38,141 @@ const Access = () => {
deleteAccess(rs.id);
};
const handleTabItemClick = (tab: string) => {
query.set("tab", tab);
navigate({ search: query.toString() });
};
return (
<div className="">
<div className="flex justify-between items-center">
<div className="text-muted-foreground"></div>
<AccessEdit trigger={<Button></Button>} op="add" />
{tab != "access_group" ? (
<AccessEdit trigger={<Button></Button>} op="add" />
) : (
<AccessGroupEdit trigger={<Button></Button>} />
)}
</div>
{accesses.length === 0 ? (
<div className="flex flex-col items-center mt-10">
<span className="bg-orange-100 p-5 rounded-full">
<Key size={40} className="text-primary" />
</span>
<div className="text-center text-sm text-muted-foreground mt-3">
</div>
<AccessEdit
trigger={<Button></Button>}
op="add"
className="mt-3"
/>
</div>
) : (
<>
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
<div className="w-48"></div>
<div className="w-48"></div>
<div className="w-52"></div>
<div className="w-52"></div>
<div className="grow"></div>
</div>
<div className="sm:hidden flex text-sm text-muted-foreground">
</div>
{accesses
.filter((item) => {
return accessGroupId ? item.group == accessGroupId : true;
})
.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}
>
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
{access.name}
</div>
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center space-x-2">
<img
src={accessTypeMap.get(access.configType)?.[1]}
className="w-6"
/>
<div>{accessTypeMap.get(access.configType)?.[0]}</div>
</div>
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
{access.created && convertZulu2Beijing(access.created)}
</div>
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
{access.updated && convertZulu2Beijing(access.updated)}
</div>
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
<AccessEdit
trigger={
<Button variant={"link"} className="p-0">
</Button>
}
op="edit"
data={access}
/>
<Separator orientation="vertical" className="h-4 mx-2" />
<Button
variant={"link"}
className="p-0"
onClick={() => {
handleDelete(access);
}}
>
</Button>
</div>
</div>
))}
<XPagination
totalPages={totalPages}
currentPage={pageNumber}
onPageChange={(page) => {
query.set("page", page.toString());
navigate({ search: query.toString() });
<Tabs
defaultValue={tab ? tab : "access"}
value={tab ? tab : "access"}
className="w-full mt-5"
>
<TabsList className="space-x-5 px-3">
<TabsTrigger
value="access"
onClick={() => {
handleTabItemClick("access");
}}
/>
</>
)}
>
</TabsTrigger>
<TabsTrigger
value="access_group"
onClick={() => {
handleTabItemClick("access_group");
}}
>
</TabsTrigger>
</TabsList>
<TabsContent value="access">
{accesses.length === 0 ? (
<div className="flex flex-col items-center mt-10">
<span className="bg-orange-100 p-5 rounded-full">
<Key size={40} className="text-primary" />
</span>
<div className="text-center text-sm text-muted-foreground mt-3">
</div>
<AccessEdit
trigger={<Button></Button>}
op="add"
className="mt-3"
/>
</div>
) : (
<>
<div className="hidden sm:flex sm:flex-row text-muted-foreground text-sm border-b dark:border-stone-500 sm:p-2 mt-5">
<div className="w-48"></div>
<div className="w-48"></div>
<div className="w-52"></div>
<div className="w-52"></div>
<div className="grow"></div>
</div>
<div className="sm:hidden flex text-sm text-muted-foreground">
</div>
{accesses
.filter((item) => {
return accessGroupId ? item.group == accessGroupId : true;
})
.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}
>
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center">
{access.name}
</div>
<div className="sm:w-48 w-full pt-1 sm:pt-0 flex items-center space-x-2">
<img
src={accessTypeMap.get(access.configType)?.[1]}
className="w-6"
/>
<div>{accessTypeMap.get(access.configType)?.[0]}</div>
</div>
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
{" "}
{access.created && convertZulu2Beijing(access.created)}
</div>
<div className="sm:w-52 w-full pt-1 sm:pt-0 flex items-center">
{" "}
{access.updated && convertZulu2Beijing(access.updated)}
</div>
<div className="flex items-center grow justify-start pt-1 sm:pt-0">
<AccessEdit
trigger={
<Button variant={"link"} className="p-0">
</Button>
}
op="edit"
data={access}
/>
<Separator orientation="vertical" className="h-4 mx-2" />
<Button
variant={"link"}
className="p-0"
onClick={() => {
handleDelete(access);
}}
>
</Button>
</div>
</div>
))}
<XPagination
totalPages={totalPages}
currentPage={pageNumber}
onPageChange={(page) => {
query.set("page", page.toString());
navigate({ search: query.toString() });
}}
/>
</>
)}
</TabsContent>
<TabsContent value="access_group">
<AccessGroupList />
</TabsContent>
</Tabs>
</div>
);
};

View File

@@ -1,219 +0,0 @@
import AccessGroupEdit from "@/components/certimate/AccessGroupEdit";
import Show from "@/components/Show";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";
import { getProviderInfo } from "@/domain/access";
import { getErrMessage } from "@/lib/error";
import { useConfig } from "@/providers/config";
import { remove } from "@/repository/access_group";
import { Group } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
import { Toaster } from "@/components/ui/toaster";
import { useNavigate } from "react-router-dom";
const AccessGroups = () => {
const {
config: { accessGroups },
reloadAccessGroups,
} = useConfig();
const { toast } = useToast();
const navigate = useNavigate();
const handleRemoveClick = async (id: string) => {
try {
await remove(id);
reloadAccessGroups();
} catch (e) {
toast({
title: "删除失败",
description: getErrMessage(e),
variant: "destructive",
});
return;
}
};
const handleAddAccess = () => {
navigate("/access");
};
return (
<div>
<Toaster />
<div className="flex justify-between items-center">
<div className="text-muted-foreground"></div>
<AccessGroupEdit trigger={<Button></Button>} />
</div>
<div className="mt-10">
<Show when={accessGroups.length == 0}>
<>
<div className="flex flex-col items-center mt-10">
<span className="bg-orange-100 p-5 rounded-full">
<Group size={40} className="text-primary" />
</span>
<div className="text-center text-sm text-muted-foreground mt-3">
</div>
<AccessGroupEdit
trigger={<Button></Button>}
className="mt-3"
/>
</div>
</>
</Show>
<ScrollArea className="h-[75vh] overflow-hidden">
<div className="flex gap-5 flex-wrap">
{accessGroups.map((accessGroup) => (
<Card className="w-full md:w-[350px]">
<CardHeader>
<CardTitle>{accessGroup.name}</CardTitle>
<CardDescription>
{accessGroup.expand ? accessGroup.expand.access.length : 0}
</CardDescription>
</CardHeader>
<CardContent className="min-h-[180px]">
{accessGroup.expand ? (
<>
{accessGroup.expand.access.slice(0, 3).map((access) => (
<div key={access.id} className="flex flex-col mb-3">
<div className="flex items-center">
<div className="">
<img
src={getProviderInfo(access.configType)![1]}
alt="provider"
className="w-8 h-8"
></img>
</div>
<div className="ml-3">
<div className="text-sm font-semibold text-gray-700 dark:text-gray-200">
{access.name}
</div>
<div className="text-xs text-muted-foreground">
{getProviderInfo(access.configType)![0]}
</div>
</div>
</div>
</div>
))}
</>
) : (
<>
<div className="flex text-gray-700 dark:text-gray-200 items-center">
<div>
<Group size={40} />
</div>
<div className="ml-2">
使
</div>
</div>
</>
)}
</CardContent>
<CardFooter>
<div className="flex justify-end w-full">
<Show
when={
accessGroup.expand &&
accessGroup.expand.access.length > 0
? true
: false
}
>
<div>
<Button
size="sm"
variant={"link"}
onClick={() => {
navigate(`/access?accessGroupId=${accessGroup.id}`);
}}
>
</Button>
</div>
</Show>
<Show
when={
!accessGroup.expand ||
accessGroup.expand.access.length == 0
? true
: false
}
>
<div>
<Button size="sm" onClick={handleAddAccess}>
</Button>
</div>
</Show>
<div className="ml-3">
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={"destructive"} size={"sm"}>
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200">
</AlertDialogTitle>
<AlertDialogDescription>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200">
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
handleRemoveClick(
accessGroup.id ? accessGroup.id : ""
);
}}
>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div>
</CardFooter>
</Card>
))}
</div>
</ScrollArea>
</div>
</div>
);
};
export default AccessGroups;

View File

@@ -10,7 +10,6 @@ import LoginLayout from "./pages/LoginLayout";
import Password from "./pages/setting/Password";
import SettingLayout from "./pages/SettingLayout";
import Dashboard from "./pages/dashboard/Dashboard";
import AccessGroups from "./pages/access_groups/AccessGroups";
export const router = createHashRouter([
{
@@ -33,10 +32,6 @@ export const router = createHashRouter([
path: "/access",
element: <Access />,
},
{
path: "/access_groups",
element: <AccessGroups />,
},
{
path: "/history",
element: <History />,