generated from gc/template-java-maven
- 디자인 시스템 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 최적화
706 lines
38 KiB
HTML
706 lines
38 KiB
HTML
<!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>
|