/* 기본 스타일 */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f5f7fa; color: #333; line-height: 1.6; } /* 로딩 스피너 */ .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.9); display: flex; justify-content: center; align-items: center; z-index: 9999; } .spinner { width: 50px; height: 50px; border: 5px solid #f3f3f3; border-top: 5px solid #007bff; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 로그인 화면 */ .login-screen { min-height: 100vh; display: flex; justify-content: center; align-items: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .login-container { background: white; padding: 2rem; border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); width: 100%; max-width: 400px; } .login-header { text-align: center; margin-bottom: 2rem; } .login-header h1 { color: #333; margin-bottom: 0.5rem; } .login-header p { color: #666; font-size: 0.9rem; } .login-tabs { display: flex; margin-bottom: 1.5rem; border-bottom: 1px solid #eee; } .tab-btn { flex: 1; padding: 0.75rem; border: none; background: none; cursor: pointer; font-size: 1rem; color: #666; border-bottom: 2px solid transparent; transition: all 0.3s; } .tab-btn.active { color: #007bff; border-bottom-color: #007bff; } .auth-form { margin-bottom: 1rem; } .form-group { margin-bottom: 1rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 500; color: #333; } .form-group input, .form-group textarea, .form-group select { width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 5px; font-size: 1rem; transition: border-color 0.3s; } .form-group input:focus, .form-group textarea:focus, .form-group select:focus { outline: none; border-color: #007bff; } .form-group small { color: #666; font-size: 0.8rem; } .login-footer { text-align: center; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #eee; } .login-footer p { color: #666; font-size: 0.8rem; } /* 버튼 스타일 */ .btn { padding: 0.75rem 1.5rem; border: none; border-radius: 5px; cursor: pointer; font-size: 1rem; text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; transition: all 0.3s; font-weight: 500; } .btn-primary { background: #007bff; color: white; } .btn-primary:hover { background: #0056b3; } .btn-secondary { background: #6c757d; color: white; } .btn-secondary:hover { background: #545b62; } .btn-success { background: #28a745; color: white; } .btn-success:hover { background: #1e7e34; } .btn-danger { background: #dc3545; color: white; } .btn-danger:hover { background: #c82333; } .btn-warning { background: #ffc107; color: #212529; } .btn-warning:hover { background: #e0a800; } .btn-info { background: #17a2b8; color: white; } .btn-info:hover { background: #138496; } .btn:disabled { opacity: 0.6; cursor: not-allowed; } /* 대시보드 레이아웃 */ .dashboard { display: grid; grid-template-areas: "header header" "sidebar main"; grid-template-columns: 250px 1fr; grid-template-rows: 60px 1fr; min-height: 100vh; } /* 헤더 */ .header { grid-area: header; background: white; padding: 0 2rem; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 100; } .header-left h1 { color: #333; font-size: 1.5rem; } .header-right { display: flex; align-items: center; gap: 1rem; } .user-info { display: flex; align-items: center; gap: 0.5rem; color: #666; } .role-badge { background: #007bff; color: white; padding: 0.2rem 0.5rem; border-radius: 12px; font-size: 0.7rem; text-transform: uppercase; } .role-badge.admin { background: #dc3545; } /* 사이드바 */ .sidebar { grid-area: sidebar; background: #2c3e50; padding: 1rem 0; } .nav-menu { list-style: none; } .nav-link { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1.5rem; color: #bdc3c7; text-decoration: none; transition: all 0.3s; } .nav-link:hover, .nav-link.active { background: #34495e; color: white; } .nav-link.active { border-right: 3px solid #007bff; } /* 메인 콘텐츠 */ .main-content { grid-area: main; padding: 2rem; overflow-y: auto; } .content-section { display: none; } .content-section.active { display: block; } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; } .section-header h2 { color: #333; font-size: 1.8rem; } /* 통계 카드 */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; } .stat-card { background: white; padding: 1.5rem; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); display: flex; align-items: center; gap: 1rem; } .stat-icon { width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; color: white; background: linear-gradient(135deg, #007bff, #0056b3); } .stat-info h3 { font-size: 2rem; font-weight: bold; color: #333; margin-bottom: 0.25rem; } .stat-info p { color: #666; font-size: 0.9rem; } /* 빠른 작업 */ .quick-actions { background: white; padding: 1.5rem; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .quick-actions h3 { margin-bottom: 1rem; color: #333; } .action-buttons { display: flex; gap: 1rem; flex-wrap: wrap; } /* 테이블 */ .table-container { background: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); overflow: hidden; } .data-table { width: 100%; border-collapse: collapse; } .data-table th, .data-table td { padding: 1rem; text-align: left; border-bottom: 1px solid #eee; } .data-table th { background: #f8f9fa; font-weight: 600; color: #333; } .data-table tr:hover { background: #f8f9fa; } /* 상태 표시 */ .status-active { color: #28a745; font-weight: 500; } .status-inactive { color: #dc3545; font-weight: 500; } .status-indicator { display: flex; align-items: center; gap: 0.5rem; } .status-indicator.online { color: #28a745; } .status-indicator.offline { color: #dc3545; } /* API 문서 */ .api-docs { background: white; padding: 2rem; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .endpoint-group { margin-bottom: 3rem; } .endpoint-group h3 { color: #333; margin-bottom: 1.5rem; padding-bottom: 0.5rem; border-bottom: 2px solid #007bff; font-size: 1.3rem; } .endpoint-group h4 { color: #495057; margin-bottom: 1rem; font-size: 1.1rem; } /* API 사용법 정보 */ .api-usage-info { background: #e7f3ff; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #007bff; margin-bottom: 1rem; } .usage-methods h4 { color: #007bff; margin-top: 1rem; margin-bottom: 0.5rem; } /* 엔드포인트 스타일 */ .endpoint { margin-bottom: 1.5rem; border: 1px solid #eee; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } .endpoint-header { background: #f8f9fa; padding: 1rem; display: flex; align-items: center; gap: 1rem; border-bottom: 1px solid #eee; } .method { padding: 0.4rem 0.8rem; border-radius: 4px; font-weight: bold; font-size: 0.8rem; text-transform: uppercase; min-width: 60px; text-align: center; } .method.get { background: #28a745; color: white; } .method.post { background: #007bff; color: white; } .method.put { background: #ffc107; color: #212529; } .method.delete { background: #dc3545; color: white; } .path { font-family: 'Courier New', monospace; font-weight: bold; font-size: 1rem; color: #495057; } .endpoint-description { padding: 1.5rem; } .endpoint-description p { margin-bottom: 0.5rem; } .endpoint-description pre { background: #f8f9fa; padding: 1rem; border-radius: 5px; overflow-x: auto; margin: 0.5rem 0; border: 1px solid #e9ecef; } .endpoint-description code { background: #f8f9fa; padding: 0.2rem 0.4rem; border-radius: 3px; font-family: 'Courier New', monospace; font-size: 0.9rem; color: #e83e8c; } /* 코드 예시 스타일 */ .code-example { margin-bottom: 2rem; border: 1px solid #dee2e6; border-radius: 8px; overflow: hidden; } .code-example h4 { background: #343a40; color: white; padding: 1rem; margin: 0; display: flex; align-items: center; gap: 0.5rem; } .code-example h4 i { font-size: 1.2rem; } /* 탭 스타일 */ .code-tabs { background: white; } .tab-buttons { display: flex; background: #f8f9fa; border-bottom: 1px solid #dee2e6; } .tab-btn { padding: 0.75rem 1.5rem; border: none; background: transparent; cursor: pointer; font-weight: 500; color: #6c757d; transition: all 0.3s; border-bottom: 3px solid transparent; } .tab-btn:hover { background: #e9ecef; color: #495057; } .tab-btn.active { color: #007bff; border-bottom-color: #007bff; background: white; } .tab-content { display: none; padding: 0; } .tab-content.active { display: block; } .tab-content pre { margin: 0; padding: 1.5rem; background: #f8f9fa; border-radius: 0; border: none; overflow-x: auto; } .tab-content code { font-family: 'Courier New', monospace; font-size: 0.9rem; line-height: 1.5; } /* 언어별 아이콘 색상 */ .fa-java { color: #f89820; } .fa-react { color: #61dafb; } .fa-python { color: #3776ab; } .fa-terminal { color: #28a745; } /* 반응형 디자인 */ @media (max-width: 768px) { .api-docs { padding: 1rem; } .endpoint-header { flex-direction: column; align-items: flex-start; gap: 0.5rem; } .tab-buttons { flex-wrap: wrap; } .tab-btn { flex: 1; min-width: 80px; padding: 0.5rem 1rem; font-size: 0.9rem; } .code-example h4 { font-size: 1rem; padding: 0.75rem; } } /* 모달 */ .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); } .modal-content { background-color: white; margin: 5% auto; padding: 0; border-radius: 10px; width: 90%; max-width: 500px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); } .modal-header { padding: 1.5rem; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .modal-header h3 { margin: 0; color: #333; } .close { color: #aaa; font-size: 28px; font-weight: bold; cursor: pointer; } .close:hover { color: #000; } .modal form { padding: 1.5rem; } .modal-actions { display: flex; gap: 1rem; justify-content: flex-end; margin-top: 1.5rem; } .checkbox-group { display: flex; gap: 1rem; flex-wrap: wrap; } .checkbox-group label { display: flex; align-items: center; gap: 0.5rem; font-weight: normal; } /* 모니터링 */ .monitoring-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; } .monitor-card { background: white; padding: 1.5rem; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .monitor-card h3 { margin-bottom: 1rem; color: #333; } /* 토스트 알림 */ .toast { position: fixed; top: 20px; right: 20px; padding: 1rem 1.5rem; border-radius: 5px; color: white; font-weight: 500; z-index: 10000; transform: translateX(400px); transition: transform 0.3s ease; display: none; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); max-width: 300px; word-wrap: break-word; } .toast.show { transform: translateX(0); display: block; } .toast.success { background: #28a745; } .toast.error { background: #dc3545; } .toast.warning { background: #ffc107; color: #212529; } .toast.info { background: #17a2b8; } .toast:hover { opacity: 0.9; } /* 관리자 전용 요소 숨김 */ .admin-only { display: none; } body.admin .admin-only { display: block; } body.admin .nav-menu .admin-only { display: list-item; } body.admin .stats-grid .admin-only { display: flex; } /* 반응형 디자인 */ @media (max-width: 768px) { .dashboard { grid-template-areas: "header" "main"; grid-template-columns: 1fr; grid-template-rows: 60px 1fr; } .sidebar { display: none; } .header { padding: 0 1rem; } .main-content { padding: 1rem; } .stats-grid { grid-template-columns: 1fr; } .action-buttons { flex-direction: column; } .section-header { flex-direction: column; gap: 1rem; align-items: flex-start; } .modal-content { margin: 10% auto; width: 95%; } } /* API 키 관리 스타일 */ .api-key-container { display: flex; align-items: center; gap: 0.5rem; min-width: 300px; } .api-key-value { flex: 1; font-family: 'Courier New', monospace; font-size: 0.85rem; background: #f8f9fa; padding: 0.25rem 0.5rem; border-radius: 3px; border: 1px solid #dee2e6; word-break: break-all; transition: all 0.3s; } .api-key-value.api-key-hidden { filter: blur(4px); user-select: none; } .api-key-value:hover { background: #e9ecef; } .api-key-actions { display: flex; gap: 0.25rem; } .btn-xs { padding: 0.25rem 0.5rem; font-size: 0.75rem; line-height: 1; border-radius: 3px; min-width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; } .btn-xs i { font-size: 0.7rem; } .btn-danger { background-color: #dc3545; border-color: #dc3545; color: white; } .btn-danger:hover { background-color: #c82333; border-color: #bd2130; } /* 정보 박스 스타일 */ .info-box { background: #e7f3ff; border: 1px solid #b3d9ff; border-radius: 8px; padding: 1.5rem; margin-bottom: 2rem; border-left: 4px solid #007bff; } .info-box h4 { color: #0056b3; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; } .info-box p { margin-bottom: 1rem; color: #495057; } .info-box ul { margin: 0; padding-left: 1.5rem; color: #495057; } .info-box li { margin-bottom: 0.5rem; } .info-box code { background: #f8f9fa; padding: 0.2rem 0.4rem; border-radius: 3px; font-family: 'Courier New', monospace; color: #e83e8c; border: 1px solid #e9ecef; } /* 키 이름 클릭 가능 스타일 */ .key-name-clickable { cursor: pointer; color: #007bff; font-weight: 500; transition: all 0.3s ease; padding: 0.25rem 0.5rem; border-radius: 4px; display: inline-block; } .key-name-clickable:hover { background-color: #e7f3ff; color: #0056b3; text-decoration: none; } .key-name-clickable i { margin-left: 0.5rem; opacity: 0.7; transition: opacity 0.3s ease; } .key-name-clickable:hover i { opacity: 1; } /* API 키 테이블 개선 */ #apiKeysTable .api-key-container { max-width: 400px; } #apiKeysTable td { vertical-align: middle; } /* 복사 성공 애니메이션 */ @keyframes copySuccess { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } .btn-success.copy-success { animation: copySuccess 0.3s ease; } /* API 키 섹션 헤더 개선 */ .api-keys-section .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; } .api-keys-section .section-header .header-actions { display: flex; gap: 0.5rem; align-items: center; } /* 모든 키 복사 버튼 */ .copy-all-keys-btn { background: #17a2b8; color: white; border: none; padding: 0.5rem 1rem; border-radius: 5px; font-size: 0.9rem; cursor: pointer; transition: all 0.3s; } .copy-all-keys-btn:hover { background: #138496; } /* 새 API 키 표시 스타일 */ .api-key-display { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem; } .new-api-key { flex: 1; font-family: 'Courier New', monospace; font-size: 0.9rem; background: #f8f9fa; padding: 0.75rem; border-radius: 5px; border: 2px solid #007bff; word-break: break-all; color: #007bff; font-weight: bold; } .alert { display: flex; align-items: center; gap: 0.5rem; } .alert i { font-size: 1.1rem; } .alert-success { background: #d4edda !important; border: 1px solid #c3e6cb !important; color: #155724 !important; } .alert-warning { background: #fff3cd !important; border: 1px solid #ffeaa7 !important; color: #856404 !important; } /* 모달 개선 */ .modal-body { max-height: 70vh; overflow-y: auto; } .modal-actions { display: flex; gap: 0.5rem; justify-content: flex-end; padding: 1rem 1.5rem; border-top: 1px solid #eee; background: #f8f9fa; } /* 스크롤바 스타일 */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } /* API 로그 스타일 */ .log-filters { background: #f8f9fa; padding: 1rem; border-radius: 8px; margin-bottom: 1.5rem; } .filter-row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: end; } .filter-group { display: flex; flex-direction: column; min-width: 150px; } .filter-group label { font-size: 0.875rem; font-weight: 500; margin-bottom: 0.25rem; color: #495057; } .filter-input { padding: 0.5rem; border: 1px solid #ced4da; border-radius: 4px; font-size: 0.875rem; } .filter-input:focus { outline: none; border-color: #007bff; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); } .log-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; } .log-stats .stat-card { background: #fff; padding: 1rem; border-radius: 8px; border: 1px solid #e9ecef; text-align: center; } .log-stats .stat-info h3 { font-size: 1.5rem; font-weight: bold; color: #007bff; margin: 0; } .log-stats .stat-info p { margin: 0.25rem 0 0 0; color: #6c757d; font-size: 0.875rem; } .method-badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: bold; text-transform: uppercase; } .method-get { background: #d4edda; color: #155724; } .method-post { background: #d1ecf1; color: #0c5460; } .method-put { background: #fff3cd; color: #856404; } .method-delete { background: #f8d7da; color: #721c24; } .status-badge { padding: 0.25rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: bold; } .status-success { background: #d4edda; color: #155724; } .status-error { background: #f8d7da; color: #721c24; } .status-warning { background: #fff3cd; color: #856404; } .pagination { display: flex; justify-content: center; gap: 0.5rem; margin-top: 1.5rem; } .pagination button { padding: 0.5rem 0.75rem; border: 1px solid #dee2e6; background: #fff; color: #007bff; border-radius: 4px; cursor: pointer; font-size: 0.875rem; } .pagination button:hover:not(:disabled) { background: #e9ecef; } .pagination button:disabled { color: #6c757d; cursor: not-allowed; background: #f8f9fa; } .pagination button.active { background: #007bff; color: #fff; border-color: #007bff; } .response-time { font-family: 'Courier New', monospace; font-size: 0.875rem; } .endpoint-path { font-family: 'Courier New', monospace; font-size: 0.875rem; color: #495057; } /* API 테스터 스타일 */ .api-tester-form { background: #f8f9fa; padding: 1.5rem; border-radius: 8px; margin-bottom: 1.5rem; } .form-row { display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; } .method-group { flex: 0 0 150px; } .url-group { flex: 1; } .url-input { display: flex; align-items: center; border: 1px solid #ced4da; border-radius: 4px; overflow: hidden; } .base-url { background: #e9ecef; padding: 0.5rem 0.75rem; font-family: 'Courier New', monospace; font-size: 0.875rem; color: #495057; white-space: nowrap; } .url-input input { border: none; flex: 1; padding: 0.5rem 0.75rem; font-family: 'Courier New', monospace; } .url-input input:focus { outline: none; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); } .form-actions { display: flex; gap: 0.5rem; margin-top: 1rem; } .api-response { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem; } .response-info { display: flex; gap: 1rem; margin-bottom: 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid #dee2e6; } .response-status { padding: 0.25rem 0.75rem; border-radius: 4px; font-weight: bold; font-size: 0.875rem; } .response-status.success { background: #d4edda; color: #155724; } .response-status.error { background: #f8d7da; color: #721c24; } .response-time { font-family: 'Courier New', monospace; color: #6c757d; font-size: 0.875rem; } .response-headers, .response-body { margin-bottom: 1rem; } .response-headers h4, .response-body h4 { margin-bottom: 0.5rem; color: #495057; font-size: 1rem; } .response-headers pre, .response-body pre { background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 1rem; font-size: 0.875rem; overflow-x: auto; max-height: 300px; overflow-y: auto; } .quick-tests { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 1.5rem; } .quick-tests h3 { margin-bottom: 1rem; color: #495057; } .test-examples { display: flex; gap: 0.5rem; flex-wrap: wrap; } .test-examples .btn { margin-bottom: 0.5rem; } .section-description { color: #6c757d; margin-bottom: 1.5rem; font-style: italic; } /* 권한 배지 스타일 */ .permissions-list { display: flex; flex-wrap: wrap; gap: 0.25rem; } .permission-badge { display: inline-block; padding: 0.25rem 0.5rem; font-size: 0.75rem; font-weight: 500; border-radius: 12px; background-color: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb; } .permission-badge:nth-child(1) { background-color: #e8f5e8; color: #2e7d32; border-color: #c8e6c9; } .permission-badge:nth-child(2) { background-color: #fff3e0; color: #ef6c00; border-color: #ffcc02; } .permission-badge:nth-child(3) { background-color: #ffebee; color: #c62828; border-color: #ffcdd2; } .permission-none { color: #6c757d; font-style: italic; font-size: 0.875rem; }