snp-connection-monitoring/design-system-preview.html
HYOJIN 88e25abe14 feat(frontend): 디자인 시스템 적용 및 전체 UI 개선 (#42)
- 디자인 시스템 CSS 변수 토큰 적용 (success/warning/danger/info)
- PeriodFilter 공통 컴포넌트 생성 및 통계 페이지 적용
- SERVICE_BADGE_VARIANTS 공통 상수 추출
- 통계/요청로그/키관리/관리자 페이지 레퍼런스 디자인 반영
- 테이블 규격 통일 (h-8/h-7, px-3 py-1, text-xs, Button xs)
- 타이틀 아이콘 전체 페이지 통일
- 카드 테두리 디자인 통일 (border + rounded-xl)
- FHD 1920x1080 최적화
2026-04-17 14:45:27 +09:00

706 lines
38 KiB
HTML
Raw Blame 히스토리

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SNP Connection Monitoring — 디자인 시스템 프리뷰</title>
<style>
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
/* Brand Scales */
--color-primary-50: #F5F7F9; --color-primary-100: #E5EBF2;
--color-primary-200: #D2DAE5; --color-primary-300: #B7C5D7;
--color-primary-400: #8AA5C7; --color-primary-500: #6D94C5;
--color-primary-600: #507FB9; --color-primary-700: #3B669C;
--color-primary-800: #2D507B; --color-primary-900: #1B2C41;
--color-primary-950: #0E1A2B;
--color-secondary-50: #F5F7F9; --color-secondary-100: #E6EDF5;
--color-secondary-200: #D1DAE5; --color-secondary-300: #B7C8D7;
--color-secondary-400: #89AAC8; --color-secondary-500: #CBDCEB;
--color-secondary-600: #6B9AC8; --color-secondary-700: #4E88BB;
--color-secondary-800: #396E9D; --color-secondary-900: #1B2F41;
--color-secondary-950: #0E1A2B;
--color-accent-50: #F9F8F6; --color-accent-100: #F2EDE4;
--color-accent-200: #E4E0D7; --color-accent-300: #D5CDB9;
--color-accent-400: #C4B48C; --color-accent-500: #E8DFCA;
--color-accent-600: #B59854; --color-accent-700: #9A7E3E;
--color-accent-800: #786230; --color-accent-900: #3F351D;
--color-accent-950: #2B2312;
/* Semantic Tokens - Light */
--color-primary: #507FB9;
--color-primary-hover: #3B669C;
--color-primary-active: #2D507B;
--color-primary-subtle: #F5F7F9;
--color-primary-text: #2D507B;
--color-secondary: #CBDCEB;
--color-secondary-hover: #89AAC8;
--color-secondary-active: #4E88BB;
--color-secondary-subtle: #F5F7F9;
--color-secondary-text: #396E9D;
--color-accent: #E8DFCA;
--color-accent-hover: #C4B48C;
--color-accent-active: #B59854;
--color-accent-subtle: #F9F8F6;
--color-accent-text: #786230;
--color-success: #059669; --color-warning: #D97706;
--color-danger: #DC2626; --color-info: #0284C7;
--color-bg-base: #F5EFE6; --color-bg-surface: #FFFFFF;
--color-bg-elevated: #FFFFFF;
--color-border: #D5D2CC; --color-border-strong: #9198A1;
--color-text-primary: #1E2329; --color-text-secondary: #5C6570;
--color-text-tertiary: #9198A1;
--color-chart-1: #507FB9; --color-chart-2: #B59854;
--color-chart-3: #B5607D; --color-chart-4: #3D998A;
--color-chart-5: #8B6DB5; --color-chart-6: #C07850;
}
.dark {
--color-primary: #B7C5D7; --color-primary-hover: #D2DAE5;
--color-primary-active: #8AA5C7; --color-primary-subtle: #1B2C41;
--color-primary-text: #D2DAE5;
--color-secondary: #B7C8D7; --color-secondary-hover: #D1DAE5;
--color-secondary-active: #89AAC8; --color-secondary-subtle: #1B2F41;
--color-secondary-text: #D1DAE5;
--color-accent: #D5CDB9; --color-accent-hover: #E4E0D7;
--color-accent-active: #C4B48C; --color-accent-subtle: #3F351D;
--color-accent-text: #E4E0D7;
--color-success: #34D399; --color-warning: #FBBF24;
--color-danger: #FCA5A5; --color-info: #38BDF8;
--color-bg-base: #131416; --color-bg-surface: #1E2023;
--color-bg-elevated: #282A2E;
--color-border: #3A3C41; --color-border-strong: #5C6570;
--color-text-primary: #F4F5F5; --color-text-secondary: #9198A1;
--color-text-tertiary: #5C6570;
--color-chart-1: #6D94C5; --color-chart-2: #C4B48C;
--color-chart-3: #D4839B; --color-chart-4: #5BB5A6;
--color-chart-5: #B79FD4; --color-chart-6: #D4956B;
}
body {
font-family: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
background: var(--color-bg-base);
color: var(--color-text-primary);
line-height: 1.6;
transition: background 0.3s, color 0.3s;
}
/* === Layout === */
.app { display: flex; min-height: 100vh; }
.sidebar {
width: 260px; background: var(--color-bg-surface);
border-right: 1px solid var(--color-border);
color: var(--color-text-secondary); padding: 24px 16px;
display: flex; flex-direction: column; gap: 8px;
flex-shrink: 0; transition: all 0.3s;
}
.sidebar-logo {
font-size: 18px; font-weight: 700; color: var(--color-text-primary);
padding: 8px 12px 20px; border-bottom: 1px solid var(--color-border);
margin-bottom: 12px; display: flex; align-items: center; gap: 10px;
}
.sidebar-logo svg { flex-shrink: 0; }
.sidebar-logo .logo-rect { fill: var(--color-primary); transition: fill 0.3s; }
.sidebar-item {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border-radius: 8px; font-size: 14px;
cursor: pointer; transition: all 0.15s; color: var(--color-text-secondary);
}
.sidebar-item:hover { background: var(--color-primary-subtle); color: var(--color-primary-text); }
.sidebar-item.active { background: var(--color-primary); color: #fff; font-weight: 500; }
.dark .sidebar-item.active { background: var(--color-primary-subtle); color: var(--color-primary-text); }
.sidebar-section { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-text-tertiary); padding: 16px 12px 6px; font-weight: 600; }
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.header {
background: var(--color-bg-surface); border-bottom: 1px solid var(--color-border);
padding: 16px 32px; display: flex; align-items: center; justify-content: space-between;
transition: all 0.3s;
}
.header h1 { font-size: 20px; font-weight: 600; }
.content { padding: 32px; overflow-y: auto; flex: 1; background: var(--color-bg-base); }
/* === Cards === */
.card {
background: var(--color-bg-surface); border: 1px solid var(--color-border);
border-radius: 12px; padding: 24px; transition: all 0.2s;
}
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.06); }
.card-title { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
.card-subtitle { font-size: 12px; color: var(--color-text-tertiary); margin-bottom: 16px; }
.card-value { font-size: 32px; font-weight: 700; letter-spacing: -0.02em; }
.card-change { font-size: 13px; margin-top: 8px; display: flex; align-items: center; gap: 4px; }
.card-change.up { color: var(--color-success); }
.card-change.down { color: var(--color-danger); }
/* === Stat Grid === */
.stat-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; margin-bottom: 28px; }
/* === Chart Area === */
.chart-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-bottom: 28px; }
.chart-container { height: 260px; position: relative; display: flex; align-items: flex-end; gap: 6px; padding-top: 24px; }
.chart-bar {
flex: 1; border-radius: 6px 6px 0 0; transition: all 0.3s; cursor: pointer;
position: relative; min-width: 20px;
}
.chart-bar:hover { opacity: 0.85; transform: translateY(-2px); }
.chart-label { position: absolute; bottom: -22px; left: 50%; transform: translateX(-50%); font-size: 11px; color: var(--color-text-tertiary); white-space: nowrap; }
/* === Donut Chart === */
.donut-wrap { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; }
.donut { position: relative; width: 160px; height: 160px; }
.donut svg { transform: rotate(-90deg); }
.donut-center {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
text-align: center;
}
.donut-center .value { font-size: 28px; font-weight: 700; }
.donut-center .label { font-size: 12px; color: var(--color-text-tertiary); }
.donut-legend { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }
.legend-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--color-text-secondary); }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; }
/* === Buttons === */
.btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 8px 16px; border-radius: 8px; font-size: 14px;
font-weight: 500; border: none; cursor: pointer;
transition: all 0.15s; font-family: inherit;
}
.btn-primary { background: var(--color-primary); color: #fff; }
.btn-primary:hover { background: var(--color-primary-hover); }
.btn-secondary { background: var(--color-secondary); color: var(--color-secondary-text); }
.btn-secondary:hover { background: var(--color-secondary-hover); }
.btn-accent { background: var(--color-accent); color: var(--color-accent-text); }
.btn-accent:hover { background: var(--color-accent-hover); }
.btn-outline { background: transparent; color: var(--color-text-primary); border: 1px solid var(--color-border-strong); }
.btn-outline:hover { background: var(--color-primary-subtle); }
.btn-ghost { background: transparent; color: var(--color-text-secondary); }
.btn-ghost:hover { background: var(--color-primary-subtle); }
.btn-danger { background: var(--color-danger); color: #fff; }
.btn-danger:hover { opacity: 0.9; }
.btn-sm { padding: 6px 12px; font-size: 13px; }
.btn-lg { padding: 12px 24px; font-size: 16px; }
/* Dark mode: 버튼 채도/대비 강화 */
.dark .btn-primary { background: var(--color-primary-600); color: #fff; }
.dark .btn-primary:hover { background: var(--color-primary-500); }
.dark .btn-secondary { background: var(--color-secondary-700); color: #fff; }
.dark .btn-secondary:hover { background: var(--color-secondary-600); }
.dark .btn-accent { background: var(--color-accent-600); color: #fff; }
.dark .btn-accent:hover { background: var(--color-accent-500); }
.dark .btn-outline { border-color: var(--color-primary-400); color: var(--color-primary-300); }
.dark .btn-outline:hover { background: rgba(80,127,185,0.15); border-color: var(--color-primary-300); }
.dark .btn-ghost { color: var(--color-primary-300); }
.dark .btn-ghost:hover { background: rgba(80,127,185,0.12); }
.dark .btn-danger { background: #EF4444; color: #fff; }
.dark .btn-danger:hover { background: #DC2626; }
/* === Badges === */
.badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 10px; border-radius: 9999px; font-size: 12px; font-weight: 500;
}
.badge-default { background: var(--color-bg-elevated); color: var(--color-text-secondary); border: 1px solid var(--color-border); }
.badge-primary { background: var(--color-primary-subtle); color: var(--color-primary-text); }
.badge-secondary { background: var(--color-secondary-subtle); color: var(--color-secondary-text); }
.badge-accent { background: var(--color-accent-subtle); color: var(--color-accent-text); }
.badge-success { background: #ecfdf5; color: var(--color-success); }
.badge-warning { background: #fffbeb; color: var(--color-warning); }
.badge-danger { background: #fef2f2; color: var(--color-danger); }
.badge-info { background: #f0f9ff; color: var(--color-info); }
.dark .badge-success { background: rgba(52,211,153,0.1); }
.dark .badge-warning { background: rgba(251,191,36,0.1); }
.dark .badge-danger { background: rgba(252,165,165,0.1); }
.dark .badge-info { background: rgba(56,189,248,0.1); }
/* === Badge Filled (Button shape) === */
.badge-filled {
border-radius: 6px; padding: 4px 12px;
}
/* Filled 전용 색상 — Chart Palette 1:1 맵핑, 연한 배경 + 색상 텍스트 */
/* Blue = Chart 1 */
.badge-filled.badge-blue { background: rgba(80,127,185,0.14); color: #3B669C; }
/* Gold = Chart 2 */
.badge-filled.badge-gold { background: rgba(181,152,84,0.14); color: #8A7230; }
/* Rose = Chart 3 */
.badge-filled.badge-rose { background: rgba(181,96,125,0.12); color: #9E4A67; }
/* Teal = Chart 4 */
.badge-filled.badge-teal { background: rgba(61,153,138,0.12); color: #2E7D6E; }
/* Lavender = Chart 5 */
.badge-filled.badge-lavender { background: rgba(139,109,181,0.12); color: #7B5DA5; }
/* Coral = Chart 6 */
.badge-filled.badge-coral { background: rgba(192,120,80,0.12); color: #A86440; }
/* Dark mode filled — Chart dark palette */
.dark .badge-filled.badge-blue { background: rgba(109,148,197,0.14); color: #6D94C5; }
.dark .badge-filled.badge-gold { background: rgba(196,180,140,0.14); color: #C4B48C; }
.dark .badge-filled.badge-rose { background: rgba(212,131,155,0.14); color: #D4839B; }
.dark .badge-filled.badge-teal { background: rgba(91,181,166,0.14); color: #5BB5A6; }
.dark .badge-filled.badge-lavender { background: rgba(183,159,212,0.14); color: #B79FD4; }
.dark .badge-filled.badge-coral { background: rgba(212,149,107,0.14); color: #D4956B; }
/* === Table === */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
text-align: left; padding: 12px 16px; font-weight: 600; font-size: 12px;
color: var(--color-text-tertiary); text-transform: uppercase; letter-spacing: 0.03em;
border-bottom: 1px solid var(--color-border);
}
td { padding: 14px 16px; border-bottom: 1px solid var(--color-border); }
tr:hover td { background: var(--color-primary-subtle); }
/* === Input === */
.input-group { display: flex; flex-direction: column; gap: 6px; }
.input-label { font-size: 13px; font-weight: 500; color: var(--color-text-secondary); }
.input {
padding: 10px 14px; border: 1px solid var(--color-border); border-radius: 8px;
font-size: 14px; font-family: inherit; background: var(--color-bg-surface);
color: var(--color-text-primary); transition: all 0.15s; outline: none;
}
.input:focus { border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(80,127,185,0.15); }
.input::placeholder { color: var(--color-text-tertiary); }
/* === Theme Toggle === */
.theme-toggle {
background: var(--color-bg-elevated); border: 1px solid var(--color-border);
border-radius: 8px; padding: 8px 14px; cursor: pointer;
display: flex; align-items: center; gap: 6px; font-size: 13px;
color: var(--color-text-secondary); font-family: inherit; transition: all 0.15s;
}
.theme-toggle:hover { border-color: var(--color-border-strong); }
/* === Color Swatch === */
.swatch-grid { display: grid; grid-template-columns: repeat(11, 1fr); gap: 4px; }
.swatch {
aspect-ratio: 1; border-radius: 8px; display: flex; align-items: flex-end;
justify-content: center; padding: 4px; font-size: 10px; font-weight: 500;
}
.swatch-row-label { font-size: 13px; font-weight: 600; margin-bottom: 8px; margin-top: 20px; color: var(--color-text-secondary); }
/* === Section === */
.section { margin-bottom: 40px; }
.section-title { font-size: 16px; font-weight: 600; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
.section-title::before { content: ''; width: 4px; height: 20px; background: var(--color-primary); border-radius: 2px; }
.component-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 16px; }
/* === Status Dot === */
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.status-dot.online { background: var(--color-success); }
.status-dot.warning { background: var(--color-warning); }
.status-dot.offline { background: var(--color-danger); }
.status-dot.maintenance { background: var(--color-info); }
/* === Toast === */
.toast {
display: flex; align-items: center; gap: 12px;
padding: 14px 18px; border-radius: 10px; font-size: 14px;
border-left: 4px solid;
}
.toast-success { background: #ecfdf5; border-color: var(--color-success); color: #065f46; }
.toast-warning { background: #fffbeb; border-color: var(--color-warning); color: #92400e; }
.toast-danger { background: #fef2f2; border-color: var(--color-danger); color: #991b1b; }
.toast-info { background: #f0f9ff; border-color: var(--color-info); color: #075985; }
.dark .toast-success { background: rgba(52,211,153,0.08); color: var(--color-success); }
.dark .toast-warning { background: rgba(251,191,36,0.08); color: var(--color-warning); }
.dark .toast-danger { background: rgba(252,165,165,0.08); color: var(--color-danger); }
.dark .toast-info { background: rgba(56,189,248,0.08); color: var(--color-info); }
.toast-grid { display: flex; flex-direction: column; gap: 10px; }
/* === Responsive === */
@media (max-width: 1024px) {
.stat-grid { grid-template-columns: repeat(2, 1fr); }
.chart-grid { grid-template-columns: 1fr; }
.sidebar { display: none; }
}
</style>
</head>
<body>
<div class="app" id="app">
<!-- Sidebar -->
<nav class="sidebar">
<div class="sidebar-logo">
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<rect class="logo-rect" width="28" height="28" rx="8" fill="#507FB9"/>
<path d="M7 18 L14 10 L21 18" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 22 L21 22" stroke="white" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
</svg>
SNP Connection
</div>
<div class="sidebar-section">모니터링</div>
<div class="sidebar-item active">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
대시보드
</div>
<div class="sidebar-item">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
서비스 상태
</div>
<div class="sidebar-item">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20V10"/><path d="M18 20V4"/><path d="M6 20v-4"/></svg>
API 통계
</div>
<div class="sidebar-section">관리</div>
<div class="sidebar-item">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
사용자 관리
</div>
<div class="sidebar-item">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
서비스 설정
</div>
<div class="sidebar-item">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
요청 로그
</div>
</nav>
<!-- Main Content -->
<div class="main">
<header class="header">
<h1>대시보드</h1>
<div style="display:flex;gap:10px;align-items:center;">
<div class="input-group" style="flex-direction:row;align-items:center;gap:8px;">
<input type="text" class="input" placeholder="검색..." style="width:220px;padding:8px 12px;">
</div>
<button class="theme-toggle" onclick="toggleTheme()">
<span id="theme-icon">🌙</span>
<span id="theme-label">다크 모드</span>
</button>
</div>
</header>
<div class="content">
<!-- Stat Cards -->
<div class="stat-grid">
<div class="card">
<div class="card-title">총 API 요청</div>
<div class="card-subtitle">오늘</div>
<div class="card-value" style="color:var(--color-primary);">12,847</div>
<div class="card-change up">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 15l-6-6-6 6"/></svg>
+12.5% vs 어제
</div>
</div>
<div class="card">
<div class="card-title">활성 서비스</div>
<div class="card-subtitle">연결된 서비스</div>
<div class="card-value" style="color:var(--color-success);">24<span style="font-size:16px;color:var(--color-text-tertiary);font-weight:400;"> / 26</span></div>
<div class="card-change down">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6 9l6 6 6-6"/></svg>
2개 서비스 점검중
</div>
</div>
<div class="card">
<div class="card-title">평균 응답 시간</div>
<div class="card-subtitle">최근 1시간</div>
<div class="card-value" style="color:var(--color-secondary-text);">142<span style="font-size:16px;color:var(--color-text-tertiary);font-weight:400;">ms</span></div>
<div class="card-change up">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 15l-6-6-6 6"/></svg>
-8.2% 개선
</div>
</div>
<div class="card">
<div class="card-title">에러율</div>
<div class="card-subtitle">최근 24시간</div>
<div class="card-value" style="color:var(--color-warning);">0.34<span style="font-size:16px;color:var(--color-text-tertiary);font-weight:400;">%</span></div>
<div class="card-change up">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 15l-6-6-6 6"/></svg>
정상 범위
</div>
</div>
</div>
<!-- Charts Row -->
<div class="chart-grid">
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;">
<div>
<div class="card-title">시간대별 API 요청 추이</div>
<div class="card-subtitle">최근 12시간</div>
</div>
<div class="component-row" style="margin-bottom:0;">
<button class="btn btn-ghost btn-sm" style="opacity:0.5;">일간</button>
<button class="btn btn-primary btn-sm">시간</button>
<button class="btn btn-ghost btn-sm" style="opacity:0.5;"></button>
</div>
</div>
<div class="chart-container" style="padding-bottom:28px;">
<div class="chart-bar" style="height:45%;background:var(--color-chart-1);"><span class="chart-label">00</span></div>
<div class="chart-bar" style="height:35%;background:var(--color-chart-1);"><span class="chart-label">01</span></div>
<div class="chart-bar" style="height:25%;background:var(--color-chart-1);"><span class="chart-label">02</span></div>
<div class="chart-bar" style="height:20%;background:var(--color-chart-1);"><span class="chart-label">03</span></div>
<div class="chart-bar" style="height:18%;background:var(--color-chart-1);"><span class="chart-label">04</span></div>
<div class="chart-bar" style="height:30%;background:var(--color-chart-1);"><span class="chart-label">05</span></div>
<div class="chart-bar" style="height:55%;background:var(--color-chart-1);"><span class="chart-label">06</span></div>
<div class="chart-bar" style="height:75%;background:var(--color-chart-1);"><span class="chart-label">07</span></div>
<div class="chart-bar" style="height:90%;background:var(--color-chart-1);"><span class="chart-label">08</span></div>
<div class="chart-bar" style="height:95%;background:var(--color-chart-2);"><span class="chart-label">09</span></div>
<div class="chart-bar" style="height:85%;background:var(--color-chart-1);"><span class="chart-label">10</span></div>
<div class="chart-bar" style="height:80%;background:var(--color-chart-1);"><span class="chart-label">11</span></div>
</div>
</div>
<div class="card">
<div class="card-title">서비스별 요청 비율</div>
<div class="card-subtitle">전체 트래픽 기준</div>
<div class="donut-wrap" style="margin-top:12px;">
<div class="donut">
<svg width="160" height="160" viewBox="0 0 160 160">
<circle cx="80" cy="80" r="60" fill="none" stroke="var(--color-border)" stroke-width="24"/>
<circle cx="80" cy="80" r="60" fill="none" stroke="var(--color-chart-1)" stroke-width="24" stroke-dasharray="131 246" stroke-dashoffset="0"/>
<circle cx="80" cy="80" r="60" fill="none" stroke="var(--color-chart-2)" stroke-width="24" stroke-dasharray="83 294" stroke-dashoffset="-131"/>
<circle cx="80" cy="80" r="60" fill="none" stroke="var(--color-chart-3)" stroke-width="24" stroke-dasharray="60 317" stroke-dashoffset="-214"/>
<circle cx="80" cy="80" r="60" fill="none" stroke="var(--color-chart-4)" stroke-width="24" stroke-dasharray="38 339" stroke-dashoffset="-274"/>
<circle cx="80" cy="80" r="60" fill="none" stroke="var(--color-chart-6)" stroke-width="24" stroke-dasharray="65 312" stroke-dashoffset="-312"/>
</svg>
<div class="donut-center">
<div class="value">26</div>
<div class="label">서비스</div>
</div>
</div>
<div class="donut-legend">
<div class="legend-item"><div class="legend-dot" style="background:var(--color-chart-1);"></div>AIS 34.8%</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--color-chart-2);"></div>기상 22.0%</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--color-chart-3);"></div>항로 15.9%</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--color-chart-4);"></div>조류 10.1%</div>
<div class="legend-item"><div class="legend-dot" style="background:var(--color-chart-6);"></div>기타 17.2%</div>
</div>
</div>
</div>
</div>
<!-- Table -->
<div class="card section">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<div>
<div class="card-title">서비스 연결 현황</div>
<div class="card-subtitle">모니터링 대상 서비스 목록</div>
</div>
<button class="btn btn-primary btn-sm">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 5v14M5 12h14"/></svg>
서비스 추가
</button>
</div>
<div class="table-wrap">
<table>
<thead>
<tr><th>서비스명</th><th>상태</th><th>API 키</th><th>요청수</th><th>응답시간</th><th>마지막 응답</th></tr>
</thead>
<tbody>
<tr>
<td style="font-weight:500;">AIS 선박위치 API</td>
<td><span class="badge badge-success"><span class="status-dot online"></span> 정상</span></td>
<td><code style="font-size:12px;background:var(--color-primary-subtle);padding:2px 8px;border-radius:4px;color:var(--color-primary-text);">snp-ais-****-7f2a</code></td>
<td>4,521</td>
<td>89ms</td>
<td style="color:var(--color-text-tertiary);">방금 전</td>
</tr>
<tr>
<td style="font-weight:500;">해양기상 데이터</td>
<td><span class="badge badge-success"><span class="status-dot online"></span> 정상</span></td>
<td><code style="font-size:12px;background:var(--color-primary-subtle);padding:2px 8px;border-radius:4px;color:var(--color-primary-text);">snp-wth-****-3e8b</code></td>
<td>2,847</td>
<td>156ms</td>
<td style="color:var(--color-text-tertiary);">3초 전</td>
</tr>
<tr>
<td style="font-weight:500;">항로 정보 서비스</td>
<td><span class="badge badge-warning"><span class="status-dot warning"></span> 지연</span></td>
<td><code style="font-size:12px;background:var(--color-primary-subtle);padding:2px 8px;border-radius:4px;color:var(--color-primary-text);">snp-rte-****-9d1c</code></td>
<td>2,054</td>
<td>342ms</td>
<td style="color:var(--color-text-tertiary);">12초 전</td>
</tr>
<tr>
<td style="font-weight:500;">조류/해류 API</td>
<td><span class="badge badge-info"><span class="status-dot maintenance"></span> 점검</span></td>
<td><code style="font-size:12px;background:var(--color-primary-subtle);padding:2px 8px;border-radius:4px;color:var(--color-primary-text);">snp-crt-****-5a7e</code></td>
<td>1,302</td>
<td></td>
<td style="color:var(--color-text-tertiary);">10분 전</td>
</tr>
<tr>
<td style="font-weight:500;">CCTV 영상 서비스</td>
<td><span class="badge badge-danger"><span class="status-dot offline"></span> 장애</span></td>
<td><code style="font-size:12px;background:var(--color-primary-subtle);padding:2px 8px;border-radius:4px;color:var(--color-primary-text);">snp-cam-****-2b4f</code></td>
<td>123</td>
<td></td>
<td style="color:var(--color-text-tertiary);">45분 전</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Components Showcase -->
<div class="section">
<div class="section-title">컴포넌트 시스템</div>
<div style="margin-bottom:24px;">
<div style="font-size:13px;font-weight:600;color:var(--color-text-secondary);margin-bottom:10px;">Buttons</div>
<div class="component-row">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-accent">Accent</button>
<button class="btn btn-outline">Outline</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-danger">Danger</button>
</div>
<div class="component-row">
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary">Medium</button>
<button class="btn btn-primary btn-lg">Large</button>
</div>
</div>
<div style="margin-bottom:24px;">
<div style="font-size:13px;font-weight:600;color:var(--color-text-secondary);margin-bottom:10px;">Badges — Pill (기본)</div>
<div class="component-row">
<span class="badge badge-default">Default</span>
<span class="badge badge-primary">Primary</span>
<span class="badge badge-secondary">Secondary</span>
<span class="badge badge-accent">Accent</span>
<span class="badge badge-success">Success</span>
<span class="badge badge-warning">Warning</span>
<span class="badge badge-danger">Danger</span>
<span class="badge badge-info">Info</span>
</div>
<div style="font-size:13px;font-weight:600;color:var(--color-text-secondary);margin-bottom:10px;margin-top:16px;">Badges — Filled (버튼 스타일)</div>
<div class="component-row">
<span class="badge badge-filled badge-default">Default</span>
<span class="badge badge-filled badge-blue">Blue</span>
<span class="badge badge-filled badge-gold">Gold</span>
<span class="badge badge-filled badge-rose">Rose</span>
<span class="badge badge-filled badge-teal">Teal</span>
<span class="badge badge-filled badge-lavender">Lavender</span>
<span class="badge badge-filled badge-coral">Coral</span>
</div>
</div>
<div style="margin-bottom:24px;">
<div style="font-size:13px;font-weight:600;color:var(--color-text-secondary);margin-bottom:10px;">Inputs</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px;max-width:720px;">
<div class="input-group">
<label class="input-label">서비스명</label>
<input type="text" class="input" placeholder="서비스명 입력">
</div>
<div class="input-group">
<label class="input-label">API 엔드포인트</label>
<input type="text" class="input" value="https://api.example.com/v1">
</div>
<div class="input-group">
<label class="input-label">타임아웃 (ms)</label>
<input type="number" class="input" placeholder="5000">
</div>
</div>
</div>
<div>
<div style="font-size:13px;font-weight:600;color:var(--color-text-secondary);margin-bottom:10px;">Toasts</div>
<div class="toast-grid" style="max-width:480px;">
<div class="toast toast-success">✓ 서비스가 정상적으로 등록되었습니다.</div>
<div class="toast toast-warning">⚠ 응답 시간이 임계값을 초과했습니다.</div>
<div class="toast toast-danger">✕ 서비스 연결에 실패했습니다.</div>
<div class="toast toast-info"> 시스템 점검이 예정되어 있습니다.</div>
</div>
</div>
</div>
<!-- Color Palette -->
<div class="section">
<div class="section-title">컬러 팔레트</div>
<div class="swatch-row-label">Primary — Slate Blue</div>
<div class="swatch-grid">
<div class="swatch" style="background:#F5F7F9;color:#1B2C41;">50</div>
<div class="swatch" style="background:#E5EBF2;color:#1B2C41;">100</div>
<div class="swatch" style="background:#D2DAE5;color:#1B2C41;">200</div>
<div class="swatch" style="background:#B7C5D7;color:#1B2C41;">300</div>
<div class="swatch" style="background:#8AA5C7;color:#fff;">400</div>
<div class="swatch" style="background:#6D94C5;color:#fff;">500</div>
<div class="swatch" style="background:#507FB9;color:#fff;">600</div>
<div class="swatch" style="background:#3B669C;color:#fff;">700</div>
<div class="swatch" style="background:#2D507B;color:#fff;">800</div>
<div class="swatch" style="background:#1B2C41;color:#fff;">900</div>
<div class="swatch" style="background:#0E1A2B;color:#fff;">950</div>
</div>
<div class="swatch-row-label">Secondary — Powder Blue</div>
<div class="swatch-grid">
<div class="swatch" style="background:#F5F7F9;color:#1B2F41;">50</div>
<div class="swatch" style="background:#E6EDF5;color:#1B2F41;">100</div>
<div class="swatch" style="background:#D1DAE5;color:#1B2F41;">200</div>
<div class="swatch" style="background:#B7C8D7;color:#1B2F41;">300</div>
<div class="swatch" style="background:#89AAC8;color:#fff;">400</div>
<div class="swatch" style="background:#CBDCEB;color:#396E9D;">500</div>
<div class="swatch" style="background:#6B9AC8;color:#fff;">600</div>
<div class="swatch" style="background:#4E88BB;color:#fff;">700</div>
<div class="swatch" style="background:#396E9D;color:#fff;">800</div>
<div class="swatch" style="background:#1B2F41;color:#fff;">900</div>
<div class="swatch" style="background:#0E1A2B;color:#fff;">950</div>
</div>
<div class="swatch-row-label">Accent — Warm Sand</div>
<div class="swatch-grid">
<div class="swatch" style="background:#F9F8F6;color:#3F351D;">50</div>
<div class="swatch" style="background:#F2EDE4;color:#3F351D;">100</div>
<div class="swatch" style="background:#E4E0D7;color:#3F351D;">200</div>
<div class="swatch" style="background:#D5CDB9;color:#3F351D;">300</div>
<div class="swatch" style="background:#C4B48C;color:#fff;">400</div>
<div class="swatch" style="background:#E8DFCA;color:#786230;">500</div>
<div class="swatch" style="background:#B59854;color:#fff;">600</div>
<div class="swatch" style="background:#9A7E3E;color:#fff;">700</div>
<div class="swatch" style="background:#786230;color:#fff;">800</div>
<div class="swatch" style="background:#3F351D;color:#fff;">900</div>
<div class="swatch" style="background:#2B2312;color:#fff;">950</div>
</div>
<div class="swatch-row-label">Chart Palette — Cool + Warm 교차</div>
<div style="display:flex;gap:6px;margin-top:4px;">
<div style="width:60px;height:60px;border-radius:10px;background:var(--color-chart-1);display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:600;">1</div>
<div style="width:60px;height:60px;border-radius:10px;background:var(--color-chart-2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:600;">2</div>
<div style="width:60px;height:60px;border-radius:10px;background:var(--color-chart-3);display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:600;">3</div>
<div style="width:60px;height:60px;border-radius:10px;background:var(--color-chart-4);display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:600;">4</div>
<div style="width:60px;height:60px;border-radius:10px;background:var(--color-chart-5);display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:600;">5</div>
<div style="width:60px;height:60px;border-radius:10px;background:var(--color-chart-6);display:flex;align-items:center;justify-content:center;color:#786230;font-size:11px;font-weight:600;">6</div>
</div>
</div>
</div>
</div>
</div>
<script>
function toggleTheme() {
const root = document.documentElement;
const icon = document.getElementById('theme-icon');
const label = document.getElementById('theme-label');
root.classList.toggle('dark');
if (root.classList.contains('dark')) {
icon.textContent = '☀️';
label.textContent = '라이트 모드';
} else {
icon.textContent = '🌙';
label.textContent = '다크 모드';
}
}
</script>
</body>
</html>