로그인로그 수정 및 API 로그 추가, sort 데이터타입 수정정

This commit is contained in:
2025-06-27 20:38:45 +09:00
parent 70832bae82
commit 932ae3f961
16 changed files with 535 additions and 87 deletions
+7 -2
View File
@@ -7,11 +7,16 @@ const serverApiUrl =
export function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname;
const searchParams = req.nextUrl.searchParams;
if (pathname.startsWith("/server")) {
const queryParams = searchParams.toString();
const decodedParams = Array.from(searchParams.entries())
.map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
.join("&");
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
queryParams ? `?${queryParams}` : ""
decodedParams ? `?${decodedParams}` : ""
}`;
return NextResponse.rewrite(new URL(newUrl));
}
@@ -0,0 +1,20 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
ApiLogList,
ApiLogParams,
ApiLogService,
} from "../model/apilog-interface";
export class ApiLogApiService implements ApiLogService {
public async getApiLogList(params: ApiLogParams): Promise<ApiLogList> {
const response = await client.get<ApiLogList>(
`${ENDPOINT.USER_SERVICE.APILOG}`,
{ params },
);
return response.data;
}
}
export const ApiLogApi = new ApiLogApiService();
+6
View File
@@ -0,0 +1,6 @@
export { ApiLogApi } from "./api/apilog-service";
export type {
ApiLog,
ApiLogParams,
} from "./model/apilog-interface";
@@ -0,0 +1,27 @@
import type { PageableList, PageableParams } from "@/shared/model";
export interface ApiLogParams extends PageableParams {
siteId?: string;
userId?: string;
apiHttpPmhd?: string;
dmndUrl?: string;
ipAddr?: string;
searchDateFrom?: string;
searchDateTo?: string;
}
export interface ApiLog {
id: string;
siteId: string;
userId: string;
apiHttpPmhd: string;
dmndUrl: string;
ipAddr: string;
dmndDt: string;
}
export type ApiLogList = PageableList<ApiLog>;
export interface ApiLogService {
getApiLogList: (params: ApiLogParams) => Promise<ApiLogList>;
}
@@ -1,7 +1,6 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
LoginlogInfo,
LoginlogList,
LoginlogParams,
LoginlogService,
@@ -16,12 +15,6 @@ export class LoginlogApiService implements LoginlogService {
return response.data;
}
public async getLoginlogInfo(lgnSn: string): Promise<LoginlogInfo> {
const response = await client.get<LoginlogInfo>(
`${ENDPOINT.USER_SERVICE.LOGINLOG}/${lgnSn}`,
);
return response.data;
}
}
export const LoginlogApi = new LoginlogApiService();
+2 -1
View File
@@ -1,5 +1,6 @@
export { LoginlogApiService } from "./api/loginlog-service";
export { LoginlogApi } from "./api/loginlog-service";
export type {
Loginlog,
LoginlogParams,
} from "./model/loginlog-interface";
@@ -1,37 +1,29 @@
import type { PageableList, PageableParams } from "@/shared/model";
export interface LoginlogParams extends PageableParams {
lgnSn?: string;
userId?: string;
siteId?: string;
userId?: string;
errOcrnYn?: string;
remoteIp?: string;
lgnMthd?: string;
lgnErrCd?: string;
searchDateFrom?: string;
searchDateTo?: string;
}
export interface Loginlog {
lgnSn: string;
lgnDt: string;
userId: string;
lgnIp: string;
lgnMethodCd: string;
errOcrnYn: string;
lgnErrCd: string;
id: string;
siteId: string;
}
export interface LoginlogInfo {
lgnSn: string;
lgnDt: string;
userId: string;
lgnIp: string;
lgnMethodCd: string;
errOcrnYn: string;
remoteIp: string;
lgnMthd: string;
lgnErrCd: string;
siteId: string;
lgnDt: string;
}
export type LoginlogList = PageableList<Loginlog>;
export interface LoginlogService {
getLoginlogList: (params: LoginlogParams) => Promise<LoginlogList>;
getLoginlogInfo: (lgnSn: string) => Promise<LoginlogInfo>;
getLoginlogList: (params: LoginlogParams) => Promise<LoginlogList>;
}
+1
View File
@@ -0,0 +1 @@
export { ApiLogPage } from "./ui/apilog-page";
@@ -0,0 +1,59 @@
import dayjs from "dayjs";
import type { ColumnDef } from "@tanstack/react-table";
import type { ApiLog } from "@/entities/apilog";
export const columns: ColumnDef<ApiLog>[] = [
{
id: "no",
header: "NO",
cell: ({ row, table }) => {
const totalRows = table.getRowCount();
const { pageIndex, pageSize } = table.getState().pagination;
return totalRows - (pageIndex * pageSize + row.index);
},
},
{
accessorKey: "dmndDt",
header: "요청일시",
cell: ({ row }) => {
const date = dayjs(row.original.dmndDt);
return date.format("YYYY-MM-DD HH:mm:ss");
},
},
{
accessorKey: "userId",
header: "사용자ID",
},
{
accessorKey: "ipAddr",
header: "요청IP",
},
{
accessorKey: "apiHttpPmhd",
header: "요청방식",
cell: ({ row }) => {
const val = row.original.apiHttpPmhd;
if (val === "POST") {
return "저장";
} else if (val === "PUT") {
return "수정";
} else if (val === "DELETE") {
return "삭제";
} else if (val === "GET") {
return "조회";
} else {
return "기타";
}
},
},
{
accessorKey: "dmndUrl",
header: "요청URL",
},
{
accessorKey: "siteId",
header: "사이트ID",
}
];
+218
View File
@@ -0,0 +1,218 @@
"use client";
import dayjs from "dayjs";
import { useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
type PaginationState,
} from "@tanstack/react-table";
import {
Button,
DateRangePicker,
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import { ApiLogApi } from "@/entities/apilog";
import { type ApiLogParams} from "@/entities/apilog/model/apilog-interface";
import { useDataTable } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { columns } from "@/pages/apilog/model/table-columns";
type DateRange = {
from: Date | undefined;
to?: Date | undefined;
};
function ApiLogPage({ }) {
// 1주일 전부터 오늘까지 기본 날짜 설정
const defaultDateRange: DateRange = {
from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(),
};
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [loginlogPostQuery, setLoginlogPostQuery] = useState< ApiLogParams>({
userId: "",
siteId: "",
apiHttpPmhd: "",
dmndUrl: "",
ipAddr: "",
searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
searchDateTo: dayjs().format("YYYY-MM-DD"),
sort:"lgnDt,asc"
});
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination],
queryFn: () =>
ApiLogApi.getApiLogList({
...loginlogPostQuery,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
placeholderData: keepPreviousData,
});
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
},
dataTableOptions: {
viewPagination: true,
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
query.refetch();
};
const handleLoginlogParamsChange =
(key: keyof ApiLogParams) => (value: unknown) => {
setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value });
};
const handlePresetDateRange = (value: string) => {
let presetDateRange: DateRange | undefined;
switch (value) {
case "1W":
presetDateRange = {
from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(),
};
break;
case "1M":
presetDateRange = {
from: dayjs().subtract(30, "day").toDate(),
to: dayjs().toDate(),
};
break;
case "3M":
presetDateRange = {
from: dayjs().subtract(90, "day").toDate(),
to: dayjs().toDate(),
};
break;
default:
presetDateRange = undefined;
}
handleDateRangeChange(presetDateRange);
};
const handleDateRangeChange = (value: DateRange | undefined) => {
setDateRange(value || defaultDateRange);
setLoginlogPostQuery({
...loginlogPostQuery,
searchDateFrom: dayjs(value?.from).format("YYYY-MM-DD") ?? dayjs().subtract(7, "day").format("YYYY-MM-DD"),
searchDateTo: dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"),
});
};
return (
<div className="flex-1">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2">조회기간</Label>
<DateRangePicker
value={dateRange}
onValueChange={handleDateRangeChange}
>
<div className="card card-border flex items-center gap-2">
<Select onValueChange={handlePresetDateRange}>
<SelectTrigger>
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1W">일주일</SelectItem>
<SelectItem value="1M">1개월</SelectItem>
<SelectItem value="3M">3개월</SelectItem>
</SelectContent>
</Select>
</div>
</DateRangePicker>
<Label className="ml-4 mr-2 text-label">사용자ID</Label>
<Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={loginlogPostQuery.userId}
onChange={(e) =>
handleLoginlogParamsChange("userId")(e.target.value)
}
/>
<Label className="ml-4 mr-2 text-label">상태</Label>
<Select onValueChange={handleLoginlogParamsChange("apiHttpPmhd")}>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="POST">저장</SelectItem>
<SelectItem value="PUT">수정</SelectItem>
<SelectItem value="DELETE">삭제</SelectItem>
<SelectItem value="GET">조회</SelectItem>
</SelectContent>
</Select>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
</form>
</div>
<div className="card card-border">
<div className="card-header">
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">로그인로그 목록</h3>
<div className="flex gap-2">
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
}
export {ApiLogPage };
@@ -18,7 +18,7 @@ export const columns: ColumnDef<Loginlog>[] = [
header: "등록일",
cell: ({ row }) => {
const date = dayjs(row.original.lgnDt);
return date.format("YYYY-MM-DD HH24:mi:ss");
return date.format("YYYY-MM-DD HH:mm:ss");
},
},
{
@@ -26,24 +26,24 @@ export const columns: ColumnDef<Loginlog>[] = [
header: "사용자ID",
},
{
accessorKey: "로그인IP",
header: "remoteIp",
accessorKey: "remoteIp",
header: "로그인IP",
},
{
accessorKey: "lgnMthd",
header: "접속방식",
},
{
accessorKey: "오류여부",
header: "errOcrnYn",
accessorKey: "errOcrnYn",
header: "오류여부",
},
{
accessorKey: "오류코드",
header: "lgnErrCd",
accessorKey: "lgnErrCd",
header: "오류코드",
},
{
accessorKey: "사이트ID",
header: "siteId",
accessorKey: "siteId",
header: "사이트ID",
}
];
+167 -42
View File
@@ -1,77 +1,183 @@
"use client";
import dayjs from "dayjs";
import { useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import {
getCoreRowModel,
useReactTable,
type PaginationState,
} from "@tanstack/react-table";
import { Button, DataTable, Input, Label } from "@common/components";
import { LoginlogApi} from "../../../entities/loginlog/api/loginlog-service";
import { type LoginlogParams} from "../../../entities/loginlog/model/loginlog-interface";
import {
Button,
DateRangePicker,
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import { LoginlogApi } from "@/entities/loginlog";
import { type LoginlogParams} from "@/entities/loginlog/model/loginlog-interface";
import { useDataTable } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config";
import { columns } from "../model/table-columns";
import { columns } from "@/pages/loginlog/model/table-columns";
type LoginlogPageProps = {
lgnSn?: string;
type DateRange = {
from: Date | undefined;
to?: Date | undefined;
};
function LoginlogPage({ }: LoginlogPageProps) {
const [loginlogQuery, setLoginlogQuery] = useState<LoginlogParams>({
userId: "",
siteId: "",
});
function LoginlogPage({ }) {
// 1주일 전부터 오늘까지 기본 날짜 설정
const defaultDateRange: DateRange = {
from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(),
};
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const { data: loginlogList, refetch } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, , pagination],
const [loginlogPostQuery, setLoginlogPostQuery] = useState<LoginlogParams>({
userId: "",
siteId: "",
errOcrnYn: "N",
remoteIp: "",
lgnMthd: "",
lgnErrCd: "",
searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
searchDateTo: dayjs().format("YYYY-MM-DD"),
sort:"lgnDt,asc"
});
const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination],
queryFn: () =>
LoginlogApi.getLoginlogList({
...loginlogQuery,
page: pagination.pageIndex,
...loginlogPostQuery,
page: pagination.pageIndex,
size: pagination.pageSize,
}),
placeholderData: keepPreviousData,
placeholderData: keepPreviousData,
});
const table = useReactTable({
data: loginlogList?.content ?? [],
columns,
rowCount: loginlogList?.totalElements ?? 0,
state: { pagination },
manualPagination: true,
enableMultiRowSelection: false,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
const { tableComponent, tableInstance: table } = useDataTable({
query,
tableOptions: {
columns,
state: {
pagination,
},
manualPagination: true,
enableMultiRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onPaginationChange: setPagination,
},
dataTableOptions: {
viewPagination: true,
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
refetch();
query.refetch();
};
const handleLoginlogParamsChange =
(key: keyof LoginlogParams) => (value: unknown) => {
setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value });
};
const handlePresetDateRange = (value: string) => {
let presetDateRange: DateRange | undefined;
switch (value) {
case "1W":
presetDateRange = {
from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(),
};
break;
case "1M":
presetDateRange = {
from: dayjs().subtract(30, "day").toDate(),
to: dayjs().toDate(),
};
break;
case "3M":
presetDateRange = {
from: dayjs().subtract(90, "day").toDate(),
to: dayjs().toDate(),
};
break;
default:
presetDateRange = undefined;
}
handleDateRangeChange(presetDateRange);
};
const handleDateRangeChange = (value: DateRange | undefined) => {
setDateRange(value || defaultDateRange);
setLoginlogPostQuery({
...loginlogPostQuery,
searchDateFrom: dayjs(value?.from).format("YYYY-MM-DD") ?? dayjs().subtract(7, "day").format("YYYY-MM-DD"),
searchDateTo: dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"),
});
};
return (
<div className="flex-1">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label htmlFor="" className="mr-2 text-label">
로그인로그
</Label>
<Label className="mr-2">조회기간</Label>
<DateRangePicker
value={dateRange}
onValueChange={handleDateRangeChange}
>
<div className="card card-border flex items-center gap-2">
<Select onValueChange={handlePresetDateRange}>
<SelectTrigger>
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1W">일주일</SelectItem>
<SelectItem value="1M">1개월</SelectItem>
<SelectItem value="3M">3개월</SelectItem>
</SelectContent>
</Select>
</div>
</DateRangePicker>
<Label className="ml-4 mr-2 text-label">사용자ID</Label>
<Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={loginlogQuery.userId}
value={loginlogPostQuery.userId}
onChange={(e) =>
setLoginlogQuery({
...loginlogQuery,
userId: e.target.value,
})
handleLoginlogParamsChange("userId")(e.target.value)
}
/>
<Label className="ml-4 mr-2 text-label">상태</Label>
<Select onValueChange={handleLoginlogParamsChange("errOcrnYn")}>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="N">정상</SelectItem>
<SelectItem value="Y">오류</SelectItem>
</SelectContent>
</Select>
<Button className="h-[3.2rem]" type="submit">
조회
</Button>
@@ -79,11 +185,30 @@ function LoginlogPage({ }: LoginlogPageProps) {
</div>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">로그인로그 목록</h3>
</div>
<div className="card !m-4 h-[calc(100vh-35rem)] overflow-auto !p-0">
<DataTable table={table} viewPagination />
</div>
<div className="flex w-full items-center justify-between">
<h3 className="text-2xl font-medium text-label">로그인로그 목록</h3>
<div className="flex gap-2">
<Select
value={table.getState().pagination.pageSize.toString()}
onValueChange={(value) => {
table.setPageSize(parseInt(value));
}}
>
<SelectTrigger className="h-[3.2rem] w-[12rem]">
<SelectValue placeholder="페이지 크기" />
</SelectTrigger>
<SelectContent>
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={pageSize.toString()}>
{pageSize}개씩 보기
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
</div>
</div>
);
@@ -46,7 +46,7 @@ function IPManagementDialog({
const { data: ipList } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.IPS, props.open],
queryFn: () => IpApi.getIpList({ siteId, sort: [] }),
queryFn: () => IpApi.getIpList({ siteId, sort: "" }),
select: (data) => {
const ips = data.content.map((item) => ({
...item,
+3 -3
View File
@@ -51,7 +51,7 @@ function UsersPage() {
grpId: "",
frDateYmd: "",
toDateYmd: "",
sort: ["aprvYmd,desc"],
sort: "regDt,desc"
},
});
@@ -288,9 +288,9 @@ function UsersPage() {
<h3 className="text-2xl font-medium text-label">회원 목록</h3>
<div className="flex gap-2">
<Select
defaultValue="aprvYmd,desc"
defaultValue="regDt,desc"
onValueChange={(value) => {
setValue("sort", [value]);
setValue("sort", value);
}}
>
<SelectTrigger>
+2 -1
View File
@@ -19,7 +19,8 @@ export const ENDPOINT = {
AUTHORIZATIONS: "/user-service/api/v1/authorizations",
ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations",
ROLES: "/user-service/api/v1/roles",
LOGINLOG: "/user-service//api/v1/login-logs",
LOGINLOG: "/user-service/api/v1/login-logs",
APILOG: "/user-service/api/v1/api-logs",
},
CMS_SERVICE: {
SITES: "/cms-service/api/v1/sites",
+1 -1
View File
@@ -30,5 +30,5 @@ export interface PageableList<T> {
export interface PageableParams {
page?: number;
size?: number;
sort?: string[];
sort?: string;
}