diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 927f3658296df35b49a32453f7ec68d7a5bf7ebf..8c3af3936d3f0037e9300d2604e375240adb6f8c 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,36 +1,70 @@ +import { useEffect, useState } from 'react'; +import { Link } from 'react-router'; import { Split, ShieldCheck, Server, ArrowRight } from 'lucide-react'; +import { toast } from 'sonner'; +import { useAuthStore } from '@/stores/authStore'; import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Badge } from '@/components/ui/badge'; import { Card, CardHeader, CardTitle, CardContent, CardFooter, CardDescription } from '@/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Log, LogListResponse } from '@/types/log'; -const logs = [ - { - id: 1, - timestamp: '2021-09-01 12:00:00', - action: '웹 라우팅 설정 변경 (console.ajou.app)', - username: 'admin', - }, - { - id: 2, - timestamp: '2021-09-01 12:01:00', - action: 'SSL 인증서 발급 (*.ajou.app)', - username: 'admin', - }, - { - id: 3, - timestamp: '2021-09-01 13:00:00', - action: 'SSH 포트포워딩 설정 변경 (10.16.1.10)', - username: 'admin', - }, - { - id: 4, - timestamp: '2021-09-02 12:00:00', - action: '웹 라우팅 설정 변경 (blog.ajou.app)', - username: 'hando1220', - }, -]; +interface ProjectInfo { + routing: number; + forwarding: number; + certificate: number; +} + +const ACTION = { + CREATE: '생성', + UPDATE: '수정', + DELETE: '삭제', +}; + +const TYPE = { + ROUTING: '라우팅', + CERTIFICATE: '인증서', + FORWARDING: '포워딩', +}; export default function Home() { + const { authFetch, selectedProject } = useAuthStore(); + const [logs, setLogs] = useState<Log[] | null>(null); + const [projectInfo, setProjectInfo] = useState<ProjectInfo | null>(null); + + useEffect(() => { + authFetch(`/api/main?projectId=${selectedProject?.id}`) + .then((response) => { + if (!response.ok) throw new Error(`프로젝트 정보 조회 실패: (${response.status})`); + + return response.json(); + }) + .then((data) => { + setProjectInfo(data); + }) + .catch((error) => { + console.error(error); + toast.error('프로젝트 정보를 조회할 수 없습니다.'); + }); + }, [authFetch, selectedProject]); + + useEffect(() => { + authFetch(`/api/logs?projectId=${selectedProject?.id}&size=5`) + .then((response): Promise<LogListResponse> => { + if (!response.ok) throw new Error(`로그 목록 조회 실패: (${response.status})`); + + return response.json(); + }) + .then(({ contents }) => { + setLogs(contents); + }) + .catch((error) => { + console.error(error); + toast.error('로그 정보를 조회할 수 없습니다.'); + }); + }, [authFetch, selectedProject]); + return ( <div className="flex flex-1 flex-col gap-4 p-6"> <h1 className="scroll-m-20 text-3xl font-semibold first:mt-0 mb-2">Aolda Proxy Manager</h1> @@ -40,14 +74,22 @@ export default function Home() { <CardTitle className="text-xl">웹 라우팅</CardTitle> </CardHeader> <CardContent> - <div className="flex flex-row justify-center items-center gap-2 font-bold text-4xl"> - <Split className="size-8" /> 0 + <div className="flex flex-row justify-center items-center gap-4 font-bold text-4xl"> + {projectInfo === null ? ( + <Skeleton className="w-22 h-10 rounded-full" /> + ) : ( + <> + <Split className="size-8" /> {projectInfo.routing} + </> + )} </div> </CardContent> <CardFooter> - <Button variant="ghost" className="ml-auto"> - 관리하기 <ArrowRight /> - </Button> + <Link className="ml-auto" to="/routing"> + <Button variant="ghost"> + 관리하기 <ArrowRight /> + </Button> + </Link> </CardFooter> </Card> <Card> @@ -55,14 +97,22 @@ export default function Home() { <CardTitle className="text-xl">SSL 인증서</CardTitle> </CardHeader> <CardContent> - <div className="flex flex-row justify-center items-center gap-2 font-bold text-4xl"> - <ShieldCheck className="size-8" /> 0 + <div className="flex flex-row justify-center items-center gap-4 font-bold text-4xl"> + {projectInfo === null ? ( + <Skeleton className="w-22 h-10 rounded-full" /> + ) : ( + <> + <ShieldCheck className="size-8" /> {projectInfo.certificate} + </> + )} </div> </CardContent> <CardFooter> - <Button variant="ghost" className="ml-auto"> - 관리하기 <ArrowRight /> - </Button> + <Link className="ml-auto" to="/certificate"> + <Button variant="ghost"> + 관리하기 <ArrowRight /> + </Button> + </Link> </CardFooter> </Card> <Card> @@ -70,14 +120,22 @@ export default function Home() { <CardTitle className="text-xl">SSH 포트포워딩</CardTitle> </CardHeader> <CardContent> - <div className="flex flex-row justify-center items-center gap-2 font-bold text-4xl"> - <Server className="size-8" /> 0 + <div className="flex flex-row justify-center items-center gap-4 font-bold text-4xl"> + {projectInfo === null ? ( + <Skeleton className="w-22 h-10 rounded-full" /> + ) : ( + <> + <Server className="size-8" /> {projectInfo.forwarding} + </> + )} </div> </CardContent> <CardFooter> - <Button variant="ghost" className="ml-auto"> - 관리하기 <ArrowRight /> - </Button> + <Link className="ml-auto" to="/forwarding"> + <Button variant="ghost"> + 관리하기 <ArrowRight /> + </Button> + </Link> </CardFooter> </Card> </div> @@ -96,20 +154,54 @@ export default function Home() { </TableRow> </TableHeader> <TableBody> - {logs.map((log) => ( - <TableRow key={log.id}> - <TableCell className="font-medium">{log.timestamp}</TableCell> - <TableCell>{log.action}</TableCell> - <TableCell className="text-center">{log.username}</TableCell> + {logs === null ? ( + <> + <TableRow> + <TableCell colSpan={3}> + <Skeleton className="w-full h-[1rem] my-2 rounded-full" /> + </TableCell> + </TableRow> + <TableRow> + <TableCell colSpan={3}> + <Skeleton className="w-full h-[1rem] my-2 rounded-full" /> + </TableCell> + </TableRow> + <TableRow> + <TableCell colSpan={3}> + <Skeleton className="w-full h-[1rem] my-2 rounded-full" /> + </TableCell> + </TableRow> + </> + ) : logs.length === 0 ? ( + <TableRow> + <TableCell colSpan={3} className="text-center text-muted-foreground"> + 조회된 로그가 없습니다. + </TableCell> </TableRow> - ))} + ) : ( + logs.map((log) => ( + <TableRow key={log.id}> + <TableCell>{log.createdAt}</TableCell> + <TableCell> + <div className="flex items-center gap-2"> + <Badge variant="default">{TYPE[log.type]}</Badge> + <Badge variant="secondary">{ACTION[log.action]}</Badge> + <div>{log.description.split('\n').join(' / ')}</div> + </div> + </TableCell> + <TableCell className="truncate max-w-32 text-center">{log.user.name}</TableCell> + </TableRow> + )) + )} </TableBody> </Table> </CardContent> <CardFooter> - <Button variant="ghost" className="ml-auto"> - 모든 기록 보기 <ArrowRight /> - </Button> + <Link className="ml-auto" to="/log"> + <Button variant="ghost"> + 모든 기록 보기 <ArrowRight /> + </Button> + </Link> </CardFooter> </Card> </div>