diff --git a/.vscode/settings.json b/.vscode/settings.json index bf620b9..08fb4f0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "tailwindCSS.experimental.configFile": "apps/monitor-web/tailwind.config.ts", - "cSpell.words": ["jikwon", "supabase"], + "cSpell.words": ["jikwon", "junyeol", "supabase"], "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, diff --git a/apps/monitor-web/src/assets/icons.constants.ts b/apps/monitor-web/src/assets/icons.constants.ts index 0bc9b95..86b80ee 100644 --- a/apps/monitor-web/src/assets/icons.constants.ts +++ b/apps/monitor-web/src/assets/icons.constants.ts @@ -2,6 +2,7 @@ import AlertIcon from "@/assets/icons/alert.svg?react"; import BaseLogo from "@/assets/icons/base-logo.svg?react"; import Check from "@/assets/icons/check.svg?react"; import Clear from "@/assets/icons/clear.svg?react"; +import EditPencil from "@/assets/icons/edit-pencil.svg?react"; import LoadingSpinner from "@/assets/icons/loading-spinner.svg?react"; export const ICON_MAP = { @@ -9,5 +10,6 @@ export const ICON_MAP = { baseLogo: BaseLogo, check: Check, clear: Clear, + editPencil: EditPencil, loadingSpinner: LoadingSpinner, } as const; diff --git a/apps/monitor-web/src/assets/icons/edit-pencil.svg b/apps/monitor-web/src/assets/icons/edit-pencil.svg new file mode 100644 index 0000000..f7ccbb6 --- /dev/null +++ b/apps/monitor-web/src/assets/icons/edit-pencil.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/monitor-web/src/components/common/display/Badge.tsx b/apps/monitor-web/src/components/common/display/Badge.tsx index b2a7e64..f4c6feb 100644 --- a/apps/monitor-web/src/components/common/display/Badge.tsx +++ b/apps/monitor-web/src/components/common/display/Badge.tsx @@ -1,9 +1,6 @@ import { cn } from "@/utils"; -import { - API_STATUS_BADGE_STYLES, - BADGE_BASE_STYLE, - type ApiStatus, -} from "./_internal/badge.constants"; +import type { ApiStatus } from "@/types"; +import { API_STATUS_BADGE_STYLES, BADGE_BASE_STYLE } from "./_internal/badge.constants"; /** * 공통 Badge 컴포넌트입니다. diff --git a/apps/monitor-web/src/components/common/display/_internal/badge.constants.ts b/apps/monitor-web/src/components/common/display/_internal/badge.constants.ts index f429578..17c713a 100644 --- a/apps/monitor-web/src/components/common/display/_internal/badge.constants.ts +++ b/apps/monitor-web/src/components/common/display/_internal/badge.constants.ts @@ -1,17 +1,6 @@ // TODO(준열) : 디자인 시스템 정립에 따라 기본 스타일 및 API 상태에 따른 스타일 변경 예정 -/** - * Badge 컴포넌트에서 사용하는 API 상태 타입입니다. - * - * @remarks - * - `healthy`: 정상 - * - `degraded`: 지연 - * - `outage`: 장애 - * - * @author junyeol - */ - -export type ApiStatus = "healthy" | "degraded" | "outage"; +import type { ApiStatus } from "@/types"; /** * API 상태별 Badge 표시 정보입니다. diff --git a/apps/monitor-web/src/mock/apiDetail.ts b/apps/monitor-web/src/mock/apiDetail.ts new file mode 100644 index 0000000..20886dd --- /dev/null +++ b/apps/monitor-web/src/mock/apiDetail.ts @@ -0,0 +1,71 @@ +import type { + ApiCheckLog, + ApiDetailData, + ApiSummaryData, + DetailSettingsData, + ImpactedFeature, +} from "../pages/ApiDetail/_types"; + +export const MOCK_HEADER_DATA: ApiDetailData = { + name: "Kakao Map API", + statusCode: "404", + status: "healthy" as const, + category: "map", + responseTime: "428ms", + lastChecked: "2026-04-24 15:30", + successRate: "99%", +}; + +export const MOCK_SUMMARY_DATA: ApiSummaryData = { + avgResponseTime: 443, + maxResponseTime: 1230, + minResponseTime: 210, + successRate: 99, + errorCount: 1, + lastErrorAt: "2026-04-24 13:20", +}; + +export const MOCK_LOGS: ApiCheckLog[] = [ + { + id: "1", + status: "healthy", + time: "15:30", + fullDate: "2024-05-13", + message: "정상 작동 중", + statusCode: "HTTP 200", + latency: "428ms", + }, + { + id: "2", + status: "outage", + time: "15:20", + fullDate: "2024-05-13", + message: "Connection Timeout", + statusCode: "HTTP 504", + latency: "5000ms", + }, + { + id: "3", + status: "healthy", + time: "15:10", + fullDate: "2024-05-13", + message: "정상 작동 중", + statusCode: "HTTP 200", + latency: "312ms", + }, +]; + +export const MOCK_FEATURES: ImpactedFeature[] = [ + { id: "1", name: "지도 표시" }, + { id: "2", name: "위치 선택" }, + { id: "3", name: "게시글 작성 시 주소 검색" }, + { id: "4", name: "내 주변 분실물 조회" }, +]; + +export const MOCK_SETTINGS: DetailSettingsData = { + requestUrl: "https://dapi.kakao.com/...", + httpMethod: "GET", + checkInterval: "10분", + isActive: true, + isNotificationEnabled: true, +}; diff --git a/apps/monitor-web/src/mock/index.ts b/apps/monitor-web/src/mock/index.ts new file mode 100644 index 0000000..cad9ca7 --- /dev/null +++ b/apps/monitor-web/src/mock/index.ts @@ -0,0 +1 @@ +export * from "./apiDetail"; diff --git a/apps/monitor-web/src/pages/ApiDetail/ApiDetail.tsx b/apps/monitor-web/src/pages/ApiDetail/ApiDetail.tsx index 3fc8840..522a416 100644 --- a/apps/monitor-web/src/pages/ApiDetail/ApiDetail.tsx +++ b/apps/monitor-web/src/pages/ApiDetail/ApiDetail.tsx @@ -1,12 +1,37 @@ import { useParams } from "react-router-dom"; +import { MOCK_HEADER_DATA, MOCK_SUMMARY_DATA } from "@/mock"; +import { + DetailCheckLogs, + DetailHeader, + DetailImpactedFeatures, + DetailIncidentHistory, + DetailResponseChart, + DetailSettings, + DetailSummaryCards, +} from "./_components"; const ApiDetail = () => { const { apiId } = useParams<{ apiId: string }>(); + console.warn(apiId); return ( -
-

API 상세 - {apiId}

-
+ <> + + + +
+ + +
+ + +
+
+ + + + + ); }; diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/DetailCheckLogs.tsx b/apps/monitor-web/src/pages/ApiDetail/_components/DetailCheckLogs.tsx new file mode 100644 index 0000000..bea1ebd --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_components/DetailCheckLogs.tsx @@ -0,0 +1,61 @@ +import { cn } from "@/utils"; +import { MOCK_LOGS } from "@/mock"; +import type { ApiStatus } from "@/types"; + +const STATUS_CONFIG: Record = { + healthy: { label: "정상", color: "bg-fg-primary-normal-default" }, + outage: { label: "장애", color: "bg-[#FF4D4F]" }, + degraded: { label: "지연", color: "bg-[#FAAD14]" }, +} as const; + +const DetailCheckLogs = () => { + return ( +
+
+

+ 최근 체크 로그 +

+ 10분 주기 +
+ +
+
    + {MOCK_LOGS.map((log) => ( +
  • +
    +
    + +
    + + + {log.message} + + +
    + {log.statusCode} + {log.latency} +
    +
  • + ))} +
+
+
+ ); +}; + +export default DetailCheckLogs; diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/DetailHeader.tsx b/apps/monitor-web/src/pages/ApiDetail/_components/DetailHeader.tsx new file mode 100644 index 0000000..55de651 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_components/DetailHeader.tsx @@ -0,0 +1,105 @@ +import { Badge, BasicButton, Icon } from "@/components"; +import { cn } from "@/utils"; +import type { ApiStatus } from "@/types"; +import type { ApiDetailData } from "../_types"; + +interface DetailHeaderProps { + apiData: ApiDetailData; +} + +const DetailHeader = ({ apiData }: DetailHeaderProps) => { + const headerInfoList = [ + { label: "카테고리", value: apiData.category }, + { label: "응답", value: apiData.responseTime }, + { + label: "마지막 체크", + value: apiData.lastChecked, + dateTime: apiData.lastChecked, + }, + { label: "최근 24시간 성공률", value: apiData.successRate }, + ]; + + return ( +
+
+
+

+ {apiData.name} +

+ +
+
+ + {headerInfoList.map((info) => ( + + ))} +
+
+ + + + + 수정 + + +
+ ); +}; + +export default DetailHeader; + +const STATUS_CONFIG = { + healthy: { + label: "정상", + color: "bg-fg-primary-normal-default", + textColor: "text-fg-primary-normal-default", + }, + outage: { + label: "장애", + color: "bg-[#FF4D4F]", + textColor: "text-[#FF4D4F]", + }, + degraded: { + label: "지연", + color: "bg-[#FAAD14]", + textColor: "text-[#FAAD14]", + }, +} as const; + +const StatusItem = ({ status }: { status: ApiStatus }) => { + const { label, color, textColor } = STATUS_CONFIG[status]; + + return ( +
+
상태
+
+
+
+ ); +}; + +interface InfoItemProps { + label: string; + value: string; + dateTime?: string; +} + +const InfoItem = ({ label, value, dateTime }: InfoItemProps) => { + const Tag = dateTime ? "time" : "span"; + + return ( +
+
{label}
+
+ + {value} + +
+
+ ); +}; diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/DetailImpactedFeatures.tsx b/apps/monitor-web/src/pages/ApiDetail/_components/DetailImpactedFeatures.tsx new file mode 100644 index 0000000..be0b28d --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_components/DetailImpactedFeatures.tsx @@ -0,0 +1,33 @@ +import { Badge } from "@/components"; +import { MOCK_FEATURES } from "@/mock"; + +const DetailImpactedFeatures = () => { + return ( +
+
+

+ 영향 받는 기능 +

+ + 이 API에 장애 시 영향을 받는 사용자 기능 + +
+ +
+
    + {MOCK_FEATURES.map((feature) => ( +
  • + +
  • + ))} +
+
+
+ ); +}; + +export default DetailImpactedFeatures; diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/DetailIncidentHistory.tsx b/apps/monitor-web/src/pages/ApiDetail/_components/DetailIncidentHistory.tsx new file mode 100644 index 0000000..1b2eec9 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_components/DetailIncidentHistory.tsx @@ -0,0 +1,14 @@ +const DetailIncidentHistory = () => { + return ( +
+

+ 최근 장애 / 에러 상세 목록 +

+
+ ); +}; + +export default DetailIncidentHistory; diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/DetailResponseChart.tsx b/apps/monitor-web/src/pages/ApiDetail/_components/DetailResponseChart.tsx new file mode 100644 index 0000000..a42eb11 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_components/DetailResponseChart.tsx @@ -0,0 +1,5 @@ +const DetailResponseChart = () => { + return
; +}; + +export default DetailResponseChart; diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/DetailSettings.tsx b/apps/monitor-web/src/pages/ApiDetail/_components/DetailSettings.tsx new file mode 100644 index 0000000..64d5600 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_components/DetailSettings.tsx @@ -0,0 +1,77 @@ +import { ReactNode } from "react"; +import { Badge, BasicButton, Icon } from "@/components"; +import { MOCK_SETTINGS } from "@/mock"; +import { cn } from "@/utils"; + +const DetailSettings = () => { + const { requestUrl, httpMethod, checkInterval, isActive, isNotificationEnabled } = MOCK_SETTINGS; + + return ( +
+
+

+ API 설정 정보 +

+ + + + 설정 수정 + + +
+ +
+ +
+ {requestUrl} +
+
+ +
+ + + + + + {checkInterval} + + + + + {isActive ? "활성" : "비활성"} + + + + + + {isNotificationEnabled ? "활성" : "비활성"} + + +
+
+
+ ); +}; + +export default DetailSettings; + +const SettingItem = ({ + label, + children, + className, +}: { + label: string; + children: ReactNode; + className?: string; +}) => ( +
+
{label}
+
{children}
+
+); diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/DetailSummaryCards.tsx b/apps/monitor-web/src/pages/ApiDetail/_components/DetailSummaryCards.tsx new file mode 100644 index 0000000..62d9ab8 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_components/DetailSummaryCards.tsx @@ -0,0 +1,48 @@ +import { ReactNode } from "react"; +import type { ApiSummaryData } from "../_types"; + +interface DetailSummaryCardsProps { + data: ApiSummaryData; +} + +const DetailSummaryCards = ({ data }: DetailSummaryCardsProps) => { + return ( +
+
+ +
+
+ {data.avgResponseTime} + ms +
+
+ 최고 {data.maxResponseTime}ms + 최저 {data.minResponseTime}ms +
+
+
+ + + + + + +
+
+ ); +}; + +export default DetailSummaryCards; + +interface SummaryCardProps { + label: string; + value?: string; + children?: ReactNode; +} + +const SummaryCard = ({ label, value, children }: SummaryCardProps) => ( +
+
{label}
+
{children || {value}}
+
+); diff --git a/apps/monitor-web/src/pages/ApiDetail/_components/index.ts b/apps/monitor-web/src/pages/ApiDetail/_components/index.ts index e69de29..1aafabb 100644 --- a/apps/monitor-web/src/pages/ApiDetail/_components/index.ts +++ b/apps/monitor-web/src/pages/ApiDetail/_components/index.ts @@ -0,0 +1,7 @@ +export { default as DetailCheckLogs } from "./DetailCheckLogs"; +export { default as DetailHeader } from "./DetailHeader"; +export { default as DetailImpactedFeatures } from "./DetailImpactedFeatures"; +export { default as DetailIncidentHistory } from "./DetailIncidentHistory"; +export { default as DetailResponseChart } from "./DetailResponseChart"; +export { default as DetailSettings } from "./DetailSettings"; +export { default as DetailSummaryCards } from "./DetailSummaryCards"; diff --git a/apps/monitor-web/src/pages/ApiDetail/_types/DetailCheckLogsType.ts b/apps/monitor-web/src/pages/ApiDetail/_types/DetailCheckLogsType.ts new file mode 100644 index 0000000..1343200 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_types/DetailCheckLogsType.ts @@ -0,0 +1,11 @@ +import type { ApiStatus } from "@/types"; + +export interface ApiCheckLog { + id: string; + status: ApiStatus; + time: string; + fullDate: string; + message: string; + statusCode: string; + latency: string; +} diff --git a/apps/monitor-web/src/pages/ApiDetail/_types/DetailHeader.ts b/apps/monitor-web/src/pages/ApiDetail/_types/DetailHeader.ts new file mode 100644 index 0000000..dd37004 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_types/DetailHeader.ts @@ -0,0 +1,11 @@ +import type { ApiStatus } from "@/types"; + +export interface ApiDetailData { + name: string; + statusCode: string; + status: ApiStatus; + category: string; + responseTime: string; + lastChecked: string; + successRate: string; +} diff --git a/apps/monitor-web/src/pages/ApiDetail/_types/DetailImpactedFeaturesType.ts b/apps/monitor-web/src/pages/ApiDetail/_types/DetailImpactedFeaturesType.ts new file mode 100644 index 0000000..505a0c4 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_types/DetailImpactedFeaturesType.ts @@ -0,0 +1,4 @@ +export interface ImpactedFeature { + id: string; + name: string; +} diff --git a/apps/monitor-web/src/pages/ApiDetail/_types/DetailIncidentHistoryType.ts b/apps/monitor-web/src/pages/ApiDetail/_types/DetailIncidentHistoryType.ts new file mode 100644 index 0000000..17364a2 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_types/DetailIncidentHistoryType.ts @@ -0,0 +1,9 @@ +export type IncidentStatus = "Recovered" | "Ongoing" | "Warning"; + +export interface IncidentHistoryItem { + id: string; + timestamp: string; + duration: string; + status: IncidentStatus; + message: string; +} diff --git a/apps/monitor-web/src/pages/ApiDetail/_types/DetailSettingsType.ts b/apps/monitor-web/src/pages/ApiDetail/_types/DetailSettingsType.ts new file mode 100644 index 0000000..2a15a1a --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_types/DetailSettingsType.ts @@ -0,0 +1,7 @@ +export interface DetailSettingsData { + requestUrl: string; + httpMethod: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + checkInterval: string; + isActive: boolean; + isNotificationEnabled: boolean; +} diff --git a/apps/monitor-web/src/pages/ApiDetail/_types/DetailSummaryCardsType.ts b/apps/monitor-web/src/pages/ApiDetail/_types/DetailSummaryCardsType.ts new file mode 100644 index 0000000..b3b6134 --- /dev/null +++ b/apps/monitor-web/src/pages/ApiDetail/_types/DetailSummaryCardsType.ts @@ -0,0 +1,8 @@ +export interface ApiSummaryData { + avgResponseTime: number; + maxResponseTime: number; + minResponseTime: number; + successRate: number; + errorCount: number; + lastErrorAt: string; +} diff --git a/apps/monitor-web/src/pages/ApiDetail/_types/index.ts b/apps/monitor-web/src/pages/ApiDetail/_types/index.ts index e69de29..6a1f7e8 100644 --- a/apps/monitor-web/src/pages/ApiDetail/_types/index.ts +++ b/apps/monitor-web/src/pages/ApiDetail/_types/index.ts @@ -0,0 +1,6 @@ +export * from "./DetailCheckLogsType"; +export * from "./DetailHeader"; +export * from "./DetailImpactedFeaturesType"; +export * from "./DetailSummaryCardsType"; +export * from "./DetailSettingsType"; +export * from "./DetailIncidentHistoryType"; diff --git a/apps/monitor-web/src/types/ApiStatusType.ts b/apps/monitor-web/src/types/ApiStatusType.ts new file mode 100644 index 0000000..67f7f2e --- /dev/null +++ b/apps/monitor-web/src/types/ApiStatusType.ts @@ -0,0 +1,12 @@ +/** + * Badge 컴포넌트에서 사용하는 API 상태 타입입니다. + * + * @remarks + * - `healthy`: 정상 + * - `degraded`: 지연 + * - `outage`: 장애 + * + * @author junyeol + */ + +export type ApiStatus = "healthy" | "degraded" | "outage"; diff --git a/apps/monitor-web/src/types/index.ts b/apps/monitor-web/src/types/index.ts index 81dbc19..6e26b2a 100644 --- a/apps/monitor-web/src/types/index.ts +++ b/apps/monitor-web/src/types/index.ts @@ -1 +1,2 @@ +export type { ApiStatus } from "./ApiStatusType"; export type { ToastType, Toast } from "./ToastType";