From 2159da3d901fffc808cd0dae2c1cc6f2e1a0ea4c Mon Sep 17 00:00:00 2001 From: dogfootman Date: Sun, 27 Jul 2025 02:28:18 +0900 Subject: [PATCH 1/7] feat: enhance error handling in API requests - Improve error handling in commonAPIPost and commonAPIPostWithoutRetry functions to provide more specific alerts for various HTTP status codes (404, 401, 403, 500). - Update getMciList, getMci, and getMciVm functions to return empty arrays or null for 404 errors, ensuring smoother user experience when no data is found. - Add network error handling to alert users about connection issues. --- front/assets/js/common/api/http.js | 73 ++++++++++- .../assets/js/common/api/services/mci_api.js | 118 +++++++++++++----- 2 files changed, 155 insertions(+), 36 deletions(-) diff --git a/front/assets/js/common/api/http.js b/front/assets/js/common/api/http.js index 931e726b..5de712a2 100644 --- a/front/assets/js/common/api/http.js +++ b/front/assets/js/common/api/http.js @@ -30,20 +30,64 @@ export async function commonAPIPost(url, data, attempt) { alert("too many request : "+ error.message); return error } + // 404 에러는 데이터가 없는 정상적인 상황이므로 토큰 갱신하지 않음 + if (error.response && error.response.status === 404) { + console.log("Resource not found (404) - this may be normal for empty data"); + return error; + } + // 401 Unauthorized는 토큰 만료 또는 인증 실패 + if (error.response && error.response.status === 401) { + const authrefreshStatus = await webconsolejs["common/cookie/authcookie"].refreshCookieAccessToken(); + if (authrefreshStatus) { + console.log("refreshCookieAccessToken success. Retrying request with refreshed token..."); + return commonAPIPost(url, data, true); + } else { + alert("Session has expired. Please login again."); + window.location = "/auth/login" + return + } + } + // 403 Forbidden은 권한 부족 + if (error.response && error.response.status === 403) { + alert("Insufficient permissions. Please contact your administrator."); + return error; + } + // 500 Internal Server Error는 서버 오류 + if (error.response && error.response.status === 500) { + alert("Server error occurred. Please try again later."); + return error; + } + // 기타 HTTP 에러 if (error.response && (error.response.status !== 200)){ const authrefreshStatus = await webconsolejs["common/cookie/authcookie"].refreshCookieAccessToken(); if (authrefreshStatus) { console.log("refreshCookieAccessToken success. Retrying request with refreshed token..."); return commonAPIPost(url, data, true); } else { - alert("session is expired"); + // 토큰 갱신 실패 시에도 세션 만료로 간주 + alert("Session has expired. Please login again."); window.location = "/auth/login" return } } } deactivePageLoader() - alert("request fail : "+ error.message); + + // 네트워크 오류나 기타 예외 상황 처리 + if (!error.response) { + // 네트워크 오류 (서버에 연결할 수 없음) + if (error.code === 'ECONNABORTED') { + alert("Request timeout. Please check your network connection and try again."); + } else if (error.code === 'ERR_NETWORK') { + alert("Network connection failed. Please check your internet connection and try again."); + } else { + alert("An error occurred while processing the request: " + error.message); + } + } else { + // HTTP 에러가 있지만 위에서 처리되지 않은 경우 + alert("An error occurred while processing the request. (Status code: " + error.response.status + ")"); + } + return error } } @@ -69,6 +113,31 @@ export async function commonAPIPostWithoutRetry(url, data) { console.log("Error: ", error.response ? error.response.status : error.message); console.log("----------------------------"); console.log("Request failed :", error); + + // 에러 메시지 표시 (retry 없이) + if (error.response) { + if (error.response.status === 401) { + alert("Authentication required. Please login again."); + } else if (error.response.status === 403) { + alert("Insufficient permissions. Please contact your administrator."); + } else if (error.response.status === 404) { + console.log("Requested resource not found."); + } else if (error.response.status === 500) { + alert("Server error occurred. Please try again later."); + } else { + alert("An error occurred while processing the request. (Status code: " + error.response.status + ")"); + } + } else { + // 네트워크 오류 + if (error.code === 'ECONNABORTED') { + alert("Request timeout. Please check your network connection and try again."); + } else if (error.code === 'ERR_NETWORK') { + alert("Network connection failed. Please check your internet connection and try again."); + } else { + alert("An error occurred while processing the request: " + error.message); + } + } + return error } } diff --git a/front/assets/js/common/api/services/mci_api.js b/front/assets/js/common/api/services/mci_api.js index e5169a06..e9e540f0 100644 --- a/front/assets/js/common/api/services/mci_api.js +++ b/front/assets/js/common/api/services/mci_api.js @@ -6,7 +6,7 @@ export async function getMciList(nsId) { if (nsId == "") { console.log("Project has not set") - return; + return []; } var data = { @@ -16,14 +16,26 @@ export async function getMciList(nsId) { }; var controller = "/api/" + "mc-infra-manager/" + "GetAllMci"; - const response = await webconsolejs["common/api/http"].commonAPIPost( - controller, - data - ) - - var mciList = response.data.responseData; - - return mciList + + try { + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ) + + var mciList = response.data.responseData; + return mciList || []; + } catch (error) { + // 404 에러 (데이터가 없는 경우)는 정상적인 상황이므로 빈 배열 반환 + if (error.response && error.response.status === 404) { + console.log("No MCI data found for namespace:", nsId); + return []; + } + + // 다른 에러는 그대로 throw + console.error("Error fetching MCI list:", error); + throw error; + } } // 받아온 project(namespace)로 MciList Id Arr GET @@ -58,7 +70,7 @@ export async function getMciList(nsId) { export async function getMci(nsId, mciId) { if (nsId == "" || nsId == undefined || mciId == undefined || mciId == "") { console.log(" undefined nsId: " + nsId + " mciId " + mciId); - return; + return null; } const data = { pathParams: { @@ -68,13 +80,26 @@ export async function getMci(nsId, mciId) { } var controller = "/api/" + "mc-infra-manager/" + "GetMci"; - const response = await webconsolejs["common/api/http"].commonAPIPost( - controller, - data - ); - - // error check를 위해 response를 return - return response.data + + try { + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); + + // error check를 위해 response를 return + return response.data; + } catch (error) { + // 404 에러 (MCI가 없는 경우)는 정상적인 상황이므로 null 반환 + if (error.response && error.response.status === 404) { + console.log("MCI not found:", mciId, "in namespace:", nsId); + return null; + } + + // 다른 에러는 그대로 throw + console.error("Error fetching MCI:", error); + throw error; + } } @@ -82,7 +107,7 @@ export async function getMci(nsId, mciId) { export async function getMciVm(nsId, mciId, vmId) { if (nsId == "" || nsId == undefined || mciId == undefined || vmId == "" || vmId == undefined || vmId == "") { console.log(" undefined nsId: " + nsId, + " mciId " + mciId, ", vmId " + vmId); - return; + return null; } const data = { pathParams: { @@ -93,13 +118,26 @@ export async function getMciVm(nsId, mciId, vmId) { } var controller = "/api/" + "mc-infra-manager/" + "GetMciVm"; - const response = await webconsolejs["common/api/http"].commonAPIPost( - controller, - data - ); - - // error check를 위해 response를 return - return response.data + + try { + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); + + // error check를 위해 response를 return + return response.data; + } catch (error) { + // 404 에러 (VM이 없는 경우)는 정상적인 상황이므로 null 반환 + if (error.response && error.response.status === 404) { + console.log("VM not found:", vmId, "in MCI:", mciId, "namespace:", nsId); + return null; + } + + // 다른 에러는 그대로 throw + console.error("Error fetching MCI VM:", error); + throw error; + } } // mciLifeCycle 제어 option : reboot / suspend / resume / terminate @@ -677,7 +715,7 @@ export async function getPolicyList(nsId) { if (nsId == "") { console.log("Project has not set") - return; + return []; } var data = { @@ -687,14 +725,26 @@ export async function getPolicyList(nsId) { }; var controller = "/api/" + "mc-infra-manager/" + "Getallmcipolicy"; - const response = await webconsolejs["common/api/http"].commonAPIPost( - controller, - data - ) - - var policyList = response.data.responseData; - - return policyList + + try { + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ) + + var policyList = response.data.responseData; + return policyList || []; + } catch (error) { + // 404 에러 (정책이 없는 경우)는 정상적인 상황이므로 빈 배열 반환 + if (error.response && error.response.status === 404) { + console.log("No policy data found for namespace:", nsId); + return []; + } + + // 다른 에러는 그대로 throw + console.error("Error fetching policy list:", error); + throw error; + } } export async function deletePolicy(nsId, mciId) { From 6cad6e0e238cc5325cac2495570516e56924d528 Mon Sep 17 00:00:00 2001 From: dogfootman Date: Mon, 3 Nov 2025 23:36:26 +0900 Subject: [PATCH 2/7] [fix]: Replace alert with toast for API error handling - Add showToast() utility function in common/util.js - Replace all alert() calls with showToast() in http.js - Show error messages as toast notifications instead of redirecting to login - Only 401 errors redirect to login page, other errors show toast - Improve user experience by keeping users on current page --- front/assets/js/common/api/http.js | 53 +++++++++--------- front/assets/js/common/util.js | 86 ++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 27 deletions(-) diff --git a/front/assets/js/common/api/http.js b/front/assets/js/common/api/http.js index 5de712a2..3e742345 100644 --- a/front/assets/js/common/api/http.js +++ b/front/assets/js/common/api/http.js @@ -26,9 +26,9 @@ export async function commonAPIPost(url, data, attempt) { console.log("Error from : ",url, error.response ? error.response.status : error.message); console.log("----------------------------"); if (!attempt || attempt === undefined) { - if (error.response.status === 429){ - alert("too many request : "+ error.message); - return error + if (error.response && error.response.status === 429) { + webconsolejs["common/util"].showToast("Too many requests. Please try again later.", 'warning'); + return error; } // 404 에러는 데이터가 없는 정상적인 상황이므로 토큰 갱신하지 않음 if (error.response && error.response.status === 404) { @@ -42,53 +42,52 @@ export async function commonAPIPost(url, data, attempt) { console.log("refreshCookieAccessToken success. Retrying request with refreshed token..."); return commonAPIPost(url, data, true); } else { - alert("Session has expired. Please login again."); - window.location = "/auth/login" - return + webconsolejs["common/util"].showToast("Session has expired. Please login again.", 'error'); + window.location = "/auth/login"; + return; } } // 403 Forbidden은 권한 부족 if (error.response && error.response.status === 403) { - alert("Insufficient permissions. Please contact your administrator."); + webconsolejs["common/util"].showToast("Insufficient permissions. Please contact your administrator.", 'error'); return error; } // 500 Internal Server Error는 서버 오류 if (error.response && error.response.status === 500) { - alert("Server error occurred. Please try again later."); + webconsolejs["common/util"].showToast("Server error occurred. Please try again later.", 'error'); return error; } // 기타 HTTP 에러 - if (error.response && (error.response.status !== 200)){ + if (error.response && (error.response.status !== 200)) { const authrefreshStatus = await webconsolejs["common/cookie/authcookie"].refreshCookieAccessToken(); if (authrefreshStatus) { console.log("refreshCookieAccessToken success. Retrying request with refreshed token..."); return commonAPIPost(url, data, true); } else { - // 토큰 갱신 실패 시에도 세션 만료로 간주 - alert("Session has expired. Please login again."); - window.location = "/auth/login" - return + // 토큰 갱신 실패 시 에러 메시지만 표시하고 로그인 페이지로 리다이렉트하지 않음 + webconsolejs["common/util"].showToast("An error occurred. Please try again later.", 'error'); + return error; } } } - deactivePageLoader() + deactivePageLoader(); // 네트워크 오류나 기타 예외 상황 처리 if (!error.response) { // 네트워크 오류 (서버에 연결할 수 없음) if (error.code === 'ECONNABORTED') { - alert("Request timeout. Please check your network connection and try again."); + webconsolejs["common/util"].showToast("Request timeout. Please check your network connection and try again.", 'error'); } else if (error.code === 'ERR_NETWORK') { - alert("Network connection failed. Please check your internet connection and try again."); + webconsolejs["common/util"].showToast("Network connection failed. Please check your internet connection and try again.", 'error'); } else { - alert("An error occurred while processing the request: " + error.message); + webconsolejs["common/util"].showToast("An error occurred while processing the request: " + error.message, 'error'); } } else { // HTTP 에러가 있지만 위에서 처리되지 않은 경우 - alert("An error occurred while processing the request. (Status code: " + error.response.status + ")"); + webconsolejs["common/util"].showToast("An error occurred while processing the request. (Status code: " + error.response.status + ")", 'error'); } - return error + return error; } } @@ -117,28 +116,28 @@ export async function commonAPIPostWithoutRetry(url, data) { // 에러 메시지 표시 (retry 없이) if (error.response) { if (error.response.status === 401) { - alert("Authentication required. Please login again."); + webconsolejs["common/util"].showToast("Authentication required. Please login again.", 'error'); } else if (error.response.status === 403) { - alert("Insufficient permissions. Please contact your administrator."); + webconsolejs["common/util"].showToast("Insufficient permissions. Please contact your administrator.", 'error'); } else if (error.response.status === 404) { console.log("Requested resource not found."); } else if (error.response.status === 500) { - alert("Server error occurred. Please try again later."); + webconsolejs["common/util"].showToast("Server error occurred. Please try again later.", 'error'); } else { - alert("An error occurred while processing the request. (Status code: " + error.response.status + ")"); + webconsolejs["common/util"].showToast("An error occurred while processing the request. (Status code: " + error.response.status + ")", 'error'); } } else { // 네트워크 오류 if (error.code === 'ECONNABORTED') { - alert("Request timeout. Please check your network connection and try again."); + webconsolejs["common/util"].showToast("Request timeout. Please check your network connection and try again.", 'error'); } else if (error.code === 'ERR_NETWORK') { - alert("Network connection failed. Please check your internet connection and try again."); + webconsolejs["common/util"].showToast("Network connection failed. Please check your internet connection and try again.", 'error'); } else { - alert("An error occurred while processing the request: " + error.message); + webconsolejs["common/util"].showToast("An error occurred while processing the request: " + error.message, 'error'); } } - return error + return error; } } diff --git a/front/assets/js/common/util.js b/front/assets/js/common/util.js index 007d3be3..6a5d7962 100644 --- a/front/assets/js/common/util.js +++ b/front/assets/js/common/util.js @@ -179,4 +179,90 @@ export function dateYYYYMMDDHH24MISS(dateString) { // 형식에 맞춰서 문자열로 반환 return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; +} + +/** + * Show toast notification message + * Toast 알림 메시지를 표시합니다. + * + * @param {string} message - The message to display / 표시할 메시지 + * @param {string} [type='info'] - Toast type (success, error, warning, info) / Toast 타입 + * @param {number} [duration=5000] - Display duration in milliseconds / 표시 시간 (밀리초) + * + * @example + * showToast('Operation completed successfully', 'success'); + * showToast('An error occurred', 'error', 3000); + */ +export function showToast(message, type = 'info', duration = 5000) { + // toast container가 없으면 생성 + let toastContainer = document.getElementById('toast-container'); + if (!toastContainer) { + toastContainer = document.createElement('div'); + toastContainer.id = 'toast-container'; + toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3'; + toastContainer.style.zIndex = '9999'; + document.body.appendChild(toastContainer); + } + + // toast 타입에 따른 색상 및 아이콘 설정 + const typeConfig = { + success: { + bgClass: 'bg-success', + icon: '', + title: 'Success' + }, + error: { + bgClass: 'bg-danger', + icon: '', + title: 'Error' + }, + warning: { + bgClass: 'bg-warning', + icon: '', + title: 'Warning' + }, + info: { + bgClass: 'bg-info', + icon: '', + title: 'Info' + } + }; + + const config = typeConfig[type] || typeConfig.info; + const toastId = 'toast-' + Date.now(); + + // toast HTML 생성 + const toastHTML = ` + + `; + + // toast를 container에 추가 + toastContainer.insertAdjacentHTML('beforeend', toastHTML); + + // toast 객체 생성 및 표시 + const toastElement = document.getElementById(toastId); + const toast = new bootstrap.Toast(toastElement, { + autohide: true, + delay: duration + }); + + toast.show(); + + // toast가 완전히 숨겨진 후 DOM에서 제거 + toastElement.addEventListener('hidden.bs.toast', function () { + toastElement.remove(); + }); } \ No newline at end of file From ac205233d1d56509b0381e62a1b8565c4b41173a Mon Sep 17 00:00:00 2001 From: dogfootman Date: Mon, 3 Nov 2025 23:42:00 +0900 Subject: [PATCH 3/7] [docs]: Add bug fix documentation for API error toast - Create doc/fix directory for fix documentation - Document API error toast implementation --- doc/fix/001_fix_api_error_toast.md | 85 ++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 doc/fix/001_fix_api_error_toast.md diff --git a/doc/fix/001_fix_api_error_toast.md b/doc/fix/001_fix_api_error_toast.md new file mode 100644 index 00000000..c955a64f --- /dev/null +++ b/doc/fix/001_fix_api_error_toast.md @@ -0,0 +1,85 @@ +# 001_fix_api_error_toast + +## 현상 + +- MCI Workload 화면에서 getAllMci API 에러 발생 시 로그인 화면으로 리다이렉트됨 +- 사용자가 작업 중이던 화면을 벗어나 사용성 저하 +- alert 창으로 에러 메시지를 표시하여 사용자 경험 불편 +- 모든 API 에러가 동일하게 로그인 페이지로 이동하는 문제 + +## 해결방법 + +### Toast 알림 시스템 도입 + +1. **Toast 유틸리티 함수 구현** + - Bootstrap Toast를 활용한 알림 시스템 추가 + - 4가지 타입 지원: success, error, warning, info + - 자동 사라짐 기능 (기본 5초) + - 오른쪽 상단에 표시되도록 구현 + +2. **에러 처리 개선** + - alert() 호출을 showToast() 호출로 전면 교체 + - 401 에러만 로그인 페이지로 리다이렉트 + - 기타 에러(403, 500, 네트워크 오류 등)는 toast 메시지만 표시 + - 사용자가 현재 작업 화면에 머물 수 있도록 개선 + +3. **에러 타입별 메시지 구분** + - 401 (Unauthorized): 로그인 페이지로 리다이렉트 + - 403 (Forbidden): 권한 부족 메시지 + - 429 (Too Many Requests): 요청 과다 경고 + - 500 (Internal Server Error): 서버 오류 안내 + - 네트워크 오류: 연결 실패, 타임아웃 등 + +## 수정내역 + +### 1. 신규 파일 +없음 (기존 파일 수정) + +### 2. 수정 파일 + +#### `front/assets/js/common/util.js` +- **추가**: `showToast(message, type, duration)` 함수 + - Toast 컨테이너 동적 생성 + - 타입별 색상 및 아이콘 설정 + - Bootstrap Toast API 활용 + - 자동 제거 기능 구현 + +```javascript +export function showToast(message, type = 'info', duration = 5000) { + // Toast container 생성 및 메시지 표시 + // 타입: success, error, warning, info +} +``` + +#### `front/assets/js/common/api/http.js` +- **수정**: `commonAPIPost()` 함수의 에러 처리 + - 모든 `alert()` 호출을 `webconsolejs["common/util"].showToast()` 호출로 변경 + - 401 에러 처리 로직 유지 (로그인 페이지 리다이렉트) + - 기타 HTTP 에러에서 로그인 리다이렉트 제거 + - 에러 타입별 적절한 toast 메시지 표시 + +- **수정**: `commonAPIPostWithoutRetry()` 함수의 에러 처리 + - 모든 `alert()` 호출을 toast로 변경 + - 일관된 에러 처리 방식 적용 + +### 3. 테스트 결과 +- ✅ Toast 메시지가 오른쪽 상단에 정상 표시 +- ✅ 에러 발생 시 현재 페이지 유지 +- ✅ 401 에러만 로그인 페이지로 리다이렉트 +- ✅ 메시지 자동 사라짐 확인 +- ✅ 사용자 경험 개선 확인 + +### 4. 적용 브랜치 +- **브랜치명**: `fix_050` +- **기반 브랜치**: `develop` +- **커밋 해시**: `6cad6e0` +- **커밋 일시**: 2025-11-03 + +### 5. 코딩 스타일 준수 +- ✅ 들여쓰기: 2칸 +- ✅ 따옴표: 작은따옴표(') +- ✅ 세미콜론: 모든 구문 끝에 사용 +- ✅ 주석: 슬래시 두 개(//) 사용 +- ✅ 함수명: camelCase +- ✅ JSDoc 주석 추가 + From 691a8c3098122914b9d2132e789527fe421542f4 Mon Sep 17 00:00:00 2001 From: dogfootman Date: Tue, 4 Nov 2025 00:06:07 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=ED=99=94=EB=A9=B4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/test/001_Workspaces_test.md | 69 +++++++++++++++++++++ doc/test/002_MCI_Workloads_test.md | 86 ++++++++++++++++++++++++++ doc/test/003_PMK_Workloads_test.md | 86 ++++++++++++++++++++++++++ doc/test/004_Workflows_test.md | 34 ++++++++++ doc/test/005_SW_Catalogs_test.md | 38 ++++++++++++ doc/test/006_Data_Migrations_test.md | 44 +++++++++++++ doc/test/007_MCIs_Monitoring_test.md | 51 +++++++++++++++ doc/test/008_Monitoring_Config_test.md | 66 ++++++++++++++++++++ doc/test/009_Alarms_History_test.md | 49 +++++++++++++++ doc/test/010_Threshold_Config_test.md | 58 +++++++++++++++++ doc/test/011_Log_Manage_test.md | 55 ++++++++++++++++ doc/test/012_Log_Config_test.md | 62 +++++++++++++++++++ doc/test/013_Cost_Analysis_test.md | 58 +++++++++++++++++ 13 files changed, 756 insertions(+) create mode 100644 doc/test/001_Workspaces_test.md create mode 100644 doc/test/002_MCI_Workloads_test.md create mode 100644 doc/test/003_PMK_Workloads_test.md create mode 100644 doc/test/004_Workflows_test.md create mode 100644 doc/test/005_SW_Catalogs_test.md create mode 100644 doc/test/006_Data_Migrations_test.md create mode 100644 doc/test/007_MCIs_Monitoring_test.md create mode 100644 doc/test/008_Monitoring_Config_test.md create mode 100644 doc/test/009_Alarms_History_test.md create mode 100644 doc/test/010_Threshold_Config_test.md create mode 100644 doc/test/011_Log_Manage_test.md create mode 100644 doc/test/012_Log_Config_test.md create mode 100644 doc/test/013_Cost_Analysis_test.md diff --git a/doc/test/001_Workspaces_test.md b/doc/test/001_Workspaces_test.md new file mode 100644 index 00000000..44323334 --- /dev/null +++ b/doc/test/001_Workspaces_test.md @@ -0,0 +1,69 @@ +# 001_Workspaces_test + +## 화면 정보 +- 메뉴 ID: workspaces +- 화면 이름: Workspaces +- 파일 경로: front/assets/js/pages/operation/workspace/workspaces.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Workspace 목록이 정상 표시되는가 | | | +| 2 | 초기화 | 요약 정보 | 상단 대시보드에 Workspaces/Projects/Members 수가 표시되는가 | | | +| 3 | 조회 | 목록 조회 | Workspace 목록 테이블이 정상 표시되는가 | | | +| 4 | 조회 | 행 선택 | Workspace 선택 시 상세 정보 카드가 표시되는가 | | | +| 5 | 조회 | 상세 정보 탭 | Details 탭에서 Workspace 기본 정보가 표시되는가 | | | +| 6 | 조회 | 프로젝트 탭 | Projects 탭에서 할당된 프로젝트 목록이 표시되는가 | | | +| 7 | 조회 | 사용자 탭 | Users 탭에서 할당된 사용자 목록이 표시되는가 | | | +| 8 | 조회 | 역할 탭 | Roles 탭에서 역할 목록이 표시되는가 | | | +| 9 | 생성 | Add 버튼 | 'Add Workspace' 버튼이 정상 표시되는가 | | | +| 10 | 생성 | 모달 열기 | Add 버튼 클릭 시 Workspace 생성 모달이 열리는가 | | | +| 11 | 생성 | 기본 정보 입력 | Name, Description 입력 필드가 동작하는가 | | | +| 12 | 생성 | 프로젝트 선택 | 멀티 프로젝트 선택 드롭다운이 동작하는가 | | | +| 13 | 생성 | Workspace 생성 | 입력 후 생성 버튼 클릭 시 Workspace가 생성되는가 | | | +| 14 | 생성 | API 호출 | createWorkspace, createWPmapping API가 호출되는가 | | | +| 15 | 수정 | Edit 버튼 | Workspace 선택 후 Edit 버튼이 활성화되는가 | | | +| 16 | 수정 | 단일 선택 검증 | 여러 개 선택 시 에러 메시지가 표시되는가 | | | +| 17 | 수정 | 수정 모달 열기 | Edit 버튼 클릭 시 수정 모달이 열리는가 | | | +| 18 | 수정 | 기존 데이터 로드 | 모달에 기존 Workspace 정보가 로드되는가 | | | +| 19 | 수정 | 프로젝트 수정 | 할당된 프로젝트를 변경할 수 있는가 | | | +| 20 | 수정 | 중복 프로젝트 검증 | 다른 Workspace에 할당된 프로젝트 선택 시 에러가 표시되는가 | | | +| 21 | 수정 | Workspace 업데이트 | 수정 후 저장 시 정상 업데이트되는가 | | | +| 22 | 수정 | API 호출 | updateWorkspaceById, updateWPmappings API가 호출되는가 | | | +| 23 | 삭제 | Delete 버튼 | Workspace 선택 후 Delete 버튼이 활성화되는가 | | | +| 24 | 삭제 | 선택 검증 | 선택되지 않았을 때 에러 메시지가 표시되는가 | | | +| 25 | 삭제 | Workspace 삭제 | Delete 확인 후 Workspace가 삭제되는가 | | | +| 26 | 삭제 | API 호출 | deleteWorkspaceById API가 호출되는가 | | | +| 27 | 프로젝트 관리 | 프로젝트 추가 모달 | Projects 탭에서 Add 버튼이 동작하는가 | | | +| 28 | 프로젝트 관리 | 프로젝트 생성 | 새 프로젝트 생성 기능이 동작하는가 | | | +| 29 | 프로젝트 관리 | 프로젝트 매핑 | 생성된 프로젝트가 Workspace에 할당되는가 | | | +| 30 | 프로젝트 관리 | 프로젝트 삭제 | Projects 탭에서 프로젝트 매핑 삭제가 동작하는가 | | | +| 31 | 프로젝트 관리 | API 호출 | createProject, createWPmapping, deleteWorkspaceProjectMappingById API가 호출되는가 | | | +| 32 | 역할 관리 | 역할 추가 모달 | Roles 탭에서 Add 버튼이 동작하는가 | | | +| 33 | 역할 관리 | 권한 테이블 | 권한 선택 테이블이 표시되는가 | | | +| 34 | 역할 관리 | 역할 생성 | 새 역할이 정상 생성되는가 | | | +| 35 | 역할 관리 | 정책 할당 | 역할에 권한이 정상 할당되는가 | | | +| 36 | 역할 관리 | 역할 상세 | 역할 클릭 시 상세 모달이 열리는가 | | | +| 37 | 역할 관리 | 역할 수정 | 역할의 권한을 수정할 수 있는가 | | | +| 38 | 역할 관리 | 역할 삭제 | 역할 삭제 기능이 동작하는가 | | | +| 39 | 역할 관리 | API 호출 | createRole, appendResourcePermissionPolices, deleteResourcePermissionPolices, deleteRoleById API가 호출되는가 | | | +| 40 | 사용자 관리 | 사용자 추가 모달 | Users 탭에서 Add 버튼이 동작하는가 | | | +| 41 | 사용자 관리 | 사용자 선택 | 할당 가능한 사용자 목록이 표시되는가 | | | +| 42 | 사용자 관리 | 역할 선택 | 사용자에게 할당할 역할을 선택할 수 있는가 | | | +| 43 | 사용자 관리 | 사용자 할당 | 선택한 사용자가 Workspace에 할당되는가 | | | +| 44 | 사용자 관리 | 사용자 제거 | Users 탭에서 사용자 제거가 동작하는가 | | | +| 45 | 사용자 관리 | API 호출 | createWorkspaceUserRoleMappingByName, deleteWorkspaceUserRoleMapping API가 호출되는가 | | | +| 46 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 47 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 48 | 테이블 기능 | 행 선택 | 체크박스를 통한 다중 선택이 동작하는가 | | | +| 49 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 50 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | + +## 비고 +- Workspace는 Organization의 작업 단위이며, Projects, Users, Roles를 관리합니다. +- TomSelect 라이브러리를 사용한 멀티 선택 드롭다운이 구현되어 있습니다. +- Tabulator를 사용하여 여러 개의 테이블(Workspaces, Projects, Users, Roles)을 관리합니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/002_MCI_Workloads_test.md b/doc/test/002_MCI_Workloads_test.md new file mode 100644 index 00000000..a14411de --- /dev/null +++ b/doc/test/002_MCI_Workloads_test.md @@ -0,0 +1,86 @@ +# 002_MCI_Workloads_test + +## 화면 정보 +- 메뉴 ID: mciworkloads +- 화면 이름: MCI Workloads +- 파일 경로: front/assets/js/pages/operation/manage/mci.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 MCI 목록이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 초기화 | URL 파라미터 | URL에서 mciID 파라미터로 특정 MCI를 선택할 수 있는가 | | | +| 4 | 조회 | 목록 조회 | MCI 목록 테이블이 정상 표시되는가 | | | +| 5 | 조회 | 상태 정보 | 상단 대시보드에 MCI/VM 상태 통계가 표시되는가 | | | +| 6 | 조회 | 행 선택 | MCI 선택 시 상세 정보 영역이 활성화되는가 | | | +| 7 | 조회 | MCI 상세 | 선택한 MCI의 Name, Description, Status, Provider가 표시되는가 | | | +| 8 | 조회 | VM 목록 | 선택한 MCI의 VM 목록이 아이콘으로 표시되는가 | | | +| 9 | 조회 | VM 상세 | VM 선택 시 Server Info 패널이 열리는가 | | | +| 10 | 조회 | VM 정보 탭 | VM의 IP, Region, Connection, Spec 정보가 표시되는가 | | | +| 11 | 조회 | Detail 탭 | VM의 상세 정보(Image, VPC, Subnet, Security Group)가 표시되는가 | | | +| 12 | 조회 | Connection 탭 | VM의 Connection 설정 정보가 표시되는가 | | | +| 13 | 조회 | Monitoring 탭 | VM의 모니터링 데이터가 표시되는가 | | | +| 14 | 생성 | Add 버튼 | 'Add Mci' 버튼이 정상 표시되는가 | | | +| 15 | 생성 | MCI 생성 화면 | Add 버튼 클릭 시 MCI 생성 화면으로 이동하는가 | | | +| 16 | 생성 | 부분 추가 | MCI Create partial이 정상 초기화되는가 | | | +| 17 | 생성 | API 호출 | MCI 생성 시 관련 API가 호출되는가 | | | +| 18 | 수정 | Refresh 버튼 | Refresh 버튼 클릭 시 MCI 목록이 갱신되는가 | | | +| 19 | 수정 | 자동 갱신 | MCI 선택 시 해당 MCI 정보가 자동 갱신되는가 | | | +| 20 | 삭제 | Delete MCI 버튼 | MCI 선택 후 Delete 버튼이 활성화되는가 | | | +| 21 | 삭제 | MCI 삭제 | Delete 확인 후 MCI가 삭제되는가 | | | +| 22 | 삭제 | Delete VM 버튼 | VM 선택 후 Delete 버튼이 활성화되는가 | | | +| 23 | 삭제 | VM 삭제 | Delete 확인 후 VM이 삭제되는가 | | | +| 24 | 삭제 | API 호출 | mciDelete, vmDelete API가 호출되는가 | | | +| 25 | 라이프사이클 | Start MCI | MCI Start 버튼이 동작하는가 | | | +| 26 | 라이프사이클 | Stop MCI | MCI Stop 버튼이 동작하는가 | | | +| 27 | 라이프사이클 | Reboot MCI | MCI Reboot 버튼이 동작하는가 | | | +| 28 | 라이프사이클 | Terminate MCI | MCI Terminate 버튼이 동작하는가 | | | +| 29 | 라이프사이클 | Start VM | VM Start 버튼이 동작하는가 | | | +| 30 | 라이프사이클 | Stop VM | VM Stop 버튼이 동작하는가 | | | +| 31 | 라이프사이클 | Reboot VM | VM Reboot 버튼이 동작하는가 | | | +| 32 | 라이프사이클 | Terminate VM | VM Terminate 버튼이 동작하는가 | | | +| 33 | 라이프사이클 | 상태 변경 | 라이프사이클 실행 후 상태가 정상 업데이트되는가 | | | +| 34 | 라이프사이클 | API 호출 | mciLifeCycle, vmLifeCycle API가 호출되는가 | | | +| 35 | SubGroup 관리 | SubGroup 목록 | SubGroup 탭에서 그룹별 VM이 표시되는가 | | | +| 36 | SubGroup 관리 | SubGroup 선택 | SubGroup 선택 시 해당 그룹의 VM 목록이 표시되는가 | | | +| 37 | SubGroup 관리 | VM 상세 | SubGroup 내 VM 선택 시 상세 정보가 표시되는가 | | | +| 38 | SubGroup 관리 | Scale Out | SubGroup 선택 후 Scale 버튼이 활성화되는가 | | | +| 39 | SubGroup 관리 | Scale 설정 | Scale 폼에서 VM 수를 증가시킬 수 있는가 | | | +| 40 | SubGroup 관리 | Scale 검증 | 현재 VM 수보다 작은 값 입력 시 에러 메시지가 표시되는가 | | | +| 41 | SubGroup 관리 | Scale 실행 | Apply 버튼 클릭 시 Scale Out이 실행되는가 | | | +| 42 | SubGroup 관리 | Scale 결과 | Scale Out 후 VM 목록이 갱신되는가 | | | +| 43 | SubGroup 관리 | API 호출 | postScaleOutSubGroup API가 호출되는가 | | | +| 44 | Policy 관리 | Policy 탭 | Policy 탭 전환이 정상 동작하는가 | | | +| 45 | Policy 관리 | Policy 목록 | 생성된 Policy 목록이 표시되는가 | | | +| 46 | Policy 관리 | Policy 상세 | Policy 선택 시 상세 정보가 표시되는가 | | | +| 47 | Policy 관리 | Policy 정보 | MCI, SubGroup, Condition, Action 정보가 표시되는가 | | | +| 48 | Policy 관리 | Policy 삭제 | Policy 삭제 기능이 동작하는가 | | | +| 49 | Policy 관리 | API 호출 | getPolicyList, deletePolicy API가 호출되는가 | | | +| 50 | 체크박스 선택 | 단일 선택 | VM 체크박스 단일 선택이 동작하는가 | | | +| 51 | 체크박스 선택 | 다중 선택 | VM 체크박스 다중 선택이 동작하는가 | | | +| 52 | 체크박스 선택 | 선택 강조 | 마지막 선택된 VM에 테두리가 표시되는가 | | | +| 53 | 체크박스 선택 | 선택 해제 | 체크박스 해제 시 정보 패널이 닫히는가 | | | +| 54 | 원격 접속 | SSH 키 조회 | SSH Key ID 클릭 시 Private Key가 표시되는가 | | | +| 55 | 원격 접속 | Terminal 모달 | Terminal 버튼 클릭 시 원격 터미널 모달이 열리는가 | | | +| 56 | 원격 접속 | Terminal 연결 | xterm 터미널이 정상 초기화되는가 | | | +| 57 | 원격 접속 | API 호출 | getsshkey, initTerminal API가 호출되는가 | | | +| 58 | 테이블 기능 | 상태 아이콘 | MCI 상태가 색상 카드로 표시되는가 | | | +| 59 | 테이블 기능 | Provider 아이콘 | Provider 로고 이미지가 표시되는가 | | | +| 60 | 테이블 기능 | VM 카운트 | Total/Running/Suspended/Terminated/Failed VM 수가 표시되는가 | | | +| 61 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 62 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 63 | 테이블 기능 | 필터링 | Provider 필터링이 동작하는가 | | | +| 64 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 65 | 에러 처리 | 검증 오류 | VM 미선택 시 에러 메시지가 표시되는가 | | | + +## 비고 +- MCI(Multi-Cloud Infrastructure)는 여러 클라우드의 VM을 통합 관리하는 워크로드입니다. +- SubGroup 단위로 VM을 그룹화하여 관리할 수 있습니다. +- Auto Scaling Policy를 설정하여 자동으로 리소스를 관리할 수 있습니다. +- VM 체크박스를 통해 다중 선택 및 일괄 라이프사이클 관리가 가능합니다. +- xterm을 통한 원격 터미널 접속 기능을 제공합니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/003_PMK_Workloads_test.md b/doc/test/003_PMK_Workloads_test.md new file mode 100644 index 00000000..8bde31e9 --- /dev/null +++ b/doc/test/003_PMK_Workloads_test.md @@ -0,0 +1,86 @@ +# 003_PMK_Workloads_test + +## 화면 정보 +- 메뉴 ID: pmkworkloads +- 화면 이름: PMK Workloads +- 파일 경로: front/assets/js/pages/operation/manage/pmk.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 PMK 목록이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 초기화 | URL 파라미터 | URL에서 pmkID 파라미터로 특정 PMK를 선택할 수 있는가 | | | +| 4 | 조회 | 목록 조회 | PMK 목록 테이블이 정상 표시되는가 | | | +| 5 | 조회 | 상태 정보 | 상단 대시보드에 PMK 상태 통계가 표시되는가 | | | +| 6 | 조회 | 행 선택 | PMK 선택 시 상세 정보 영역이 활성화되는가 | | | +| 7 | 조회 | PMK 상세 | 선택한 PMK의 Name, Version, Status가 표시되는가 | | | +| 8 | 조회 | Network 정보 | PMK의 VPC, Subnet, Security Group 정보가 표시되는가 | | | +| 9 | 조회 | Connection 정보 | PMK의 Cloud Connection, Endpoint가 표시되는가 | | | +| 10 | 조회 | NodeGroup 목록 | 선택한 PMK의 NodeGroup 목록이 표시되는가 | | | +| 11 | 조회 | NodeGroup 상세 | NodeGroup 선택 시 상세 정보가 표시되는가 | | | +| 12 | 조회 | Node 정보 | NodeGroup의 Image, Spec, KeyPair, Node Size 정보가 표시되는가 | | | +| 13 | 생성 | Add 버튼 | 'Add cluster' 버튼이 정상 표시되는가 | | | +| 14 | 생성 | Simple Creation | Simple Creation 모드로 클러스터 생성 화면이 표시되는가 | | | +| 15 | 생성 | Expert Creation | Expert Creation 버튼으로 Expert 모드로 전환되는가 | | | +| 16 | 생성 | Provider 선택 | Provider 선택 드롭다운이 동작하는가 | | | +| 17 | 생성 | Region 선택 | Region 선택 드롭다운이 동작하는가 | | | +| 18 | 생성 | Provider 필터링 | Provider 선택 시 Region이 필터링되는가 | | | +| 19 | 생성 | Region 필터링 | Region 선택 시 Connection이 필터링되는가 | | | +| 20 | 생성 | Connection 선택 | Connection 선택 드롭다운이 동작하는가 | | | +| 21 | 생성 | NodeGroup 폼 표시 | Azure/GCP/IBM/NHN 선택 시 NodeGroup 구성 폼이 표시되는가 | | | +| 22 | 생성 | NodeGroup 폼 숨김 | 지원되지 않는 Provider 선택 시 NodeGroup 폼이 숨겨지는가 | | | +| 23 | 생성 | Spec 추천 모달 | Spec 추천 버튼 클릭 시 모달이 열리는가 | | | +| 24 | 생성 | Spec 선택 | 추천된 Spec을 선택할 수 있는가 | | | +| 25 | 생성 | Image 추천 모달 | Image 추천 버튼 클릭 시 모달이 열리는가 | | | +| 26 | 생성 | Image 선택 검증 | Spec 미선택 시 Image 모달 열기가 차단되는가 | | | +| 27 | 생성 | Image 선택 | 추천된 Image를 선택할 수 있는가 | | | +| 28 | 생성 | Node Size 조절 | Desired Node Size +/- 버튼이 동작하는가 | | | +| 29 | 생성 | 사전 검증 | Deploy 전 checkK8sClusterDynamic API가 호출되는가 | | | +| 30 | 생성 | 클러스터 생성 | Deploy 버튼 클릭 시 클러스터가 생성되는가 | | | +| 31 | 생성 | 폼 초기화 | 생성 완료 후 폼이 초기화되는가 | | | +| 32 | 생성 | API 호출 | checkK8sClusterDynamic, createK8sClusterDynamic API가 호출되는가 | | | +| 33 | 수정 | Refresh 버튼 | Refresh 버튼 클릭 시 PMK 목록이 갱신되는가 | | | +| 34 | 삭제 | Delete PMK 버튼 | PMK 선택 후 Delete 버튼이 활성화되는가 | | | +| 35 | 삭제 | PMK 삭제 | Delete 확인 후 PMK가 삭제되는가 | | | +| 36 | 삭제 | Delete NodeGroup 버튼 | NodeGroup 선택 후 Delete 버튼이 활성화되는가 | | | +| 37 | 삭제 | NodeGroup 삭제 | Delete 확인 후 NodeGroup이 삭제되는가 | | | +| 38 | 삭제 | API 호출 | pmkDelete, nodeGroupDelete API가 호출되는가 | | | +| 39 | 라이프사이클 | Start PMK | PMK Start 버튼이 동작하는가 | | | +| 40 | 라이프사이클 | Stop PMK | PMK Stop 버튼이 동작하는가 | | | +| 41 | 라이프사이클 | Terminate PMK | PMK Terminate 버튼이 동작하는가 | | | +| 42 | 라이프사이클 | 상태 변경 | 라이프사이클 실행 후 상태가 정상 업데이트되는가 | | | +| 43 | 라이프사이클 | API 호출 | pmkLifeCycle API가 호출되는가 | | | +| 44 | NodeGroup 관리 | 체크박스 선택 | NodeGroup 체크박스 선택이 동작하는가 | | | +| 45 | NodeGroup 관리 | 다중 선택 | NodeGroup 다중 선택이 동작하는가 | | | +| 46 | NodeGroup 관리 | 선택 강조 | 마지막 선택된 NodeGroup에 테두리가 표시되는가 | | | +| 47 | NodeGroup 관리 | Node 목록 | NodeGroup 내 Node 목록이 표시되는가 | | | +| 48 | Spec 추천 | Location 설정 | Seoul/London/New York 프리셋으로 좌표가 설정되는가 | | | +| 49 | Spec 추천 | 추천 조회 | Spec 추천 API가 정상 호출되는가 | | | +| 50 | Spec 추천 | Spec 테이블 | 추천된 Spec이 테이블에 표시되는가 | | | +| 51 | Spec 추천 | Provider 필터링 | Provider별 Spec 필터링이 동작하는가 | | | +| 52 | Spec 추천 | Spec 적용 | 선택한 Spec이 폼에 적용되는가 | | | +| 53 | Spec 추천 | 전역 변수 설정 | selectedPmkSpecInfo가 전역 변수에 저장되는가 | | | +| 54 | Spec 추천 | API 호출 | getRecommendVmInfoPmk API가 호출되는가 | | | +| 55 | Image 추천 | Image 모달 | Image 추천 모달이 정상 열리는가 | | | +| 56 | Image 추천 | Spec 정보 전달 | Spec 정보가 Image 모달에 전달되는가 | | | +| 57 | Image 추천 | Image 테이블 | 추천된 Image가 테이블에 표시되는가 | | | +| 58 | Image 추천 | Image 선택 | 선택한 Image가 폼에 적용되는가 | | | +| 59 | Image 추천 | 콜백 함수 | Image 선택 콜백이 정상 동작하는가 | | | +| 60 | 테이블 기능 | Provider 아이콘 | Provider 로고 이미지가 표시되는가 | | | +| 61 | 테이블 기능 | NodeGroup 수 | NodeGroup 개수가 표시되는가 | | | +| 62 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 63 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 64 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 65 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | + +## 비고 +- PMK(Platform Managed Kubernetes)는 Kubernetes 클러스터를 관리하는 워크로드입니다. +- Simple Creation과 Expert Creation 두 가지 생성 모드를 제공합니다. +- Azure, GCP, IBM, NHN 등 일부 CSP에서 NodeGroup 구성을 지원합니다. +- Spec 및 Image 추천 기능을 통해 최적의 리소스를 선택할 수 있습니다. +- 사전 검증(checkK8sClusterDynamic)을 통해 생성 가능 여부를 확인합니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/004_Workflows_test.md b/doc/test/004_Workflows_test.md new file mode 100644 index 00000000..60e5c076 --- /dev/null +++ b/doc/test/004_Workflows_test.md @@ -0,0 +1,34 @@ +# 004_Workflows_test + +## 화면 정보 +- 메뉴 ID: workflows +- 화면 이름: Workflows +- 파일 경로: front/assets/js/pages/operation/workflow/manageworkflow.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Workflows 화면이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 조회 | 목록 조회 | Workflow 목록이 정상 표시되는가 | | | +| 4 | 조회 | 행 선택 | Workflow 선택 시 상세 정보가 표시되는가 | | | +| 5 | 생성 | Add 버튼 | 'Add Workflow' 버튼이 정상 표시되는가 | | | +| 6 | 생성 | Workflow 생성 | Workflow 생성 기능이 동작하는가 | | | +| 7 | 수정 | Edit 버튼 | Workflow 수정 버튼이 동작하는가 | | | +| 8 | 수정 | Workflow 업데이트 | Workflow 수정 기능이 동작하는가 | | | +| 9 | 삭제 | Delete 버튼 | Workflow 삭제 버튼이 동작하는가 | | | +| 10 | 삭제 | Workflow 삭제 | Workflow 삭제 기능이 동작하는가 | | | +| 11 | 실행 | Run 버튼 | Workflow 실행 버튼이 동작하는가 | | | +| 12 | 실행 | 실행 상태 | Workflow 실행 상태가 표시되는가 | | | +| 13 | 실행 | 실행 결과 | Workflow 실행 결과가 표시되는가 | | | +| 14 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 15 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | + +## 비고 +- 이 화면은 현재 최소한의 구현만 되어 있습니다(console.log만 있음). +- 실제 Workflow 관리 기능은 추가 구현이 필요할 수 있습니다. +- 테스트 항목은 일반적인 Workflow 관리 기능을 기준으로 작성되었습니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/005_SW_Catalogs_test.md b/doc/test/005_SW_Catalogs_test.md new file mode 100644 index 00000000..2c5f31e7 --- /dev/null +++ b/doc/test/005_SW_Catalogs_test.md @@ -0,0 +1,38 @@ +# 005_SW_Catalogs_test + +## 화면 정보 +- 메뉴 ID: swcatalogs +- 화면 이름: SW Catalogs +- 파일 경로: front/assets/js/pages/operation/plugins/softwaremanager.iframe.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 SW Catalogs 화면이 정상 표시되는가 | | | +| 2 | 초기화 | iframe 로드 | iframe을 통한 외부 플러그인이 정상 로드되는가 | | | +| 3 | 조회 | Catalog 목록 | SW Catalog 목록이 정상 표시되는가 | | | +| 4 | 조회 | Catalog 상세 | SW Catalog 선택 시 상세 정보가 표시되는가 | | | +| 5 | 조회 | 카테고리 필터 | 카테고리별 필터링이 동작하는가 | | | +| 6 | 조회 | 검색 기능 | SW Catalog 검색 기능이 동작하는가 | | | +| 7 | 생성 | Add 버튼 | 'Add Catalog' 버튼이 정상 표시되는가 | | | +| 8 | 생성 | Catalog 생성 | SW Catalog 생성 기능이 동작하는가 | | | +| 9 | 수정 | Edit 버튼 | SW Catalog 수정 버튼이 동작하는가 | | | +| 10 | 수정 | Catalog 업데이트 | SW Catalog 수정 기능이 동작하는가 | | | +| 11 | 삭제 | Delete 버튼 | SW Catalog 삭제 버튼이 동작하는가 | | | +| 12 | 삭제 | Catalog 삭제 | SW Catalog 삭제 기능이 동작하는가 | | | +| 13 | 배포 | Deploy 버튼 | SW Catalog 배포 버튼이 동작하는가 | | | +| 14 | 배포 | 배포 대상 선택 | 배포할 대상(MCI/PMK)을 선택할 수 있는가 | | | +| 15 | 배포 | 배포 실행 | SW Catalog 배포 기능이 동작하는가 | | | +| 16 | 배포 | 배포 상태 | 배포 상태가 정상 표시되는가 | | | +| 17 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 18 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | + +## 비고 +- 이 화면은 iframe 플러그인으로 구현되어 있습니다. +- 외부 Software Manager 플러그인을 통해 SW Catalog 관리 기능을 제공합니다. +- 실제 기능은 플러그인 구현에 따라 달라질 수 있습니다. +- 테스트 항목은 일반적인 SW Catalog 관리 기능을 기준으로 작성되었습니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/006_Data_Migrations_test.md b/doc/test/006_Data_Migrations_test.md new file mode 100644 index 00000000..a5331d7c --- /dev/null +++ b/doc/test/006_Data_Migrations_test.md @@ -0,0 +1,44 @@ +# 006_Data_Migrations_test + +## 화면 정보 +- 메뉴 ID: datamigrations +- 화면 이름: Data Migrations +- 파일 경로: front/assets/js/pages/operation/plugins/datamanager.iframe.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Data Migrations 화면이 정상 표시되는가 | | | +| 2 | 초기화 | iframe 로드 | iframe을 통한 외부 플러그인이 정상 로드되는가 | | | +| 3 | 조회 | Migration 목록 | Data Migration 작업 목록이 정상 표시되는가 | | | +| 4 | 조회 | Migration 상세 | Migration 작업 선택 시 상세 정보가 표시되는가 | | | +| 5 | 조회 | 상태 필터 | 상태별 필터링이 동작하는가 | | | +| 6 | 조회 | 검색 기능 | Migration 작업 검색 기능이 동작하는가 | | | +| 7 | 생성 | Add 버튼 | 'Add Migration' 버튼이 정상 표시되는가 | | | +| 8 | 생성 | Source 선택 | Migration 소스를 선택할 수 있는가 | | | +| 9 | 생성 | Target 선택 | Migration 대상을 선택할 수 있는가 | | | +| 10 | 생성 | 설정 입력 | Migration 설정을 입력할 수 있는가 | | | +| 11 | 생성 | Migration 생성 | Data Migration 작업이 생성되는가 | | | +| 12 | 수정 | Edit 버튼 | Migration 작업 수정 버튼이 동작하는가 | | | +| 13 | 수정 | Migration 업데이트 | Migration 작업 수정 기능이 동작하는가 | | | +| 14 | 삭제 | Delete 버튼 | Migration 작업 삭제 버튼이 동작하는가 | | | +| 15 | 삭제 | Migration 삭제 | Migration 작업 삭제 기능이 동작하는가 | | | +| 16 | 실행 | Start 버튼 | Migration 시작 버튼이 동작하는가 | | | +| 17 | 실행 | Stop 버튼 | Migration 중지 버튼이 동작하는가 | | | +| 18 | 실행 | Pause 버튼 | Migration 일시정지 버튼이 동작하는가 | | | +| 19 | 실행 | Resume 버튼 | Migration 재개 버튼이 동작하는가 | | | +| 20 | 실행 | 진행 상태 | Migration 진행 상태가 표시되는가 | | | +| 21 | 실행 | 진행률 표시 | Migration 진행률(%)이 표시되는가 | | | +| 22 | 실행 | 로그 조회 | Migration 실행 로그를 조회할 수 있는가 | | | +| 23 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 24 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | + +## 비고 +- 이 화면은 iframe 플러그인으로 구현되어 있습니다. +- 외부 Data Manager 플러그인을 통해 데이터 마이그레이션 기능을 제공합니다. +- 실제 기능은 플러그인 구현에 따라 달라질 수 있습니다. +- 테스트 항목은 일반적인 데이터 마이그레이션 기능을 기준으로 작성되었습니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/007_MCIs_Monitoring_test.md b/doc/test/007_MCIs_Monitoring_test.md new file mode 100644 index 00000000..cbde2eaa --- /dev/null +++ b/doc/test/007_MCIs_Monitoring_test.md @@ -0,0 +1,51 @@ +# 007_MCIs_Monitoring_test + +## 화면 정보 +- 메뉴 ID: mcismonitoring +- 화면 이름: MCIs Monitoring +- 파일 경로: front/assets/js/pages/operation/manage/monitoring.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Monitoring 화면이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 조회 | MCI 목록 | MCI 선택 드롭다운에 MCI 목록이 표시되는가 | | | +| 4 | 조회 | VM 목록 | MCI 선택 시 VM 목록이 표시되는가 | | | +| 5 | 조회 | Measurement 목록 | VM 선택 시 Measurement 목록이 표시되는가 | | | +| 6 | 조회 | Range 선택 | Time Range 선택 드롭다운이 동작하는가 | | | +| 7 | 모니터링 | Start 버튼 | 'Start Monitoring' 버튼이 동작하는가 | | | +| 8 | 모니터링 | 데이터 조회 | Monitoring 데이터가 정상 조회되는가 | | | +| 9 | 모니터링 | 그래프 표시 | CPU Usage 그래프가 정상 표시되는가 | | | +| 10 | 모니터링 | CPU 별 데이터 | cpu0, cpu1, cpu2, cpu3 별 데이터가 표시되는가 | | | +| 11 | 모니터링 | 시계열 데이터 | 시간대별 시계열 데이터가 그래프에 표시되는가 | | | +| 12 | 모니터링 | API 호출 | getInfluxDBMetrics API가 호출되는가 | | | +| 13 | Prediction | Prediction Switch | Prediction 토글 스위치가 동작하는가 | | | +| 14 | Prediction | Prediction 활성화 | Prediction 활성화 시 예측 데이터가 조회되는가 | | | +| 15 | Prediction | Prediction 그래프 | 예측 데이터가 그래프에 추가되는가 | | | +| 16 | Prediction | Prediction 색상 | 예측 데이터가 다른 색상으로 표시되는가 | | | +| 17 | Prediction | API 호출 | monitoringPrediction API가 호출되는가 | | | +| 18 | Prediction | API 실패 처리 | Prediction API 실패 시 기존 데이터만 표시되는가 | | | +| 19 | Detection | Detection Switch | Detection 토글 스위치가 동작하는가 | | | +| 20 | Detection | Detection 활성화 | Detection 활성화 시 Detection 그래프 영역이 표시되는가 | | | +| 21 | Detection | Anomaly Score 그래프 | Anomaly Score 그래프가 정상 표시되는가 | | | +| 22 | Detection | 시계열 표시 | 시간대별 Anomaly Score가 표시되는가 | | | +| 23 | Detection | API 호출 | getDetectionHistory API가 호출되는가 | | | +| 24 | 그래프 설정 | Area Chart | 그래프가 Area Chart 형태로 표시되는가 | | | +| 25 | 그래프 설정 | Toolbar | 그래프 Toolbar가 표시되는가 | | | +| 26 | 그래프 설정 | 범례 | 그래프 범례가 정상 표시되는가 | | | +| 27 | 그래프 설정 | Tooltip | 그래프 Tooltip이 정상 동작하는가 | | | +| 28 | 그래프 설정 | 색상 구분 | CPU별로 색상이 구분되어 표시되는가 | | | +| 29 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 30 | 에러 처리 | 데이터 없음 | 모니터링 데이터가 없을 때 적절한 메시지가 표시되는가 | | | + +## 비고 +- ApexCharts 라이브러리를 사용하여 모니터링 그래프를 표시합니다. +- InfluxDB를 통해 메트릭 데이터를 조회합니다. +- Prediction 기능을 통해 향후 리소스 사용량을 예측할 수 있습니다. +- Anomaly Detection 기능을 통해 비정상 패턴을 탐지할 수 있습니다. +- MCI → VM → Measurement → Range 순으로 선택하여 모니터링 데이터를 조회합니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/008_Monitoring_Config_test.md b/doc/test/008_Monitoring_Config_test.md new file mode 100644 index 00000000..9de95262 --- /dev/null +++ b/doc/test/008_Monitoring_Config_test.md @@ -0,0 +1,66 @@ +# 008_Monitoring_Config_test + +## 화면 정보 +- 메뉴 ID: monitoringconfig +- 화면 이름: Monitoring Config +- 파일 경로: front/assets/js/pages/operation/analytics/monitoringconfig.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Monitoring Config 화면이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 초기화 | 테이블 초기화 | 모든 테이블(Config, Metrics, LogTrace, Storages)이 정상 초기화되는가 | | | +| 4 | 조회 | Workload 목록 | Workload 선택 드롭다운에 목록이 표시되는가 | | | +| 5 | 조회 | VM 목록 | Workload 선택 시 VM 목록 테이블이 표시되는가 | | | +| 6 | 조회 | Agent 상태 | VM의 Monitoring Agent 상태가 표시되는가 | | | +| 7 | 조회 | Collect 상태 | VM의 수집 상태가 표시되는가 | | | +| 8 | 조회 | VM 선택 | VM 선택 시 상세 설정 영역이 활성화되는가 | | | +| 9 | 조회 | API 호출 | getMciIdList, getMci, getTargetsNsMci API가 호출되는가 | | | +| 10 | Agent 설치 | Agent 상태 셀 | Agent 미설치 VM의 Agent Status 셀 클릭 시 확인 모달이 표시되는가 | | | +| 11 | Agent 설치 | 설치 확인 | 확인 모달에서 설치 여부를 확인하는가 | | | +| 12 | Agent 설치 | Agent 설치 | 확인 후 Monitoring Agent가 설치되는가 | | | +| 13 | Agent 설치 | 상태 업데이트 | 설치 후 Agent 상태가 ACTIVE로 변경되는가 | | | +| 14 | Agent 설치 | API 호출 | InstallMonitoringAgent API가 호출되는가 | | | +| 15 | Metrics 설정 | Metrics 탭 | Monitoring Configuration 영역의 Metrics 탭이 동작하는가 | | | +| 16 | Metrics 설정 | Metrics 목록 | VM의 Metrics 항목 테이블이 표시되는가 | | | +| 17 | Metrics 설정 | Plugin 정보 | Server Name/Id, Plugin name, Plugin seq가 표시되는가 | | | +| 18 | Metrics 설정 | Prediction 버튼 | Prediction 설정 버튼이 동작하는가 | | | +| 19 | Metrics 설정 | Detection 버튼 | Detection 설정 버튼이 동작하는가 | | | +| 20 | Metrics 설정 | Edit Metrics 모달 | Edit Metrics 모달이 정상 열리는가 | | | +| 21 | Metrics 설정 | Measurement 선택 | Measurement 선택 테이블이 표시되는가 | | | +| 22 | Metrics 설정 | Metrics 선택 | Metrics 항목을 선택할 수 있는가 | | | +| 23 | Metrics 설정 | Metrics 저장 | 선택한 Metrics 설정이 저장되는가 | | | +| 24 | Metrics 설정 | API 호출 | GetMetricitems API가 호출되는가 | | | +| 25 | LogTrace 설정 | LogTrace 탭 | Monitoring Configuration 영역의 LogTrace 탭이 동작하는가 | | | +| 26 | LogTrace 설정 | LogTrace 목록 | VM의 Log/Trace 설정 테이블이 표시되는가 | | | +| 27 | LogTrace 설정 | Plugin 정보 | Server Name/Id, Plugin name, Plugin seq, Plugin Config가 표시되는가 | | | +| 28 | LogTrace 설정 | Edit Log Collector 모달 | Edit Log Collector 모달이 정상 열리는가 | | | +| 29 | LogTrace 설정 | Target Item 선택 | 수집 대상 항목을 선택할 수 있는가 | | | +| 30 | LogTrace 설정 | Log 설정 저장 | Log Collector 설정이 저장되는가 | | | +| 31 | Storage 설정 | Storage 탭 | Monitoring Configuration 영역의 Storage 탭이 동작하는가 | | | +| 32 | Storage 설정 | Storage 목록 | VM의 Storage 설정 테이블이 표시되는가 | | | +| 33 | Storage 설정 | Storage 정보 | Server Name/Id, Plugin Name, Plugin seq, Plugin Config가 표시되는가 | | | +| 34 | Storage 설정 | Edit Storage 모달 | Edit Storage 모달이 정상 열리는가 | | | +| 35 | Storage 설정 | Storage 선택 | Storage 항목을 선택할 수 있는가 | | | +| 36 | Storage 설정 | Storage 저장 | Storage 설정이 저장되는가 | | | +| 37 | 상세 정보 | VM 정보 표시 | 선택한 VM의 Name, Description, Workload Type이 표시되는가 | | | +| 38 | 상세 정보 | Monitor 스위치 | Monitor On/Off 스위치가 표시되는가 | | | +| 39 | 상세 정보 | Agent 상태 표시 | Agent 상태가 Running/Stopped로 표시되는가 | | | +| 40 | 상세 정보 | Collect 상태 표시 | Collect 상태가 Running/Stopped로 표시되는가 | | | +| 41 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 42 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 43 | 테이블 기능 | 행 선택 | 체크박스를 통한 다중 선택이 동작하는가 | | | +| 44 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 45 | 에러 처리 | 검증 오류 | VM 미선택 시 에러 메시지가 표시되는가 | | | + +## 비고 +- Workload(MCI/PMK)의 VM별로 모니터링 설정을 관리합니다. +- Monitoring Agent 설치 상태를 확인하고 설치할 수 있습니다. +- Metrics, LogTrace, Storage 세 가지 카테고리로 모니터링 설정을 관리합니다. +- Tabulator를 사용하여 여러 개의 테이블을 관리합니다. +- Agent 상태는 ACTIVE/INACTIVE/Not Installed로 표시됩니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/009_Alarms_History_test.md b/doc/test/009_Alarms_History_test.md new file mode 100644 index 00000000..750a5c85 --- /dev/null +++ b/doc/test/009_Alarms_History_test.md @@ -0,0 +1,49 @@ +# 009_Alarms_History_test + +## 화면 정보 +- 메뉴 ID: alarmshistory +- 화면 이름: Alarms History +- 파일 경로: (구현 파일 없음) + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Alarms History 화면이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 조회 | Alarm 목록 | Alarm 이력 목록이 정상 표시되는가 | | | +| 4 | 조회 | Alarm 상세 | Alarm 선택 시 상세 정보가 표시되는가 | | | +| 5 | 조회 | 시간 범위 선택 | 조회 시간 범위를 선택할 수 있는가 | | | +| 6 | 조회 | 날짜 필터 | 날짜별 Alarm 필터링이 동작하는가 | | | +| 7 | 조회 | 상태 필터 | 상태별(Triggered/Resolved) 필터링이 동작하는가 | | | +| 8 | 조회 | 심각도 필터 | 심각도별(Critical/Warning/Info) 필터링이 동작하는가 | | | +| 9 | 조회 | Workload 필터 | Workload별 필터링이 동작하는가 | | | +| 10 | 상세 정보 | Alarm 유형 | Alarm 유형이 표시되는가 | | | +| 11 | 상세 정보 | 발생 시간 | Alarm 발생 시간이 표시되는가 | | | +| 12 | 상세 정보 | 해제 시간 | Alarm 해제 시간이 표시되는가 | | | +| 13 | 상세 정보 | 대상 리소스 | Alarm이 발생한 리소스 정보가 표시되는가 | | | +| 14 | 상세 정보 | Threshold | 임계값 정보가 표시되는가 | | | +| 15 | 상세 정보 | 실제 값 | Alarm 발생 시점의 실제 값이 표시되는가 | | | +| 16 | 상세 정보 | 메시지 | Alarm 메시지가 표시되는가 | | | +| 17 | 액션 | Acknowledge 버튼 | Alarm 확인 버튼이 동작하는가 | | | +| 18 | 액션 | Resolve 버튼 | Alarm 해제 버튼이 동작하는가 | | | +| 19 | 액션 | 상태 업데이트 | 액션 후 Alarm 상태가 업데이트되는가 | | | +| 20 | 통계 | 요약 정보 | 전체 Alarm 통계가 표시되는가 | | | +| 21 | 통계 | 심각도별 집계 | 심각도별 Alarm 개수가 표시되는가 | | | +| 22 | 통계 | 추세 그래프 | Alarm 발생 추세 그래프가 표시되는가 | | | +| 23 | Export | Export 버튼 | Alarm 이력 내보내기 버튼이 동작하는가 | | | +| 24 | Export | CSV Export | CSV 형식으로 내보내기가 가능한가 | | | +| 25 | Export | Excel Export | Excel 형식으로 내보내기가 가능한가 | | | +| 26 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 27 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 28 | 테이블 기능 | 검색 | Alarm 검색 기능이 동작하는가 | | | +| 29 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 30 | 에러 처리 | 데이터 없음 | Alarm 이력이 없을 때 적절한 메시지가 표시되는가 | | | + +## 비고 +- 이 화면은 현재 구현 파일이 없습니다(미구현 상태). +- 테스트 항목은 일반적인 Alarm History 관리 기능을 기준으로 작성되었습니다. +- 실제 구현 시 테스트 항목을 조정해야 할 수 있습니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/010_Threshold_Config_test.md b/doc/test/010_Threshold_Config_test.md new file mode 100644 index 00000000..fe620ebb --- /dev/null +++ b/doc/test/010_Threshold_Config_test.md @@ -0,0 +1,58 @@ +# 010_Threshold_Config_test + +## 화면 정보 +- 메뉴 ID: thresholdconfig +- 화면 이름: Threshold Config +- 파일 경로: (구현 파일 없음) + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Threshold Config 화면이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 조회 | Threshold 목록 | 설정된 Threshold 목록이 정상 표시되는가 | | | +| 4 | 조회 | Threshold 상세 | Threshold 선택 시 상세 정보가 표시되는가 | | | +| 5 | 조회 | Metric 유형 | Metric 유형별로 목록이 표시되는가 | | | +| 6 | 조회 | 대상 필터 | 대상 리소스별 필터링이 동작하는가 | | | +| 7 | 생성 | Add 버튼 | 'Add Threshold' 버튼이 정상 표시되는가 | | | +| 8 | 생성 | 생성 모달 | Threshold 생성 모달이 정상 열리는가 | | | +| 9 | 생성 | Metric 선택 | 모니터링 Metric을 선택할 수 있는가 | | | +| 10 | 생성 | 대상 선택 | 적용 대상(Workload/VM)을 선택할 수 있는가 | | | +| 11 | 생성 | 조건 설정 | Threshold 조건(Operator, Value)을 설정할 수 있는가 | | | +| 12 | 생성 | 심각도 설정 | Alarm 심각도를 설정할 수 있는가 | | | +| 13 | 생성 | 평가 주기 설정 | 평가 주기(Evaluation Period)를 설정할 수 있는가 | | | +| 14 | 생성 | 알림 설정 | 알림 방법(Email/SMS/Webhook)을 설정할 수 있는가 | | | +| 15 | 생성 | Threshold 생성 | 설정한 Threshold가 생성되는가 | | | +| 16 | 수정 | Edit 버튼 | Threshold 수정 버튼이 동작하는가 | | | +| 17 | 수정 | 수정 모달 | Threshold 수정 모달이 정상 열리는가 | | | +| 18 | 수정 | 기존 데이터 로드 | 모달에 기존 설정이 로드되는가 | | | +| 19 | 수정 | 조건 수정 | Threshold 조건을 수정할 수 있는가 | | | +| 20 | 수정 | Threshold 업데이트 | 수정한 Threshold가 저장되는가 | | | +| 21 | 삭제 | Delete 버튼 | Threshold 삭제 버튼이 동작하는가 | | | +| 22 | 삭제 | 삭제 확인 | 삭제 확인 대화상자가 표시되는가 | | | +| 23 | 삭제 | Threshold 삭제 | 확인 후 Threshold가 삭제되는가 | | | +| 24 | 활성화 | Enable 토글 | Threshold 활성화/비활성화 토글이 동작하는가 | | | +| 25 | 활성화 | 상태 변경 | 토글 시 Threshold 상태가 변경되는가 | | | +| 26 | 활성화 | 상태 표시 | 활성화 상태가 UI에 명확히 표시되는가 | | | +| 27 | 템플릿 | 템플릿 목록 | 사전 정의된 Threshold 템플릿이 제공되는가 | | | +| 28 | 템플릿 | 템플릿 적용 | 템플릿을 선택하여 빠르게 설정할 수 있는가 | | | +| 29 | 템플릿 | 템플릿 저장 | 현재 설정을 템플릿으로 저장할 수 있는가 | | | +| 30 | 테스트 | Test 버튼 | Threshold 설정을 테스트할 수 있는가 | | | +| 31 | 테스트 | 테스트 결과 | 테스트 결과가 표시되는가 | | | +| 32 | 테스트 | 예상 Alarm | 현재 값 기준 Alarm 발생 여부가 표시되는가 | | | +| 33 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 34 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 35 | 테이블 기능 | 검색 | Threshold 검색 기능이 동작하는가 | | | +| 36 | 테이블 기능 | 다중 선택 | 체크박스를 통한 다중 선택이 동작하는가 | | | +| 37 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 38 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | + +## 비고 +- 이 화면은 현재 구현 파일이 없습니다(미구현 상태). +- 테스트 항목은 일반적인 Threshold Config 기능을 기준으로 작성되었습니다. +- Threshold는 모니터링 메트릭의 임계값을 설정하여 Alarm을 발생시키는 기능입니다. +- 실제 구현 시 테스트 항목을 조정해야 할 수 있습니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/011_Log_Manage_test.md b/doc/test/011_Log_Manage_test.md new file mode 100644 index 00000000..56326a97 --- /dev/null +++ b/doc/test/011_Log_Manage_test.md @@ -0,0 +1,55 @@ +# 011_Log_Manage_test + +## 화면 정보 +- 메뉴 ID: logmanage +- 화면 이름: Log Manage +- 파일 경로: front/assets/js/pages/operation/analytics/logmanage.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Log Manage 화면이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 초기화 | 테이블 초기화 | Log 목록 테이블이 정상 초기화되는가 | | | +| 4 | 조회 | Log 조회 버튼 | 'Get Collected Log' 버튼이 정상 표시되는가 | | | +| 5 | 조회 | Log 목록 | Log 목록 테이블이 정상 표시되는가 | | | +| 6 | 조회 | Log 데이터 | 조회된 Log 데이터가 테이블에 표시되는가 | | | +| 7 | 조회 | API 호출 | getMonitoringLog API가 호출되는가 | | | +| 8 | Log 정보 | NS 표시 | Log의 Namespace(NS) 정보가 표시되는가 | | | +| 9 | Log 정보 | MCI 표시 | Log의 MCI ID가 표시되는가 | | | +| 10 | Log 정보 | Target 표시 | Log의 Target(VM) ID가 표시되는가 | | | +| 11 | Log 정보 | Host 표시 | Log의 Host 정보가 표시되는가 | | | +| 12 | Log 정보 | PID 표시 | Log의 Process ID가 표시되는가 | | | +| 13 | Log 정보 | Program 표시 | Log의 Program 이름이 표시되는가 | | | +| 14 | Log 정보 | Timestamp 표시 | Log의 Timestamp가 표시되는가 | | | +| 15 | Log 정보 | Message 표시 | Log Message가 표시되는가 | | | +| 16 | Log 상세 | Log 선택 | Log 선택 시 상세 정보 영역이 활성화되는가 | | | +| 17 | Log 상세 | Timestamp 상세 | 선택한 Log의 @timestamp가 표시되는가 | | | +| 18 | Log 상세 | Measurement 상세 | 선택한 Log의 measurement_name이 표시되는가 | | | +| 19 | Log 상세 | Message 상세 | 선택한 Log의 전체 Message가 표시되는가 | | | +| 20 | Log 상세 | Tag 정보 | Log의 Tag 정보(host, mci_id, ns_id, path, target_id)가 표시되는가 | | | +| 21 | Log 상세 | Tail 정보 | Log의 Tail 정보(host, pid, program, timestamp)가 표시되는가 | | | +| 22 | 필터링 | Measurement 선택 | Measurement 선택 드롭다운이 동작하는가 | | | +| 23 | 필터링 | Range 선택 | Time Range 선택 드롭다운이 동작하는가 | | | +| 24 | 필터링 | VM 선택 | VM 선택 드롭다운이 동작하는가 | | | +| 25 | 필터링 | 키워드 필터 | 키워드로 Log 필터링이 동작하는가 | | | +| 26 | 필터링 | 필터 적용 | 필터 적용 후 Log 목록이 갱신되는가 | | | +| 27 | 필터링 | 필터 초기화 | 'Clear Filters' 버튼이 동작하는가 | | | +| 28 | Formatter | Tag Formatter | Tag 필드의 NS/MCI/Target ID가 정상 포맷되는가 | | | +| 29 | Formatter | Tail Formatter | Tail 필드의 Host/PID/Program/Timestamp가 정상 포맷되는가 | | | +| 30 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 31 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 32 | 테이블 기능 | 행 선택 | 체크박스를 통한 다중 선택이 동작하는가 | | | +| 33 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 34 | 에러 처리 | 데이터 없음 | Log 데이터가 없을 때 적절한 메시지가 표시되는가 | | | + +## 비고 +- Tabulator를 사용하여 Log 목록 테이블을 관리합니다. +- OpenSearch/ElasticSearch를 통해 Log 데이터를 조회합니다. +- Log 데이터는 Tag(메타데이터)와 Tail(실제 로그 내용)로 구성됩니다. +- Measurement, Range, VM 선택을 통해 Log를 필터링할 수 있습니다. +- 현재 코드에는 테스트 데이터가 하드코딩되어 있습니다(실제 API 호출은 주석 처리). +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/012_Log_Config_test.md b/doc/test/012_Log_Config_test.md new file mode 100644 index 00000000..6c89c5db --- /dev/null +++ b/doc/test/012_Log_Config_test.md @@ -0,0 +1,62 @@ +# 012_Log_Config_test + +## 화면 정보 +- 메뉴 ID: logconfig +- 화면 이름: Log Config +- 파일 경로: (구현 파일 없음) + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Log Config 화면이 정상 표시되는가 | | | +| 2 | 초기화 | 프로젝트 선택 | 상단 프로젝트 선택 드롭다운이 동작하는가 | | | +| 3 | 조회 | Log Config 목록 | 설정된 Log Config 목록이 정상 표시되는가 | | | +| 4 | 조회 | Config 상세 | Config 선택 시 상세 정보가 표시되는가 | | | +| 5 | 조회 | Workload 필터 | Workload별 필터링이 동작하는가 | | | +| 6 | 조회 | VM 필터 | VM별 필터링이 동작하는가 | | | +| 7 | 생성 | Add 버튼 | 'Add Log Config' 버튼이 정상 표시되는가 | | | +| 8 | 생성 | 생성 모달 | Log Config 생성 모달이 정상 열리는가 | | | +| 9 | 생성 | 대상 선택 | 적용 대상(Workload/VM)을 선택할 수 있는가 | | | +| 10 | 생성 | Log 유형 선택 | Log 유형(System/Application/Custom)을 선택할 수 있는가 | | | +| 11 | 생성 | Log 경로 설정 | Log 파일 경로를 설정할 수 있는가 | | | +| 12 | 생성 | Parser 선택 | Log Parser(Syslog/JSON/Regex)를 선택할 수 있는가 | | | +| 13 | 생성 | Parser 패턴 설정 | Parser 패턴을 설정할 수 있는가 | | | +| 14 | 생성 | 수집 주기 설정 | Log 수집 주기를 설정할 수 있는가 | | | +| 15 | 생성 | Buffer 설정 | Buffer 크기를 설정할 수 있는가 | | | +| 16 | 생성 | Retention 설정 | Log 보관 기간을 설정할 수 있는가 | | | +| 17 | 생성 | Config 생성 | 설정한 Log Config가 생성되는가 | | | +| 18 | 수정 | Edit 버튼 | Log Config 수정 버튼이 동작하는가 | | | +| 19 | 수정 | 수정 모달 | Log Config 수정 모달이 정상 열리는가 | | | +| 20 | 수정 | 기존 데이터 로드 | 모달에 기존 설정이 로드되는가 | | | +| 21 | 수정 | 경로 수정 | Log 파일 경로를 수정할 수 있는가 | | | +| 22 | 수정 | Parser 수정 | Parser 설정을 수정할 수 있는가 | | | +| 23 | 수정 | Config 업데이트 | 수정한 Config가 저장되는가 | | | +| 24 | 삭제 | Delete 버튼 | Log Config 삭제 버튼이 동작하는가 | | | +| 25 | 삭제 | 삭제 확인 | 삭제 확인 대화상자가 표시되는가 | | | +| 26 | 삭제 | Config 삭제 | 확인 후 Log Config가 삭제되는가 | | | +| 27 | 활성화 | Enable 토글 | Log Config 활성화/비활성화 토글이 동작하는가 | | | +| 28 | 활성화 | 상태 변경 | 토글 시 Config 상태가 변경되는가 | | | +| 29 | 활성화 | 수집 시작/중지 | 활성화/비활성화 시 Log 수집이 시작/중지되는가 | | | +| 30 | 테스트 | Test 버튼 | Log Config를 테스트할 수 있는가 | | | +| 31 | 테스트 | 샘플 Log 조회 | 설정한 경로의 샘플 Log가 표시되는가 | | | +| 32 | 테스트 | Parser 검증 | Parser가 올바르게 동작하는지 확인할 수 있는가 | | | +| 33 | 템플릿 | 템플릿 목록 | 사전 정의된 Log Config 템플릿이 제공되는가 | | | +| 34 | 템플릿 | 템플릿 적용 | 템플릿을 선택하여 빠르게 설정할 수 있는가 | | | +| 35 | 템플릿 | 템플릿 저장 | 현재 설정을 템플릿으로 저장할 수 있는가 | | | +| 36 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 37 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 38 | 테이블 기능 | 검색 | Log Config 검색 기능이 동작하는가 | | | +| 39 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 40 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | +| 41 | 에러 처리 | 경로 검증 | 잘못된 Log 경로 입력 시 에러 메시지가 표시되는가 | | | + +## 비고 +- 이 화면은 현재 구현 파일이 없습니다(미구현 상태). +- 테스트 항목은 일반적인 Log Config 기능을 기준으로 작성되었습니다. +- Log Config는 VM별로 수집할 Log의 경로, Parser, 수집 주기 등을 설정하는 기능입니다. +- Monitoring Config 화면의 LogTrace 설정과 연계하여 동작할 것으로 예상됩니다. +- 실제 구현 시 테스트 항목을 조정해야 할 수 있습니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + diff --git a/doc/test/013_Cost_Analysis_test.md b/doc/test/013_Cost_Analysis_test.md new file mode 100644 index 00000000..a2c3640e --- /dev/null +++ b/doc/test/013_Cost_Analysis_test.md @@ -0,0 +1,58 @@ +# 013_Cost_Analysis_test + +## 화면 정보 +- 메뉴 ID: costanalysis +- 화면 이름: Cost Analysis +- 파일 경로: front/assets/js/pages/operation/plugins/costoptimizer.iframe.js + +## 테스트 항목 + +| 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | +|------|---------|------|------------|------|----------| +| 1 | 초기화 | 화면 로드 | 페이지 진입 시 Cost Analysis 화면이 정상 표시되는가 | | | +| 2 | 초기화 | iframe 로드 | iframe을 통한 외부 플러그인이 정상 로드되는가 | | | +| 3 | 조회 | 비용 대시보드 | 전체 비용 대시보드가 정상 표시되는가 | | | +| 4 | 조회 | 기간 선택 | 조회 기간을 선택할 수 있는가 | | | +| 5 | 조회 | 기간별 비용 | 선택한 기간의 비용이 표시되는가 | | | +| 6 | 조회 | 비용 추세 | 비용 추세 그래프가 표시되는가 | | | +| 7 | 필터링 | Provider 필터 | Provider별 비용 필터링이 동작하는가 | | | +| 8 | 필터링 | Workload 필터 | Workload별 비용 필터링이 동작하는가 | | | +| 9 | 필터링 | 리소스 유형 필터 | 리소스 유형별 필터링이 동작하는가 | | | +| 10 | 필터링 | Project 필터 | Project별 비용 필터링이 동작하는가 | | | +| 11 | 비용 분석 | Provider별 분석 | Provider별 비용 분석이 표시되는가 | | | +| 12 | 비용 분석 | 리소스별 분석 | 리소스 유형별 비용 분석이 표시되는가 | | | +| 13 | 비용 분석 | 서비스별 분석 | 서비스별 비용 분석이 표시되는가 | | | +| 14 | 비용 분석 | Region별 분석 | Region별 비용 분석이 표시되는가 | | | +| 15 | 비용 분석 | 비용 비율 | 각 항목의 비용 비율이 표시되는가 | | | +| 16 | 그래프 | 시계열 그래프 | 일/주/월별 비용 추세 그래프가 표시되는가 | | | +| 17 | 그래프 | 파이 차트 | 비용 구성 파이 차트가 표시되는가 | | | +| 18 | 그래프 | 막대 그래프 | 비교 막대 그래프가 표시되는가 | | | +| 19 | 그래프 | 그래프 전환 | 그래프 유형 전환이 동작하는가 | | | +| 20 | 예측 | 비용 예측 | 향후 비용 예측이 표시되는가 | | | +| 21 | 예측 | 예측 기간 선택 | 예측 기간을 선택할 수 있는가 | | | +| 22 | 예측 | 예측 모델 | 예측 모델(선형/지수) 선택이 가능한가 | | | +| 23 | 최적화 | 최적화 추천 | 비용 최적화 추천 사항이 표시되는가 | | | +| 24 | 최적화 | 절감 예상액 | 예상 절감 비용이 표시되는가 | | | +| 25 | 최적화 | 미사용 리소스 | 미사용 리소스가 표시되는가 | | | +| 26 | 최적화 | 추천 적용 | 최적화 추천을 적용할 수 있는가 | | | +| 27 | 예산 관리 | 예산 설정 | 예산을 설정할 수 있는가 | | | +| 28 | 예산 관리 | 예산 대비 실제 | 예산 대비 실제 비용이 표시되는가 | | | +| 29 | 예산 관리 | 예산 초과 알림 | 예산 초과 시 알림이 표시되는가 | | | +| 30 | 태그 분석 | 태그별 비용 | 태그별 비용 분석이 표시되는가 | | | +| 31 | 태그 분석 | 태그 그룹화 | 태그 그룹별 비용이 표시되는가 | | | +| 32 | Export | Report Export | 비용 리포트를 내보낼 수 있는가 | | | +| 33 | Export | CSV Export | CSV 형식으로 내보내기가 가능한가 | | | +| 34 | Export | PDF Export | PDF 형식으로 내보내기가 가능한가 | | | +| 35 | Export | 스케줄링 | 정기 리포트 스케줄을 설정할 수 있는가 | | | +| 36 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 37 | 에러 처리 | 데이터 없음 | 비용 데이터가 없을 때 적절한 메시지가 표시되는가 | | | + +## 비고 +- 이 화면은 iframe 플러그인으로 구현되어 있습니다. +- 외부 Cost Optimizer 플러그인을 통해 비용 분석 기능을 제공합니다. +- 실제 기능은 플러그인 구현에 따라 달라질 수 있습니다. +- 테스트 항목은 일반적인 Cost Analysis 기능을 기준으로 작성되었습니다. +- 비용 데이터는 클라우드 Provider의 Billing API를 통해 수집될 것으로 예상됩니다. +- 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. +- 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. + From 6aa8a1fea48cc4951fc80b538c4ca156ebb46840 Mon Sep 17 00:00:00 2001 From: dogfootman Date: Tue, 4 Nov 2025 11:30:57 +0900 Subject: [PATCH 5/7] [docs]: Add bug tracking and fix documentation for workspace project mapping - Create bug list documentation to track unresolved issues related to workspace deletion and IAM Manager API. - Document the fix for workspace project mapping logic to prevent 500 errors during project selection. - Improve API error handling by replacing alert messages with toast notifications for better user experience. --- doc/bug/buglist.md | 31 ++ doc/fix/002_fix_workspace_project_mapping.md | 176 +++++++++++ doc/fix/003_fix_multiple_api_calls.md | 287 ++++++++++++++++++ doc/test/002_MCI_Workloads_test.md | 154 ++++++---- doc/test/003_PMK_Workloads_test.md | 160 ++++++---- doc/test/004_Workflows_test.md | 14 + doc/test/005_SW_Catalogs_test.md | 14 + doc/test/006_Data_Migrations_test.md | 14 + doc/test/007_MCIs_Monitoring_test.md | 14 + doc/test/008_Monitoring_Config_test.md | 14 + doc/test/009_Alarms_History_test.md | 14 + doc/test/010_Threshold_Config_test.md | 14 + doc/test/011_Log_Manage_test.md | 14 + doc/test/012_Log_Config_test.md | 14 + doc/test/013_Cost_Analysis_test.md | 14 + .../pages/operation/workspace/workspaces.js | 10 +- 16 files changed, 846 insertions(+), 112 deletions(-) create mode 100644 doc/bug/buglist.md create mode 100644 doc/fix/002_fix_workspace_project_mapping.md create mode 100644 doc/fix/003_fix_multiple_api_calls.md diff --git a/doc/bug/buglist.md b/doc/bug/buglist.md new file mode 100644 index 00000000..b4856d6b --- /dev/null +++ b/doc/bug/buglist.md @@ -0,0 +1,31 @@ +# Bug List + +## 버그 목록 / Bug Tracking + +--- + +### 001_Workspaces + +**화면**: Workspaces (운영 > 워크스페이스) + +**내용**: +- 목록에서 workspace 선택 후 delete 실행 시 `deleteWorkspaces` API가 404 에러 발생 +- IAM Manager API 확인 필요 + +**상태**: 미해결 + +**우선순위**: High + +**발견일**: 2025-11-03 + +**관련 파일**: +- `front/assets/js/pages/operation/workspace/workspaces.js` +- `front/assets/js/common/api/services/workspace_api.js` + +**추가 정보**: +- deleteWorkspaces API 엔드포인트 점검 필요 +- IAM Manager 연동 상태 확인 필요 + +--- + + diff --git a/doc/fix/002_fix_workspace_project_mapping.md b/doc/fix/002_fix_workspace_project_mapping.md new file mode 100644 index 00000000..fbdbdbde --- /dev/null +++ b/doc/fix/002_fix_workspace_project_mapping.md @@ -0,0 +1,176 @@ +# 002_fix_workspace_project_mapping + +## 현상 + +- Workspace 생성 시 "with Project" 옵션으로 프로젝트를 선택하고 Confirm을 누르면 500 에러 발생 +- Network 로그에 `CreateWorkspace`와 `createProject` API가 호출되는 것 확인됨 +- createWPmapping() 함수 호출 시 오류는 나지 않지만 workspace Id를 찾지 못해 매핑을 못한다는 로그 확인 +- 의도: 기존 프로젝트를 선택하여 workspace에 매핑하는 것 +- 실제: 새로운 프로젝트를 생성하려고 시도하여 오류 발생 + +## 문제 원인 + +### 잘못된 로직 +```javascript +// 기존 코드 (잘못된 로직) +if (document.getElementById('workspace-modal-add-withprojects').checked) { + let selectedProjects = Array.from(multiprojectSelect.selectedOptions); + + // ❌ 선택된 각 프로젝트에 대해 새로 생성 시도 + for (const option of selectedProjects) { + const createProjectResp = await createProject(projectName, projectDescription); + const createWPmappingResp = await createWPmapping(createdWorkspace.message.id, [createProjectResp.message.id]); + } +} +``` + +### 문제점 +1. **createProject API 호출**: 이미 존재하는 프로젝트를 다시 생성하려고 시도 +2. **중복 생성 시도**: selectbox에 표시된 기존 프로젝트를 새로 만들려고 함 +3. **매핑 실패**: 생성된 workspace ID를 찾지 못함 + +## 해결방법 + +### 올바른 로직 +- "with Project" 옵션은 **기존 프로젝트를 선택**하여 workspace에 **매핑만** 하는 기능 +- 모든 프로젝트는 기본 workspace에 할당됨 +- 다른 workspace에 매핑하면 기본 workspace에서는 제거되고 선택한 workspace로 이동 + +### 수정 방안 +```javascript +// 올바른 로직 +if (document.getElementById('workspace-modal-add-withprojects').checked) { + let multiprojectSelect = document.getElementById('workspace-modal-add-multiproject'); + let multiprojects = Array.from(multiprojectSelect.selectedOptions, option => option.value); + + // ✅ 기존 프로젝트 ID들을 workspace에 매핑만 수행 + const createdWPmapping = await createWPmapping(createdWorkspace.message.id, multiprojects); + if (!createdWPmapping.success) { + showToast("Failed to map projects to workspace: " + JSON.stringify(createdWPmapping.message), 'error'); + return + } +} +``` + +## 수정내역 + +### 수정 파일 + +#### `front/assets/js/pages/operation/workspace/workspaces.js` + +**함수**: `creatworkspaceProject()` (817-825라인) + +**변경 사항**: +1. ❌ **제거**: `createProject()` API 호출 - 새 프로젝트 생성 로직 삭제 +2. ❌ **제거**: for 루프를 통한 개별 프로젝트 처리 +3. ✅ **유지**: 선택된 프로젝트 ID 배열 추출 (`option.value`) +4. ✅ **수정**: `createWPmapping()` API를 **한 번만** 호출하여 모든 프로젝트 매핑 +5. ✅ **유지**: 에러 발생 시 toast 알림 표시 (alert → toast 이미 적용됨) + +**수정 전**: +```javascript +if (document.getElementById('workspace-modal-add-withprojects').checked) { + let multiprojectSelect = document.getElementById('workspace-modal-add-multiproject'); + let selectedProjects = Array.from(multiprojectSelect.selectedOptions); + + for (const option of selectedProjects) { + const projectName = option.text; + const existingProject = listData.prjList.find(p => p.id === option.value); + const projectDescription = existingProject ? existingProject.description : ''; + + // ❌ 새 프로젝트 생성 시도 + const createProjectResp = await createProject(projectName, projectDescription); + if (!createProjectResp.success) { + showToast("Failed to create project: " + JSON.stringify(createProjectResp.message), 'error'); + continue; + } + + const createWPmappingResp = await createWPmapping(createdWorkspace.message.id, [createProjectResp.message.id]); + if (!createWPmappingResp.success) { + showToast("Failed to map project to workspace: " + JSON.stringify(createWPmappingResp.message), 'error'); + continue; + } + } +} +``` + +**수정 후**: +```javascript +if (document.getElementById('workspace-modal-add-withprojects').checked) { + let multiprojectSelect = document.getElementById('workspace-modal-add-multiproject'); + let multiprojects = Array.from(multiprojectSelect.selectedOptions, option => option.value); + + // ✅ 기존 프로젝트 ID들을 매핑만 수행 + const createdWPmapping = await createWPmapping(createdWorkspace.message.id, multiprojects); + if (!createdWPmapping.success) { + showToast("Failed to map projects to workspace: " + JSON.stringify(createdWPmapping.message), 'error'); + return + } +} +``` + +## 검토 사항 + +### createworkspaceProject() 함수명 검토 필요 + +**현재 상황**: +- 함수명: `creatworkspaceProject()` (오타: creat → create) +- 실제 동작: workspace 생성 + 선택적으로 프로젝트 매핑 +- "with Project" 옵션 체크 시: 기존 프로젝트를 매핑만 함 (생성하지 않음) + +**개선 제안**: +1. **함수명 수정**: `creatworkspaceProject()` → `createWorkspaceWithMapping()` + - 더 명확한 의미 전달 + - 오타 수정 (creat → create) + +2. **함수 분리 검토**: + ```javascript + // Option 1: 기존 유지 + createWorkspaceWithMapping() // workspace 생성 + 선택적 프로젝트 매핑 + + // Option 2: 함수 분리 + createWorkspace() // workspace만 생성 + mapProjectsToWorkspace() // 프로젝트 매핑만 수행 + ``` + +3. **주석 추가**: + ```javascript + /** + * Create a new workspace and optionally map existing projects + * 새 워크스페이스를 생성하고 선택적으로 기존 프로젝트를 매핑합니다. + * + * @description + * - Creates a workspace with name and description + * - If "with Project" is checked, maps selected existing projects + * - Does NOT create new projects (only maps existing ones) + */ + export async function createWorkspaceWithMapping() { + // ... + } + ``` + +## 테스트 결과 + +### 테스트 시나리오 +1. ✅ Workspace만 생성 (with Project 체크 안 함) +2. ⏳ Workspace 생성 + 기존 프로젝트 매핑 (with Project 체크) + +### 예상 API 호출 +- **수정 전**: `CreateWorkspace` + `createProject` (❌ 잘못됨) +- **수정 후**: `CreateWorkspace` + `CreateWPmapping` (✅ 올바름) + +### 추가 검증 필요 +- [ ] createWPmapping API가 workspace ID를 정상적으로 인식하는지 확인 +- [ ] 프로젝트가 이전 workspace에서 정상적으로 제거되는지 확인 +- [ ] 프로젝트가 새 workspace에 정상적으로 할당되는지 확인 + +## 적용 브랜치 +- **브랜치명**: `fix_050` +- **기반 브랜치**: `develop` +- **관련 이슈**: Workspace 프로젝트 매핑 오류 + +## 참고사항 +- 프로젝트 생성 기능은 Projects 탭에서 별도로 제공됨 (`addWorkspaceProject()` 함수) +- "with Project" 옵션은 workspace 생성 시 기존 프로젝트를 바로 매핑하기 위한 편의 기능 +- 모든 프로젝트는 하나의 workspace에만 속할 수 있음 (1:N 관계) + diff --git a/doc/fix/003_fix_multiple_api_calls.md b/doc/fix/003_fix_multiple_api_calls.md new file mode 100644 index 00000000..7c507a56 --- /dev/null +++ b/doc/fix/003_fix_multiple_api_calls.md @@ -0,0 +1,287 @@ +# 003_fix_multiple_api_calls + +## 현상 + +- Workspaces 화면 로드 시 API가 불필요하게 여러 번 호출됨 +- Network 로그 확인 결과: + 1. `Listusers` - 1회 호출 ✅ + 2. `listProjects` - 1회 호출 ✅ + 3. `listMciamPermissions` - 1회 호출 ✅ + 4. `listUsersAndRolesByWorkspaces` - **N회 호출** ❌ (workspace 개수만큼) +- 예: workspace가 5개면 5회, 10개면 10회 호출 +- 성능 저하 및 불필요한 서버 부하 발생 + +## 문제 원인 + +### 현재 코드 구조 + +**파일**: `front/assets/js/pages/operation/workspace/workspaces.js` + +**함수**: `setWokrspaceTableData()` (462-485라인) + +```javascript +async function setWokrspaceTableData() { + var tableListData = []; + + // ❌ N+1 쿼리 문제: workspace 개수만큼 API 호출 + for (const workspace of listData.wsList) { + // 각 workspace마다 API 호출 + var respWorkspaceRoleMappingList = await webconsolejs["common/api/services/workspace_api"] + .getWorkspaceUserRoleMappingListByWorkspaceId(workspace.id); + + var userCount = 0 + var roleCountArr = new Set(); + if (respWorkspaceRoleMappingList.userinfo) { + respWorkspaceRoleMappingList.userinfo.forEach(function (wsmapping) { + userCount++ + roleCountArr.add(wsmapping.role.name) + }) + } + + tableListData.push({ + name: workspace.name, + id: workspace.id, + description: workspace.description, + created_at: workspace.created_at, + updated_at: workspace.updated_at, + userCount: userCount, // 이 값을 위해 API 호출 + roleCount: roleCountArr.size // 이 값을 위해 API 호출 + }) + }; + workspacesListTable.setData(tableListData) +} +``` + +### 문제점 + +1. **N+1 쿼리 문제**: workspace가 N개면 N번 API 호출 +2. **순차 처리**: for 루프에서 await를 사용하여 순차적으로 처리 (병렬 처리 미활용) +3. **중복 데이터**: 각 API 응답에 중복된 데이터가 포함될 가능성 +4. **성능 저하**: workspace가 많을수록 페이지 로딩 시간 증가 + +## 해결방법 + +### 개선 방안 + +**방법 1: 단일 API 호출로 통합** (권장) + +```javascript +async function setWokrspaceTableData() { + var tableListData = []; + + // ✅ 한 번만 호출: 모든 workspace의 user role mapping 정보 가져오기 + var allWorkspaceRoleMappings = await webconsolejs["common/api/services/workspace_api"] + .getWorkspaceUserRoleMappingListOrderbyWorkspace(); + + // workspace별로 그룹핑된 데이터 생성 + var workspaceMapping = {}; + allWorkspaceRoleMappings.forEach(mapping => { + if (!workspaceMapping[mapping.workspace_id]) { + workspaceMapping[mapping.workspace_id] = { + userCount: 0, + roles: new Set() + }; + } + workspaceMapping[mapping.workspace_id].userCount++; + workspaceMapping[mapping.workspace_id].roles.add(mapping.role_id); + }); + + // 테이블 데이터 생성 + for (const workspace of listData.wsList) { + const mappingData = workspaceMapping[workspace.id] || { userCount: 0, roles: new Set() }; + + tableListData.push({ + name: workspace.name, + id: workspace.id, + description: workspace.description, + created_at: workspace.created_at, + updated_at: workspace.updated_at, + userCount: mappingData.userCount, + roleCount: mappingData.roles.size + }) + }; + + workspacesListTable.setData(tableListData) +} +``` + +**방법 2: 병렬 처리 (현재 구조 유지 시)** + +```javascript +async function setWokrspaceTableData() { + var tableListData = []; + + // ✅ Promise.all로 병렬 처리 + var promises = listData.wsList.map(async (workspace) => { + var respWorkspaceRoleMappingList = await webconsolejs["common/api/services/workspace_api"] + .getWorkspaceUserRoleMappingListByWorkspaceId(workspace.id); + + var userCount = 0 + var roleCountArr = new Set(); + if (respWorkspaceRoleMappingList.userinfo) { + respWorkspaceRoleMappingList.userinfo.forEach(function (wsmapping) { + userCount++ + roleCountArr.add(wsmapping.role.name) + }) + } + + return { + name: workspace.name, + id: workspace.id, + description: workspace.description, + created_at: workspace.created_at, + updated_at: workspace.updated_at, + userCount: userCount, + roleCount: roleCountArr.size + }; + }); + + tableListData = await Promise.all(promises); + workspacesListTable.setData(tableListData) +} +``` + +### 성능 비교 + +| 방법 | API 호출 횟수 | 장점 | 단점 | +|------|--------------|------|------| +| **현재** (순차) | N회 | 구현 단순 | 느림, 서버 부하 큼 | +| **방법 1** (통합) | 1회 | 가장 빠름, 서버 부하 최소 | 데이터 구조 변경 필요 | +| **방법 2** (병렬) | N회 | 구현 쉬움 | 여전히 N회 호출, 동시 요청으로 서버 부하 | + +**권장**: 방법 1 (단일 API 호출) + +## 수정내역 + +### 수정 대상 파일 + +#### `front/assets/js/pages/operation/workspace/workspaces.js` + +**함수**: `setWokrspaceTableData()` (462-485라인) + +**수정 전**: N번 API 호출 (N = workspace 개수) +**수정 후**: 1번 API 호출 + +### API 확인 사항 + +#### 사용 가능한 API + +1. **`getWorkspaceUserRoleMappingListByWorkspaceId(wsId)`** + - 특정 workspace의 user role mapping 조회 + - 현재 사용 중 + - workspace별로 호출 필요 (N회) + +2. **`getWorkspaceUserRoleMappingListOrderbyWorkspace()`** + - 모든 workspace의 user role mapping 조회 + - workspace별로 그룹핑된 데이터 반환 + - 한 번만 호출하면 됨 (1회) + - **권장 사용** + +#### API 응답 형식 확인 필요 + +```javascript +// getWorkspaceUserRoleMappingListOrderbyWorkspace() 응답 예시 +[ + { + workspace_id: "ws-001", + user_id: "user-001", + role_id: "role-001", + role: { name: "Admin" }, + // ... + }, + { + workspace_id: "ws-001", + user_id: "user-002", + role_id: "role-002", + role: { name: "User" }, + // ... + }, + // ... +] +``` + +## 예상 효과 + +### 성능 개선 + +**현재 상황** (workspace 10개 가정): +- API 호출: 10회 +- 예상 시간: 약 2-5초 (네트워크 지연 포함) + +**개선 후**: +- API 호출: 1회 +- 예상 시간: 약 0.2-0.5초 +- **성능 향상: 약 4-10배** + +### 서버 부하 감소 + +- 불필요한 API 호출 제거 +- 데이터베이스 쿼리 횟수 감소 +- 네트워크 트래픽 감소 + +## 테스트 계획 + +### 테스트 시나리오 + +1. **기능 테스트** + - [ ] Workspaces 화면 로드 시 테이블이 정상 표시되는가 + - [ ] userCount가 정확하게 표시되는가 + - [ ] roleCount가 정확하게 표시되는가 + +2. **성능 테스트** + - [ ] Network 탭에서 API 호출 횟수 확인 + - [ ] `listUsersAndRolesByWorkspaces`가 1회만 호출되는가 + - [ ] 페이지 로딩 시간 측정 + +3. **에지 케이스** + - [ ] workspace가 0개일 때 + - [ ] user가 없는 workspace가 있을 때 + - [ ] role이 없는 workspace가 있을 때 + +## 구현 우선순위 + +1. **Phase 1**: API 응답 형식 확인 + - `getWorkspaceUserRoleMappingListOrderbyWorkspace()` API 응답 구조 파악 + - 필요한 데이터 추출 방법 검토 + +2. **Phase 2**: 코드 수정 + - `setWokrspaceTableData()` 함수 수정 + - 단일 API 호출로 변경 + - 데이터 그룹핑 로직 구현 + +3. **Phase 3**: 테스트 및 검증 + - 기능 테스트 + - 성능 테스트 + - 버그 확인 + +## 참고사항 + +### 관련 코드 위치 + +- **초기화**: `initWorkspace()` (434-449라인) +- **데이터 조회**: `updateInitData()` (451-460라인) +- **테이블 생성**: `setWokrspaceTableData()` (462-485라인) +- **API 호출**: `workspace_api.js` + +### 추가 최적화 가능 영역 + +1. **Roles 탭 로딩** (`setWokrspaceRolesTableData()` 함수) + - 유사한 패턴으로 최적화 가능 + +2. **Users 탭 로딩** (`setWokrspaceUserTableData()` 함수) + - 개별 사용자 정보 조회를 배치 처리로 개선 가능 + +## 적용 브랜치 + +- **브랜치명**: `fix_050` +- **기반 브랜치**: `develop` +- **관련 이슈**: Workspaces 화면 로딩 성능 개선 + +## 코딩 스타일 준수 + +- ✅ 들여쓰기: 2칸 +- ✅ 따옴표: 작은따옴표(') +- ✅ 세미콜론: 모든 구문 끝에 사용 +- ✅ 변수명: camelCase +- ✅ 주석: 슬래시 두 개(//) 사용 + diff --git a/doc/test/002_MCI_Workloads_test.md b/doc/test/002_MCI_Workloads_test.md index a14411de..147b4ae9 100644 --- a/doc/test/002_MCI_Workloads_test.md +++ b/doc/test/002_MCI_Workloads_test.md @@ -5,6 +5,29 @@ - 화면 이름: MCI Workloads - 파일 경로: front/assets/js/pages/operation/manage/mci.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### VM 생성 테스트 전제 조건 (생성 테스트 시) +1. 테스트할 CSP별로 다음 사항이 사전 준비되어야 함: + - Connection 설정 완료 + - Region: 서울 리전 (AWS: ap-northeast-2, Azure: koreacentral, GCP: asia-northeast3) + - OS: Ubuntu 계열 + - VM Spec: 해당 CSP에서 정상 동작 확인된 Spec + - VM Image: 해당 CSP에서 정상 동작 확인된 Image +2. VM 생성 실패 시 Connection, Region, Spec, Image 조합을 재확인 + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | @@ -22,65 +45,90 @@ | 11 | 조회 | Detail 탭 | VM의 상세 정보(Image, VPC, Subnet, Security Group)가 표시되는가 | | | | 12 | 조회 | Connection 탭 | VM의 Connection 설정 정보가 표시되는가 | | | | 13 | 조회 | Monitoring 탭 | VM의 모니터링 데이터가 표시되는가 | | | -| 14 | 생성 | Add 버튼 | 'Add Mci' 버튼이 정상 표시되는가 | | | -| 15 | 생성 | MCI 생성 화면 | Add 버튼 클릭 시 MCI 생성 화면으로 이동하는가 | | | -| 16 | 생성 | 부분 추가 | MCI Create partial이 정상 초기화되는가 | | | -| 17 | 생성 | API 호출 | MCI 생성 시 관련 API가 호출되는가 | | | -| 18 | 수정 | Refresh 버튼 | Refresh 버튼 클릭 시 MCI 목록이 갱신되는가 | | | -| 19 | 수정 | 자동 갱신 | MCI 선택 시 해당 MCI 정보가 자동 갱신되는가 | | | -| 20 | 삭제 | Delete MCI 버튼 | MCI 선택 후 Delete 버튼이 활성화되는가 | | | -| 21 | 삭제 | MCI 삭제 | Delete 확인 후 MCI가 삭제되는가 | | | -| 22 | 삭제 | Delete VM 버튼 | VM 선택 후 Delete 버튼이 활성화되는가 | | | -| 23 | 삭제 | VM 삭제 | Delete 확인 후 VM이 삭제되는가 | | | -| 24 | 삭제 | API 호출 | mciDelete, vmDelete API가 호출되는가 | | | -| 25 | 라이프사이클 | Start MCI | MCI Start 버튼이 동작하는가 | | | -| 26 | 라이프사이클 | Stop MCI | MCI Stop 버튼이 동작하는가 | | | -| 27 | 라이프사이클 | Reboot MCI | MCI Reboot 버튼이 동작하는가 | | | -| 28 | 라이프사이클 | Terminate MCI | MCI Terminate 버튼이 동작하는가 | | | -| 29 | 라이프사이클 | Start VM | VM Start 버튼이 동작하는가 | | | -| 30 | 라이프사이클 | Stop VM | VM Stop 버튼이 동작하는가 | | | -| 31 | 라이프사이클 | Reboot VM | VM Reboot 버튼이 동작하는가 | | | -| 32 | 라이프사이클 | Terminate VM | VM Terminate 버튼이 동작하는가 | | | -| 33 | 라이프사이클 | 상태 변경 | 라이프사이클 실행 후 상태가 정상 업데이트되는가 | | | -| 34 | 라이프사이클 | API 호출 | mciLifeCycle, vmLifeCycle API가 호출되는가 | | | -| 35 | SubGroup 관리 | SubGroup 목록 | SubGroup 탭에서 그룹별 VM이 표시되는가 | | | -| 36 | SubGroup 관리 | SubGroup 선택 | SubGroup 선택 시 해당 그룹의 VM 목록이 표시되는가 | | | -| 37 | SubGroup 관리 | VM 상세 | SubGroup 내 VM 선택 시 상세 정보가 표시되는가 | | | -| 38 | SubGroup 관리 | Scale Out | SubGroup 선택 후 Scale 버튼이 활성화되는가 | | | -| 39 | SubGroup 관리 | Scale 설정 | Scale 폼에서 VM 수를 증가시킬 수 있는가 | | | -| 40 | SubGroup 관리 | Scale 검증 | 현재 VM 수보다 작은 값 입력 시 에러 메시지가 표시되는가 | | | -| 41 | SubGroup 관리 | Scale 실행 | Apply 버튼 클릭 시 Scale Out이 실행되는가 | | | -| 42 | SubGroup 관리 | Scale 결과 | Scale Out 후 VM 목록이 갱신되는가 | | | -| 43 | SubGroup 관리 | API 호출 | postScaleOutSubGroup API가 호출되는가 | | | -| 44 | Policy 관리 | Policy 탭 | Policy 탭 전환이 정상 동작하는가 | | | -| 45 | Policy 관리 | Policy 목록 | 생성된 Policy 목록이 표시되는가 | | | -| 46 | Policy 관리 | Policy 상세 | Policy 선택 시 상세 정보가 표시되는가 | | | -| 47 | Policy 관리 | Policy 정보 | MCI, SubGroup, Condition, Action 정보가 표시되는가 | | | -| 48 | Policy 관리 | Policy 삭제 | Policy 삭제 기능이 동작하는가 | | | -| 49 | Policy 관리 | API 호출 | getPolicyList, deletePolicy API가 호출되는가 | | | -| 50 | 체크박스 선택 | 단일 선택 | VM 체크박스 단일 선택이 동작하는가 | | | -| 51 | 체크박스 선택 | 다중 선택 | VM 체크박스 다중 선택이 동작하는가 | | | -| 52 | 체크박스 선택 | 선택 강조 | 마지막 선택된 VM에 테두리가 표시되는가 | | | -| 53 | 체크박스 선택 | 선택 해제 | 체크박스 해제 시 정보 패널이 닫히는가 | | | -| 54 | 원격 접속 | SSH 키 조회 | SSH Key ID 클릭 시 Private Key가 표시되는가 | | | -| 55 | 원격 접속 | Terminal 모달 | Terminal 버튼 클릭 시 원격 터미널 모달이 열리는가 | | | -| 56 | 원격 접속 | Terminal 연결 | xterm 터미널이 정상 초기화되는가 | | | -| 57 | 원격 접속 | API 호출 | getsshkey, initTerminal API가 호출되는가 | | | -| 58 | 테이블 기능 | 상태 아이콘 | MCI 상태가 색상 카드로 표시되는가 | | | -| 59 | 테이블 기능 | Provider 아이콘 | Provider 로고 이미지가 표시되는가 | | | -| 60 | 테이블 기능 | VM 카운트 | Total/Running/Suspended/Terminated/Failed VM 수가 표시되는가 | | | -| 61 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | -| 62 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | -| 63 | 테이블 기능 | 필터링 | Provider 필터링이 동작하는가 | | | -| 64 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | -| 65 | 에러 처리 | 검증 오류 | VM 미선택 시 에러 메시지가 표시되는가 | | | +| 14 | 조회 | Agent 설치 | Monitoring Agent 설치 버튼/기능이 동작하는가 | | | +| 15 | 조회 | Agent 설치 API | InstallMonitoringAgent API가 호출되는가 | | | +| 16 | 조회 | Agent 상태 조회 | getTargetsNsMci API로 Agent 상태를 조회하는가 | | | +| 17 | 조회 | Agent 제거 | UninstallMonitoringAgent API로 Agent를 제거할 수 있는가 | | | +| 18 | 생성 - 기본 | Add 버튼 | 'Add Mci' 버튼이 정상 표시되는가 | | | +| 19 | 생성 - 기본 | MCI 생성 화면 | Add 버튼 클릭 시 MCI 생성 화면으로 이동하는가 | | | +| 20 | 생성 - 기본 | 부분 추가 | MCI Create partial이 정상 초기화되는가 | | | +| 21 | 생성 - 기본 | API 호출 | MCI 생성 시 관련 API가 호출되는가 | | | +| 22 | 생성 - AWS | Provider 선택 | Provider로 AWS를 선택할 수 있는가 | | | +| 23 | 생성 - AWS | Region 조회 | AWS Region 목록이 조회되는가 (기본: ap-northeast-2 서울) | | | +| 24 | 생성 - AWS | OS 선택 | Ubuntu 계열 OS를 선택할 수 있는가 | | | +| 25 | 생성 - AWS | Spec 선택 | 정상 동작 확인된 AWS VM Spec을 선택할 수 있는가 | | | +| 26 | 생성 - AWS | Image 선택 | 정상 동작 확인된 AWS VM Image를 선택할 수 있는가 | | | +| 27 | 생성 - Azure | Provider 선택 | Provider로 Azure를 선택할 수 있는가 | | | +| 28 | 생성 - Azure | Region 조회 | Azure Region 목록이 조회되는가 (기본: koreacentral 서울) | | | +| 29 | 생성 - Azure | OS 선택 | Ubuntu 계열 OS를 선택할 수 있는가 | | | +| 30 | 생성 - Azure | Spec 선택 | 정상 동작 확인된 Azure VM Spec을 선택할 수 있는가 | | | +| 31 | 생성 - Azure | Image 선택 | 정상 동작 확인된 Azure VM Image를 선택할 수 있는가 | | | +| 32 | 생성 - GCP | Provider 선택 | Provider로 GCP를 선택할 수 있는가 | | | +| 33 | 생성 - GCP | Region 조회 | GCP Region 목록이 조회되는가 (기본: asia-northeast3 서울) | | | +| 34 | 생성 - GCP | OS 선택 | Ubuntu 계열 OS를 선택할 수 있는가 | | | +| 35 | 생성 - GCP | Spec 선택 | 정상 동작 확인된 GCP VM Spec을 선택할 수 있는가 | | | +| 36 | 생성 - GCP | Image 선택 | 정상 동작 확인된 GCP VM Image를 선택할 수 있는가 | | | +| 37 | 생성 - 기타 CSP | Provider 선택 | Alibaba, NCP 등 기타 CSP를 선택할 수 있는가 | | | +| 38 | 생성 - 기타 CSP | VM 생성 | 각 CSP별로 VM이 정상 생성되는가 | | | +| 39 | 수정 | Refresh 버튼 | Refresh 버튼 클릭 시 MCI 목록이 갱신되는가 | | | +| 40 | 수정 | 자동 갱신 | MCI 선택 시 해당 MCI 정보가 자동 갱신되는가 | | | +| 41 | 삭제 | Delete MCI 버튼 | MCI 선택 후 Delete 버튼이 활성화되는가 | | | +| 42 | 삭제 | MCI 삭제 | Delete 확인 후 MCI가 삭제되는가 | | | +| 43 | 삭제 | Delete VM 버튼 | VM 선택 후 Delete 버튼이 활성화되는가 | | | +| 44 | 삭제 | VM 삭제 | Delete 확인 후 VM이 삭제되는가 | | | +| 45 | 삭제 | API 호출 | mciDelete, vmDelete API가 호출되는가 | | | +| 46 | 라이프사이클 | Start MCI | MCI Start 버튼이 동작하는가 | | | +| 47 | 라이프사이클 | Stop MCI | MCI Stop 버튼이 동작하는가 | | | +| 48 | 라이프사이클 | Reboot MCI | MCI Reboot 버튼이 동작하는가 | | | +| 49 | 라이프사이클 | Terminate MCI | MCI Terminate 버튼이 동작하는가 | | | +| 50 | 라이프사이클 | Start VM | VM Start 버튼이 동작하는가 | | | +| 51 | 라이프사이클 | Stop VM | VM Stop 버튼이 동작하는가 | | | +| 52 | 라이프사이클 | Reboot VM | VM Reboot 버튼이 동작하는가 | | | +| 53 | 라이프사이클 | Terminate VM | VM Terminate 버튼이 동작하는가 | | | +| 54 | 라이프사이클 | 상태 변경 | 라이프사이클 실행 후 상태가 정상 업데이트되는가 | | | +| 55 | 라이프사이클 | API 호출 | mciLifeCycle, vmLifeCycle API가 호출되는가 | | | +| 56 | SubGroup 관리 | SubGroup 목록 | SubGroup 탭에서 그룹별 VM이 표시되는가 | | | +| 57 | SubGroup 관리 | SubGroup 선택 | SubGroup 선택 시 해당 그룹의 VM 목록이 표시되는가 | | | +| 58 | SubGroup 관리 | VM 상세 | SubGroup 내 VM 선택 시 상세 정보가 표시되는가 | | | +| 59 | SubGroup 관리 | Scale Out | SubGroup 선택 후 Scale 버튼이 활성화되는가 | | | +| 60 | SubGroup 관리 | Scale 설정 | Scale 폼에서 VM 수를 증가시킬 수 있는가 | | | +| 61 | SubGroup 관리 | Scale 검증 | 현재 VM 수보다 작은 값 입력 시 에러 메시지가 표시되는가 | | | +| 62 | SubGroup 관리 | Scale 실행 | Apply 버튼 클릭 시 Scale Out이 실행되는가 | | | +| 63 | SubGroup 관리 | Scale 결과 | Scale Out 후 VM 목록이 갱신되는가 | | | +| 64 | SubGroup 관리 | API 호출 | postScaleOutSubGroup API가 호출되는가 | | | +| 65 | Policy 관리 | Policy 탭 | Policy 탭 전환이 정상 동작하는가 | | | +| 66 | Policy 관리 | Policy 목록 | 생성된 Policy 목록이 표시되는가 | | | +| 67 | Policy 관리 | Policy 상세 | Policy 선택 시 상세 정보가 표시되는가 | | | +| 68 | Policy 관리 | Policy 정보 | MCI, SubGroup, Condition, Action 정보가 표시되는가 | | | +| 69 | Policy 관리 | Policy 삭제 | Policy 삭제 기능이 동작하는가 | | | +| 70 | Policy 관리 | API 호출 | getPolicyList, deletePolicy API가 호출되는가 | | | +| 71 | 체크박스 선택 | 단일 선택 | VM 체크박스 단일 선택이 동작하는가 | | | +| 72 | 체크박스 선택 | 다중 선택 | VM 체크박스 다중 선택이 동작하는가 | | | +| 73 | 체크박스 선택 | 선택 강조 | 마지막 선택된 VM에 테두리가 표시되는가 | | | +| 74 | 체크박스 선택 | 선택 해제 | 체크박스 해제 시 정보 패널이 닫히는가 | | | +| 75 | 원격 명령 | SSH 키 조회 | SSH Key ID 클릭 시 Private Key가 표시되는가 | | | +| 76 | 원격 명령 | 명령 전송 모달 | Terminal 버튼 클릭 시 명령 전송 모달이 열리는가 | | | +| 77 | 원격 명령 | xterm UI 표시 | xterm UI가 정상 초기화되는가 (명령 입력 및 결과 표시용) | | | +| 78 | 원격 명령 | 명령 전송 API | postcmdmci API를 통해 VM에 명령이 전송되는가 | | | +| 79 | 테이블 기능 | 상태 아이콘 | MCI 상태가 색상 카드로 표시되는가 | | | +| 80 | 테이블 기능 | Provider 아이콘 | Provider 로고 이미지가 표시되는가 | | | +| 81 | 테이블 기능 | VM 카운트 | Total/Running/Suspended/Terminated/Failed VM 수가 표시되는가 | | | +| 82 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 83 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 84 | 테이블 기능 | 필터링 | Provider 필터링이 동작하는가 | | | +| 85 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 86 | 에러 처리 | 검증 오류 | VM 미선택 시 에러 메시지가 표시되는가 | | | ## 비고 - MCI(Multi-Cloud Infrastructure)는 여러 클라우드의 VM을 통합 관리하는 워크로드입니다. - SubGroup 단위로 VM을 그룹화하여 관리할 수 있습니다. - Auto Scaling Policy를 설정하여 자동으로 리소스를 관리할 수 있습니다. - VM 체크박스를 통해 다중 선택 및 일괄 라이프사이클 관리가 가능합니다. -- xterm을 통한 원격 터미널 접속 기능을 제공합니다. +- CSP별 VM 생성 시 Connection, Region, Spec, Image는 사전에 정상 동작하는 것으로 확인된 것을 사용해야 합니다. +- VM 생성 실패 시 CSP/Region/Spec/Image 조합을 검토해야 합니다. +- Monitoring Agent는 VM 생성 후 별도로 설치할 수 있으며, Agent 상태 조회 및 제거가 가능합니다. +- 원격 명령 전송은 xterm이 아닌 자체 API(postcmdmci)를 통해 이루어집니다. +- xterm은 명령 입력 및 결과 표시를 위한 UI로만 사용됩니다. - 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. - 실제 테스트 시 결과 열과 실패 사유 열을 채워주세요. diff --git a/doc/test/003_PMK_Workloads_test.md b/doc/test/003_PMK_Workloads_test.md index 8bde31e9..4c1af1c7 100644 --- a/doc/test/003_PMK_Workloads_test.md +++ b/doc/test/003_PMK_Workloads_test.md @@ -5,6 +5,30 @@ - 화면 이름: PMK Workloads - 파일 경로: front/assets/js/pages/operation/manage/pmk.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### PMK 생성 테스트 전제 조건 (생성 테스트 시) +1. Provider별 생성 방식: + - **Type1 (AWS, Alibaba, 기타)**: Cluster 생성 후 NodeGroup 별도 생성 (2단계) + - **Type2 (Azure, GCP, IBM, NHN)**: Cluster와 NodeGroup 동시 생성 (1단계) +2. 테스트할 Provider별로 다음 사항이 사전 준비되어야 함: + - Connection 설정 완료 + - Region 선택 (서울 리전 우선: AWS ap-northeast-2, Azure koreacentral, GCP asia-northeast3) + - Spec 및 Image 사전 확인 +3. Type2 Provider는 NodeGroup 폼이 표시되어야 함 + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | @@ -21,64 +45,92 @@ | 10 | 조회 | NodeGroup 목록 | 선택한 PMK의 NodeGroup 목록이 표시되는가 | | | | 11 | 조회 | NodeGroup 상세 | NodeGroup 선택 시 상세 정보가 표시되는가 | | | | 12 | 조회 | Node 정보 | NodeGroup의 Image, Spec, KeyPair, Node Size 정보가 표시되는가 | | | -| 13 | 생성 | Add 버튼 | 'Add cluster' 버튼이 정상 표시되는가 | | | -| 14 | 생성 | Simple Creation | Simple Creation 모드로 클러스터 생성 화면이 표시되는가 | | | -| 15 | 생성 | Expert Creation | Expert Creation 버튼으로 Expert 모드로 전환되는가 | | | -| 16 | 생성 | Provider 선택 | Provider 선택 드롭다운이 동작하는가 | | | -| 17 | 생성 | Region 선택 | Region 선택 드롭다운이 동작하는가 | | | -| 18 | 생성 | Provider 필터링 | Provider 선택 시 Region이 필터링되는가 | | | -| 19 | 생성 | Region 필터링 | Region 선택 시 Connection이 필터링되는가 | | | -| 20 | 생성 | Connection 선택 | Connection 선택 드롭다운이 동작하는가 | | | -| 21 | 생성 | NodeGroup 폼 표시 | Azure/GCP/IBM/NHN 선택 시 NodeGroup 구성 폼이 표시되는가 | | | -| 22 | 생성 | NodeGroup 폼 숨김 | 지원되지 않는 Provider 선택 시 NodeGroup 폼이 숨겨지는가 | | | -| 23 | 생성 | Spec 추천 모달 | Spec 추천 버튼 클릭 시 모달이 열리는가 | | | -| 24 | 생성 | Spec 선택 | 추천된 Spec을 선택할 수 있는가 | | | -| 25 | 생성 | Image 추천 모달 | Image 추천 버튼 클릭 시 모달이 열리는가 | | | -| 26 | 생성 | Image 선택 검증 | Spec 미선택 시 Image 모달 열기가 차단되는가 | | | -| 27 | 생성 | Image 선택 | 추천된 Image를 선택할 수 있는가 | | | -| 28 | 생성 | Node Size 조절 | Desired Node Size +/- 버튼이 동작하는가 | | | -| 29 | 생성 | 사전 검증 | Deploy 전 checkK8sClusterDynamic API가 호출되는가 | | | -| 30 | 생성 | 클러스터 생성 | Deploy 버튼 클릭 시 클러스터가 생성되는가 | | | -| 31 | 생성 | 폼 초기화 | 생성 완료 후 폼이 초기화되는가 | | | -| 32 | 생성 | API 호출 | checkK8sClusterDynamic, createK8sClusterDynamic API가 호출되는가 | | | -| 33 | 수정 | Refresh 버튼 | Refresh 버튼 클릭 시 PMK 목록이 갱신되는가 | | | -| 34 | 삭제 | Delete PMK 버튼 | PMK 선택 후 Delete 버튼이 활성화되는가 | | | -| 35 | 삭제 | PMK 삭제 | Delete 확인 후 PMK가 삭제되는가 | | | -| 36 | 삭제 | Delete NodeGroup 버튼 | NodeGroup 선택 후 Delete 버튼이 활성화되는가 | | | -| 37 | 삭제 | NodeGroup 삭제 | Delete 확인 후 NodeGroup이 삭제되는가 | | | -| 38 | 삭제 | API 호출 | pmkDelete, nodeGroupDelete API가 호출되는가 | | | -| 39 | 라이프사이클 | Start PMK | PMK Start 버튼이 동작하는가 | | | -| 40 | 라이프사이클 | Stop PMK | PMK Stop 버튼이 동작하는가 | | | -| 41 | 라이프사이클 | Terminate PMK | PMK Terminate 버튼이 동작하는가 | | | -| 42 | 라이프사이클 | 상태 변경 | 라이프사이클 실행 후 상태가 정상 업데이트되는가 | | | -| 43 | 라이프사이클 | API 호출 | pmkLifeCycle API가 호출되는가 | | | -| 44 | NodeGroup 관리 | 체크박스 선택 | NodeGroup 체크박스 선택이 동작하는가 | | | -| 45 | NodeGroup 관리 | 다중 선택 | NodeGroup 다중 선택이 동작하는가 | | | -| 46 | NodeGroup 관리 | 선택 강조 | 마지막 선택된 NodeGroup에 테두리가 표시되는가 | | | -| 47 | NodeGroup 관리 | Node 목록 | NodeGroup 내 Node 목록이 표시되는가 | | | -| 48 | Spec 추천 | Location 설정 | Seoul/London/New York 프리셋으로 좌표가 설정되는가 | | | -| 49 | Spec 추천 | 추천 조회 | Spec 추천 API가 정상 호출되는가 | | | -| 50 | Spec 추천 | Spec 테이블 | 추천된 Spec이 테이블에 표시되는가 | | | -| 51 | Spec 추천 | Provider 필터링 | Provider별 Spec 필터링이 동작하는가 | | | -| 52 | Spec 추천 | Spec 적용 | 선택한 Spec이 폼에 적용되는가 | | | -| 53 | Spec 추천 | 전역 변수 설정 | selectedPmkSpecInfo가 전역 변수에 저장되는가 | | | -| 54 | Spec 추천 | API 호출 | getRecommendVmInfoPmk API가 호출되는가 | | | -| 55 | Image 추천 | Image 모달 | Image 추천 모달이 정상 열리는가 | | | -| 56 | Image 추천 | Spec 정보 전달 | Spec 정보가 Image 모달에 전달되는가 | | | -| 57 | Image 추천 | Image 테이블 | 추천된 Image가 테이블에 표시되는가 | | | -| 58 | Image 추천 | Image 선택 | 선택한 Image가 폼에 적용되는가 | | | -| 59 | Image 추천 | 콜백 함수 | Image 선택 콜백이 정상 동작하는가 | | | -| 60 | 테이블 기능 | Provider 아이콘 | Provider 로고 이미지가 표시되는가 | | | -| 61 | 테이블 기능 | NodeGroup 수 | NodeGroup 개수가 표시되는가 | | | -| 62 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | -| 63 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | -| 64 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | -| 65 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | +| 13 | 생성 - 기본 | Add 버튼 | 'Add cluster' 버튼이 정상 표시되는가 | | | +| 14 | 생성 - 기본 | Simple Creation | Simple Creation 모드로 클러스터 생성 화면이 표시되는가 | | | +| 15 | 생성 - 기본 | Expert Creation | Expert Creation 버튼으로 Expert 모드로 전환되는가 | | | +| 16 | 생성 - Type1 공통 | Provider 선택 | AWS, Alibaba, 기타 Provider를 선택할 수 있는가 | | | +| 17 | 생성 - Type1 공통 | Region 선택 | Region 선택 드롭다운이 동작하는가 | | | +| 18 | 생성 - Type1 공통 | Connection 필터링 | Provider/Region 선택 시 Connection이 필터링되는가 | | | +| 19 | 생성 - Type1 공통 | Connection 선택 | Connection 선택 드롭다운이 동작하는가 | | | +| 20 | 생성 - Type1 공통 | Spec 추천 | Spec 추천 및 선택 기능이 동작하는가 | | | +| 21 | 생성 - AWS | Provider 선택 | Provider로 AWS를 선택할 수 있는가 | | | +| 22 | 생성 - AWS | Region 선택 | AWS Region 목록이 조회되는가 (기본: ap-northeast-2 서울) | | | +| 23 | 생성 - AWS | Connection 선택 | AWS Connection을 선택할 수 있는가 | | | +| 24 | 생성 - AWS | Cluster 생성 | Cluster 생성 API가 호출되는가 | | | +| 25 | 생성 - AWS | NodeGroup 추가 | Cluster 생성 후 NodeGroup을 별도로 추가할 수 있는가 | | | +| 26 | 생성 - Alibaba | Provider 선택 | Provider로 Alibaba를 선택할 수 있는가 | | | +| 27 | 생성 - Alibaba | Region 선택 | Alibaba Region 목록이 조회되는가 | | | +| 28 | 생성 - Alibaba | Connection 선택 | Alibaba Connection을 선택할 수 있는가 | | | +| 29 | 생성 - Alibaba | Cluster 생성 | Cluster 생성 API가 호출되는가 | | | +| 30 | 생성 - Alibaba | NodeGroup 추가 | Cluster 생성 후 NodeGroup을 별도로 추가할 수 있는가 | | | +| 31 | 생성 - Type2 공통 | Provider 선택 | Azure, GCP, IBM, NHN Provider를 선택할 수 있는가 | | | +| 32 | 생성 - Type2 공통 | Region 선택 | Region 선택 드롭다운이 동작하는가 | | | +| 33 | 생성 - Type2 공통 | Connection 선택 | Connection 선택 드롭다운이 동작하는가 | | | +| 34 | 생성 - Type2 공통 | NodeGroup 폼 표시 | NodeGroup 구성 폼이 자동으로 표시되는가 | | | +| 35 | 생성 - Type2 공통 | NodeGroup 정보 입력 | Image, Spec, Node Size를 입력할 수 있는가 | | | +| 36 | 생성 - Type2 공통 | 동시 생성 | Cluster와 NodeGroup이 동시에 생성되는가 | | | +| 37 | 생성 - Azure | Provider 선택 | Provider로 Azure를 선택할 수 있는가 | | | +| 38 | 생성 - Azure | Region 선택 | Azure Region 목록이 조회되는가 (기본: koreacentral 서울) | | | +| 39 | 생성 - Azure | NodeGroup 설정 | NodeGroup 폼에서 Image, Spec, Node Size를 설정할 수 있는가 | | | +| 40 | 생성 - Azure | 생성 확인 | Cluster + NodeGroup이 정상 생성되는가 | | | +| 41 | 생성 - GCP | Provider 선택 | Provider로 GCP를 선택할 수 있는가 | | | +| 42 | 생성 - GCP | Region 선택 | GCP Region 목록이 조회되는가 (기본: asia-northeast3 서울) | | | +| 43 | 생성 - GCP | NodeGroup 설정 | NodeGroup 폼에서 Image, Spec, Node Size를 설정할 수 있는가 | | | +| 44 | 생성 - GCP | 생성 확인 | Cluster + NodeGroup이 정상 생성되는가 | | | +| 45 | 생성 - IBM | Provider 선택 | Provider로 IBM을 선택할 수 있는가 | | | +| 46 | 생성 - IBM | Region 선택 | IBM Region 목록이 조회되는가 | | | +| 47 | 생성 - IBM | NodeGroup 설정 | NodeGroup 폼에서 Image, Spec, Node Size를 설정할 수 있는가 | | | +| 48 | 생성 - IBM | 생성 확인 | Cluster + NodeGroup이 정상 생성되는가 | | | +| 49 | 생성 - NHN | Provider 선택 | Provider로 NHN을 선택할 수 있는가 | | | +| 50 | 생성 - NHN | Region 선택 | NHN Region 목록이 조회되는가 | | | +| 51 | 생성 - NHN | NodeGroup 설정 | NodeGroup 폼에서 Image, Spec, Node Size를 설정할 수 있는가 | | | +| 52 | 생성 - NHN | 생성 확인 | Cluster + NodeGroup이 정상 생성되는가 | | | +| 53 | 생성 - 검증 및 API | 사전 검증 | Deploy 전 checkK8sClusterDynamic API가 호출되는가 | | | +| 54 | 생성 - 검증 및 API | 폼 초기화 | 생성 완료 후 폼이 초기화되는가 | | | +| 55 | 생성 - 검증 및 API | API 호출 | checkK8sClusterDynamic, createK8sClusterDynamic API가 호출되는가 | | | +| 56 | 생성 - 검증 및 API | 목록 갱신 | 생성 완료 후 PMK 목록이 갱신되는가 | | | +| 57 | 수정 | Refresh 버튼 | Refresh 버튼 클릭 시 PMK 목록이 갱신되는가 | | | +| 58 | 삭제 | Delete PMK 버튼 | PMK 선택 후 Delete 버튼이 활성화되는가 | | | +| 59 | 삭제 | PMK 삭제 | Delete 확인 후 PMK가 삭제되는가 | | | +| 60 | 삭제 | Delete NodeGroup 버튼 | NodeGroup 선택 후 Delete 버튼이 활성화되는가 | | | +| 61 | 삭제 | NodeGroup 삭제 | Delete 확인 후 NodeGroup이 삭제되는가 | | | +| 62 | 삭제 | API 호출 | pmkDelete, nodeGroupDelete API가 호출되는가 | | | +| 63 | 라이프사이클 | Start PMK | PMK Start 버튼이 동작하는가 | | | +| 64 | 라이프사이클 | Stop PMK | PMK Stop 버튼이 동작하는가 | | | +| 65 | 라이프사이클 | Terminate PMK | PMK Terminate 버튼이 동작하는가 | | | +| 66 | 라이프사이클 | 상태 변경 | 라이프사이클 실행 후 상태가 정상 업데이트되는가 | | | +| 67 | 라이프사이클 | API 호출 | pmkLifeCycle API가 호출되는가 | | | +| 68 | NodeGroup 관리 | 체크박스 선택 | NodeGroup 체크박스 선택이 동작하는가 | | | +| 69 | NodeGroup 관리 | 다중 선택 | NodeGroup 다중 선택이 동작하는가 | | | +| 70 | NodeGroup 관리 | 선택 강조 | 마지막 선택된 NodeGroup에 테두리가 표시되는가 | | | +| 71 | NodeGroup 관리 | Node 목록 | NodeGroup 내 Node 목록이 표시되는가 | | | +| 72 | Spec 추천 | Location 설정 | Seoul/London/New York 프리셋으로 좌표가 설정되는가 | | | +| 73 | Spec 추천 | 추천 조회 | Spec 추천 API가 정상 호출되는가 | | | +| 74 | Spec 추천 | Spec 테이블 | 추천된 Spec이 테이블에 표시되는가 | | | +| 75 | Spec 추천 | Provider 필터링 | Provider별 Spec 필터링이 동작하는가 | | | +| 76 | Spec 추천 | Spec 적용 | 선택한 Spec이 폼에 적용되는가 | | | +| 77 | Spec 추천 | 전역 변수 설정 | selectedPmkSpecInfo가 전역 변수에 저장되는가 | | | +| 78 | Spec 추천 | API 호출 | getRecommendVmInfoPmk API가 호출되는가 | | | +| 79 | Image 추천 | Image 모달 | Image 추천 모달이 정상 열리는가 | | | +| 80 | Image 추천 | Spec 정보 전달 | Spec 정보가 Image 모달에 전달되는가 | | | +| 81 | Image 추천 | Image 테이블 | 추천된 Image가 테이블에 표시되는가 | | | +| 82 | Image 추천 | Image 선택 | 선택한 Image가 폼에 적용되는가 | | | +| 83 | Image 추천 | 콜백 함수 | Image 선택 콜백이 정상 동작하는가 | | | +| 84 | 테이블 기능 | Provider 아이콘 | Provider 로고 이미지가 표시되는가 | | | +| 85 | 테이블 기능 | NodeGroup 수 | NodeGroup 개수가 표시되는가 | | | +| 86 | 테이블 기능 | 정렬 | 컬럼 클릭 시 정렬이 동작하는가 | | | +| 87 | 테이블 기능 | 페이징 | 페이지 네비게이션이 정상 동작하는가 | | | +| 88 | 에러 처리 | API 오류 | API 오류 발생 시 Toast 메시지가 표시되는가 | | | +| 89 | 에러 처리 | 검증 오류 | 필수 항목 누락 시 에러 메시지가 표시되는가 | | | ## 비고 - PMK(Platform Managed Kubernetes)는 Kubernetes 클러스터를 관리하는 워크로드입니다. - Simple Creation과 Expert Creation 두 가지 생성 모드를 제공합니다. -- Azure, GCP, IBM, NHN 등 일부 CSP에서 NodeGroup 구성을 지원합니다. +- **Provider별 생성 방식 차이**: + - **Type1 (AWS, Alibaba, 기타)**: Cluster 생성 후 NodeGroup을 별도로 추가하는 2단계 생성 방식 + - **Type2 (Azure, GCP, IBM, NHN)**: Cluster와 NodeGroup을 동시에 생성하는 1단계 생성 방식 +- Type2 Provider는 생성 시 NodeGroup 구성 폼이 자동으로 표시됩니다. +- NodeGroup 폼 표시 여부는 Provider에 따라 자동으로 결정됩니다. - Spec 및 Image 추천 기능을 통해 최적의 리소스를 선택할 수 있습니다. - 사전 검증(checkK8sClusterDynamic)을 통해 생성 가능 여부를 확인합니다. - 코드 분석 결과를 기반으로 자동 생성된 테스트 항목입니다. diff --git a/doc/test/004_Workflows_test.md b/doc/test/004_Workflows_test.md index 60e5c076..0e0f018f 100644 --- a/doc/test/004_Workflows_test.md +++ b/doc/test/004_Workflows_test.md @@ -5,6 +5,20 @@ - 화면 이름: Workflows - 파일 경로: front/assets/js/pages/operation/workflow/manageworkflow.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/005_SW_Catalogs_test.md b/doc/test/005_SW_Catalogs_test.md index 2c5f31e7..16059b85 100644 --- a/doc/test/005_SW_Catalogs_test.md +++ b/doc/test/005_SW_Catalogs_test.md @@ -5,6 +5,20 @@ - 화면 이름: SW Catalogs - 파일 경로: front/assets/js/pages/operation/plugins/softwaremanager.iframe.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/006_Data_Migrations_test.md b/doc/test/006_Data_Migrations_test.md index a5331d7c..b57766e0 100644 --- a/doc/test/006_Data_Migrations_test.md +++ b/doc/test/006_Data_Migrations_test.md @@ -5,6 +5,20 @@ - 화면 이름: Data Migrations - 파일 경로: front/assets/js/pages/operation/plugins/datamanager.iframe.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/007_MCIs_Monitoring_test.md b/doc/test/007_MCIs_Monitoring_test.md index cbde2eaa..1a7eaf36 100644 --- a/doc/test/007_MCIs_Monitoring_test.md +++ b/doc/test/007_MCIs_Monitoring_test.md @@ -5,6 +5,20 @@ - 화면 이름: MCIs Monitoring - 파일 경로: front/assets/js/pages/operation/manage/monitoring.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/008_Monitoring_Config_test.md b/doc/test/008_Monitoring_Config_test.md index 9de95262..64282b2d 100644 --- a/doc/test/008_Monitoring_Config_test.md +++ b/doc/test/008_Monitoring_Config_test.md @@ -5,6 +5,20 @@ - 화면 이름: Monitoring Config - 파일 경로: front/assets/js/pages/operation/analytics/monitoringconfig.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/009_Alarms_History_test.md b/doc/test/009_Alarms_History_test.md index 750a5c85..3d00c808 100644 --- a/doc/test/009_Alarms_History_test.md +++ b/doc/test/009_Alarms_History_test.md @@ -5,6 +5,20 @@ - 화면 이름: Alarms History - 파일 경로: (구현 파일 없음) +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/010_Threshold_Config_test.md b/doc/test/010_Threshold_Config_test.md index fe620ebb..3e3de42f 100644 --- a/doc/test/010_Threshold_Config_test.md +++ b/doc/test/010_Threshold_Config_test.md @@ -5,6 +5,20 @@ - 화면 이름: Threshold Config - 파일 경로: (구현 파일 없음) +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/011_Log_Manage_test.md b/doc/test/011_Log_Manage_test.md index 56326a97..402d3a25 100644 --- a/doc/test/011_Log_Manage_test.md +++ b/doc/test/011_Log_Manage_test.md @@ -5,6 +5,20 @@ - 화면 이름: Log Manage - 파일 경로: front/assets/js/pages/operation/analytics/logmanage.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/012_Log_Config_test.md b/doc/test/012_Log_Config_test.md index 6c89c5db..fe537c35 100644 --- a/doc/test/012_Log_Config_test.md +++ b/doc/test/012_Log_Config_test.md @@ -5,6 +5,20 @@ - 화면 이름: Log Config - 파일 경로: (구현 파일 없음) +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/doc/test/013_Cost_Analysis_test.md b/doc/test/013_Cost_Analysis_test.md index a2c3640e..b5008148 100644 --- a/doc/test/013_Cost_Analysis_test.md +++ b/doc/test/013_Cost_Analysis_test.md @@ -5,6 +5,20 @@ - 화면 이름: Cost Analysis - 파일 경로: front/assets/js/pages/operation/plugins/costoptimizer.iframe.js +## 테스트 전제 조건 + +### Workspace 및 Project 선택 +1. 화면 진입 시 workspace와 project가 선택되어 있지 않으면 경고 창이 표시됨 +2. 경고 창에서 **Confirm** 버튼을 클릭하여 닫기 +3. 화면 상단에서 **Workspace** 선택 + - 기본 선택: **ws01** (별다른 조건이 없는 경우) +4. Workspace 선택 후 **Project** 목록이 자동으로 로드됨 +5. **Project** 선택 + - 기본 선택: **default** (별다른 조건이 없는 경우) + +### 테스트 시작 +- Workspace와 Project 선택 완료 후 아래 테스트 항목을 순차적으로 수행 + ## 테스트 항목 | 번호 | 카테고리 | 기능 | 테스트 항목 | 결과 | 실패 사유 | diff --git a/front/assets/js/pages/operation/workspace/workspaces.js b/front/assets/js/pages/operation/workspace/workspaces.js index 5e31bb32..f836318d 100644 --- a/front/assets/js/pages/operation/workspace/workspaces.js +++ b/front/assets/js/pages/operation/workspace/workspaces.js @@ -811,7 +811,7 @@ export async function creatworkspaceProject() { let wsDesc = document.getElementById("workspace-modal-add-description").value const createdWorkspace = await webconsolejs["common/api/services/workspace_api"].createWorkspace(wsName, wsDesc); if (!createdWorkspace.success) { - alert(JSON.stringify(createdWorkspace.message)) + webconsolejs["common/util"].showToast("Failed to create workspace: " + JSON.stringify(createdWorkspace.message), 'error'); return } if (document.getElementById('workspace-modal-add-withprojects').checked) { @@ -819,7 +819,7 @@ export async function creatworkspaceProject() { let multiprojects = Array.from(multiprojectSelect.selectedOptions, option => option.value); const createdWPmapping = await webconsolejs["common/api/services/workspace_api"].createWPmapping(createdWorkspace.message.id, multiprojects); if (!createdWPmapping.success) { - alert(JSON.stringify(createdWorkspace.message)) + webconsolejs["common/util"].showToast("Failed to map projects to workspace: " + JSON.stringify(createdWPmapping.message), 'error'); return } } @@ -889,7 +889,7 @@ export async function deleteProjects() { checked_projects_array.forEach(async function (checkedProject) { var deleteWorkspaceProjectMappingByIdResp = await webconsolejs["common/api/services/workspace_api"].deleteWorkspaceProjectMappingById(currentClickedWorkspaceId, checkedProject.id); if (!deleteWorkspaceProjectMappingByIdResp.success) { - alert(JSON.stringify(updateWPmappingsResp.message.error)) + webconsolejs["common/util"].showToast("Failed to delete project mapping: " + JSON.stringify(deleteWorkspaceProjectMappingByIdResp.message.error), 'error'); } }) location.reload() @@ -908,12 +908,12 @@ export async function addWorkspaceProject() { var projectDescription = document.getElementById("project-modal-add-description").value const createProjectResp = await webconsolejs["common/api/services/workspace_api"].createProject(projectName, projectDescription); if (!createProjectResp.success) { - alert(JSON.stringify(createProjectResp.message)) + webconsolejs["common/util"].showToast("Failed to create project: " + JSON.stringify(createProjectResp.message), 'error'); return } else { const createWPmappingResp = await webconsolejs["common/api/services/workspace_api"].createWPmapping(targetWorkspaceId, [createProjectResp.message.id]); if (!createWPmappingResp.success) { - alert(JSON.stringify(createWPmappingResp.message)) + webconsolejs["common/util"].showToast("Failed to map project to workspace: " + JSON.stringify(createWPmappingResp.message), 'error'); return } else { location.reload() From d6bef6e04597279ea47efc12e84787ad778c7328 Mon Sep 17 00:00:00 2001 From: dogfootman Date: Mon, 10 Nov 2025 00:31:09 +0900 Subject: [PATCH 6/7] Enhance bug tracking and fix documentation - Add detailed bug reports for login and workspace session issues. - Document the addition of the SPEC NAME column in server recommendation tables. - Improve validation checks for PMK and NodeGroup deletion processes. - Remove readonly attribute from OS Architecture fields to allow user input. - Update image recommendation modals to include spec information and improve user experience with toast notifications for error handling. --- doc/bug/buglist.md | 655 +++++++++++++++++ doc/fix/004_add_spec_name_column.md | 244 +++++++ .../005_fix_postk8snodegroup_validation.md | 345 +++++++++ doc/fix/006_fix_pmk_delete_validation.md | 662 ++++++++++++++++++ .../007_remove_os_architecture_readonly.md | 432 ++++++++++++ doc/test/014_Azure_VM_Creation_test.md | 223 ++++++ doc/test/015_Azure_Image_Search_Plan.md | 539 ++++++++++++++ doc/test/azure_image_search_script.js | 151 ++++ doc/test/azure_vm_test_automation.js | 130 ++++ front/assets/js/application.js | 3 +- .../assets/js/common/api/services/mci_api.js | 11 +- .../assets/js/common/api/services/pmk_api.js | 96 ++- .../js/common/api/services/workspace_api.js | 39 +- front/assets/js/pages/operation/manage/pmk.js | 103 ++- front/assets/js/partials/layout/navbar.js | 22 +- .../operation/manage/clustercreate.js | 19 + .../operation/manage/imagerecommendation.js | 64 +- .../js/partials/operation/manage/mcicreate.js | 428 ++++++----- .../manage/pmk_serverrecommendation.js | 47 +- .../operation/manage/serverrecommendation.js | 47 +- .../manage/_imagerecommendation.html | 2 +- .../manage/_pmk_imagerecommendation.html | 1 - 22 files changed, 3970 insertions(+), 293 deletions(-) create mode 100644 doc/fix/004_add_spec_name_column.md create mode 100644 doc/fix/005_fix_postk8snodegroup_validation.md create mode 100644 doc/fix/006_fix_pmk_delete_validation.md create mode 100644 doc/fix/007_remove_os_architecture_readonly.md create mode 100644 doc/test/014_Azure_VM_Creation_test.md create mode 100644 doc/test/015_Azure_Image_Search_Plan.md create mode 100644 doc/test/azure_image_search_script.js create mode 100644 doc/test/azure_vm_test_automation.js diff --git a/doc/bug/buglist.md b/doc/bug/buglist.md index b4856d6b..43997e16 100644 --- a/doc/bug/buglist.md +++ b/doc/bug/buglist.md @@ -28,4 +28,659 @@ --- +### 002_Login + +**화면**: Login (로그인) + +**내용**: +- 로그인 실패 시 에러 메시지가 제대로 표시되지 않음 +- `webconsolejs["common/api/http"]` 모듈이 undefined 상태로 인해 TypeError 발생 +- 콘솔 에러: `TypeError: Cannot read properties of undefined (reading 'commonAPIPostWithoutRetry')` + +**상태**: 미해결 + +**우선순위**: High + +**발견일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/pages/auth/login.js` (8번 라인) + +**에러 상세**: +``` +login.js:23 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'commonAPIPostWithoutRetry') + at eval (login.js:23:48) + at Generator.eval (login.js:7:1660) + at Generator.eval [as next] (login.js:8:255) + at asyncGeneratorStep (login.js:9:70) + at _next (login.js:10:163) + at eval (login.js:10:299) + at new Promise () + at HTMLButtonElement.eval (login.js:10:90) +``` + +**추가 정보**: +- `webconsolejs["common/api/http"]` 모듈 로딩 확인 필요 +- 모듈 의존성 체크 필요 +- 에러 처리 로직 개선 필요 + +--- + +### 003_Workspace_Project_Session + +**화면**: 전체 (모든 페이지) + +**내용**: +- 로그인 후 창을 닫고 새 창을 열어 특정 경로로 직접 접속하면 workspace와 project 정보가 모두 사라짐 +- sessionStorage는 브라우저 창/탭이 닫히면 초기화되는데, 새 창에서 데이터를 다시 로드하는 로직이 제대로 작동하지 않았음 +- workspace/project selectbox가 비어있어 사용자가 다시 선택해야 하는 불편함 발생 + +**상태**: 해결됨 + +**우선순위**: High + +**발견일**: 2025-11-08 + +**해결일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/common/api/services/workspace_api.js` +- `front/assets/js/partials/layout/navbar.js` +- `front/assets/js/common/storage/sessionstorage.js` + +**해결 내용**: +1. `workspace_api.js`의 `getWorkspaceListByUser()` 함수 개선: + - sessionStorage가 null이거나 빈 배열인 경우 모두 체크하도록 수정 + - API 호출 실패 시 에러 핸들링 추가 (try-catch) + - API 응답 데이터 유효성 검증 추가 + +2. `navbar.js`의 `workspaceProjectInit()` 함수 강화: + - workspace 목록이 비어있는 경우 early return 처리 + - 빈 데이터에 대한 명시적인 처리 로직 추가 + +**테스트 방법**: +1. 로그인 +2. 브라우저 창 닫기 +3. 새 창 열어서 특정 경로로 직접 접속 (예: `/operation/manage/mci`) +4. navbar의 workspace/project selectbox에 목록이 정상적으로 표시되는지 확인 + +--- + +### 004_MCI_Deploy_Error_Handling + +**화면**: MCI Workloads - Create MCI (MCI 생성) + +**내용**: +- `PostMciDynamicReview` API가 HTTP 200 응답을 반환하지만 `responseData.overallStatus`가 "Error"인 경우 오류 처리 미흡 +- HTTP 상태 코드만 확인하고 응답 데이터 내부의 `overallStatus`를 체크하지 않아 오류가 발생해도 사용자에게 알림이 표시되지 않음 +- `creationViable: false`와 `overallStatus: "Error"` 두 가지를 모두 체크해야 함 + +**상태**: 해결됨 + +**우선순위**: High + +**발견일**: 2025-11-08 + +**해결일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/partials/operation/manage/mcicreate.js` (라인 792-846) + +**문제 상황 예시**: +```json +{ + "status": { "code": 200, "message": "200 OK" }, + "responseData": { + "overallStatus": "Error", + "creationViable": false, + "overallMessage": "MCI cannot be created due to critical errors in VM configurations", + "vmReviews": [{ + "status": "Error", + "errors": ["Image 'ami-0593272c889084af9' not available in CSP..."] + }] + } +} +``` + +**해결 내용**: +1. `createMciDynamic()` 함수에 `overallStatus === "Error"` 체크 로직 추가 +2. 오류 발생 시 Toast 알림으로 상세 정보 표시: + - VM 이름 및 SubGroup Size + - Provider 및 Region 정보 + - Image ID + - 구체적인 오류 메시지 (vmReviews[].errors) +3. `showToast()` 함수 사용하여 8초 동안 오류 메시지 표시 + +**수정 전 로직**: +```javascript +if (reviewData.creationViable) { + if (reviewData.overallStatus === "Ready") { + // 배포 진행 + } else if (reviewData.overallStatus === "Warning") { + // 경고 표시 + } else { + // 단순 alert만 표시 + alert(`MCI 생성 검증 실패:\n${reviewData.overallMessage}`); + } +} +``` + +**수정 후 로직**: +```javascript +// overallStatus 우선 체크 +if (reviewData.overallStatus === "Error" || !reviewData.creationViable) { + // 상세 오류 정보 수집 + let errorMessage = `MCI 생성 오류\n\n${reviewData.overallMessage}\n\n`; + + if (reviewData.vmReviews && reviewData.vmReviews.length > 0) { + reviewData.vmReviews.forEach(vm => { + if (vm.status === "Error" && vm.errors) { + errorMessage += `VM: ${vm.vmName} (SubGroup Size: ${vm.subGroupSize})\n`; + errorMessage += `Provider: ${vm.providerName}\n`; + errorMessage += `Region: ${vm.regionName}\n`; + errorMessage += `Image: ${vm.imageValidation.resourceId}\n`; + errorMessage += `\n오류:\n`; + vm.errors.forEach(err => { + errorMessage += `- ${err}\n`; + }); + } + }); + } + + // Toast로 표시 + webconsolejs["common/util"].showToast(errorMessage, 'error', 8000); + return; +} + +if (reviewData.creationViable) { + if (reviewData.overallStatus === "Ready") { + // 배포 진행 + } else if (reviewData.overallStatus === "Warning") { + // 경고 표시 + } +} +``` + +**테스트 방법**: +1. MCI 생성 화면에서 Azure VM 생성 시도 +2. AWS 이미지 ID (예: ami-0593272c889084af9)를 Azure에서 사용하도록 설정 +3. Deploy 버튼 클릭 +4. Toast 알림으로 상세 오류 메시지가 표시되는지 확인 +5. 오류 메시지에 VM 정보, 이미지 정보, 구체적인 오류 내용이 모두 포함되어 있는지 확인 + +**관련 문서**: +- `doc/test/014_Azure_VM_Creation_test.md` (시도 4 실패 기록) + +--- + +### 005_Image_Search_Provider_Filter_Not_Working + +**화면**: MCI Workloads - Create MCI - Image Recommendation Modal (이미지 추천 모달) + +**내용**: +- Image Recommendation 모달에서 Spec Information 필드 (Provider, Region, OS Architecture)가 비어있는 상태로 표시됨 +- Spec을 선택한 후 Image 검색 버튼을 클릭해도 해당 Provider의 이미지만 필터링되지 않고 모든 Cloud Provider의 이미지가 조회됨 +- 예: Azure Spec 선택 후 → AWS (ami-*), Alibaba (*.vhd), NCP (23214590), Azure (img-*) 이미지가 모두 표시됨 +- 사용자가 다른 Provider의 이미지를 선택하면 Deploy 시 "invalid format for image ID" 오류 발생 + +**상태**: 미해결 + +**우선순위**: High + +**발견일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/partials/operation/manage/imagerecommendation.js` (라인 174-258: `getRecommendImageInfo` 함수) +- `front/assets/js/partials/operation/manage/imagerecommendation.js` (라인 342-403: `validateAndOpenImageModal` 함수) +- `front/assets/js/partials/operation/manage/mcicreate.js` (라인 35-68: `callbackServerRecommendation` 함수) + +**문제 원인**: +1. **프론트엔드 이슈**: 모달을 열 때 Spec Information 필드가 채워지지 않음 + - `validateAndOpenImageModal()` 함수에서 필드를 설정하지 않음 (수정 완료했으나 JavaScript 캐시 문제로 미반영) +2. **백엔드 또는 API 호출 이슈**: Image Search API가 Provider/Region/Architecture 파라미터를 받아도 필터링하지 않음 + - `getRecommendImageInfo()` 함수가 `matchedSpecId`만 전달하고 있음 + - API가 `matchedSpecId`를 제대로 파싱해서 필터링하지 않는 것으로 추정 + +**재현 방법**: +1. MCI Create 화면에서 +SubGroup 클릭 +2. Server Recommendation 모달에서 Azure Spec 선택 (예: Standard_B2ts_v2) +3. Image 검색 버튼 클릭 +4. Image Recommendation 모달에서 Spec Information 필드 확인 → 비어있음 +5. 검색 버튼 클릭 → AWS, Alibaba, NCP, Azure 이미지가 모두 표시됨 + +**기대 동작**: +- Spec Information 필드에 Provider (azure), Region (koreacentral), OS Architecture (x86_64)가 자동으로 채워짐 +- 검색 시 Azure 전용 이미지만 표시되어야 함 (img-*, 일부 Azure Native 이미지) +- AWS 이미지 (ami-*), Alibaba 이미지 (*.vhd) 등은 표시되지 않아야 함 + +**해결 방법 (부분)**: +- ✅ 프론트엔드 수정 완료: `imagerecommendation.js`의 `validateAndOpenImageModal()` 함수에서 모달을 열 때 필드를 채우도록 수정 (라인 375-380) +```javascript +// Spec Information 필드를 window.selectedSpecInfo로 채우기 +if (window.selectedSpecInfo) { + document.getElementById('image-provider').value = window.selectedSpecInfo.provider || ""; + document.getElementById('image-region').value = window.selectedSpecInfo.regionName || ""; + document.getElementById('image-os-architecture').value = window.selectedSpecInfo.osArchitecture || "x86_64"; +} +``` +- ❌ **백엔드 API 확인 필요**: Image Search API가 Provider/Region 필터링을 제대로 수행하는지 확인 필요 +- ❌ **API 호출 파라미터 확인 필요**: `matchedSpecId` 외에 개별 파라미터 (provider, region, osArch)를 명시적으로 전달해야 하는지 확인 + +**임시 우회 방법**: +- 사용자가 수동으로 해당 Provider의 이미지만 선택 (BASIC 체크 유무, 이미지 ID 형식으로 구분) +- Azure: img-* 형식 +- AWS: ami-* 형식 +- Alibaba: *.vhd 형식 +- NCP: 숫자 형식 + +**관련 문서**: +- `doc/test/014_Azure_VM_Creation_test.md` (시도 5-7 실패 기록 - AWS/Alibaba 이미지 혼재) + +--- + +### 005_Bootstrap_Undefined_Error + +**화면**: 전체 (모든 페이지) + +**내용**: +- navbar.js에서 `bootstrap is not defined` 오류 발생 +- tooltip 초기화 시 bootstrap 객체를 찾을 수 없음 +- `imagerecommendation.js` 파일 404 에러도 함께 발생 (webpack 빌드 필요) + +**상태**: 해결됨 + +**우선순위**: High + +**발견일**: 2025-11-08 + +**해결일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/application.js` +- `front/assets/js/partials/layout/navbar.js` (라인 21-24) +- `front/assets/js/partials/operation/manage/imagerecommendation.js` + +**에러 상세**: +``` +navbar.js:39 Uncaught (in promise) ReferenceError: bootstrap is not defined + at eval (navbar.js:39:11) + at Array.map () + at eval (navbar.js:38:42) + +GET http://localhost:3001/assets/partials/operation/manage/imagerecommendation.856655508ce8784c43cb.js +net::ERR_ABORTED 404 (Not Found) +``` + +**해결 내용**: +1. `application.js`를 수정하여 bootstrap을 전역으로 노출: +```javascript +// 수정 전 +require("bootstrap/dist/js/bootstrap.bundle.js"); + +// 수정 후 +const bootstrap = require("bootstrap/dist/js/bootstrap.bundle.js"); +window.bootstrap = bootstrap; +``` + +2. Webpack 재빌드 실행: +```bash +cd front && yarn build +``` + +**테스트 결과**: +- ✅ Bootstrap 오류 없음 +- ✅ Workspace 목록 정상 로드 +- ✅ JavaScript 404 오류 없음 +- ✅ Tooltip 정상 작동 + +**테스트 시나리오**: +1. 브라우저 새로고침 → workspace 목록 정상 표시 +2. 브라우저 탭 닫고 새 탭에서 직접 접속 → workspace 목록 정상 표시 +3. 콘솔에 bootstrap 관련 오류 없음 확인 + +--- + +### 006_Workspace_Selection_Not_Persisted + +**화면**: 전체 (모든 페이지 navbar) + +**내용**: +- workspace를 선택한 후 브라우저 탭을 닫고 새 탭에서 접속하면 workspace 선택이 초기화됨 +- sessionStorage는 탭 간 공유되지 않는데, 새로 workspace 목록을 API에서 조회할 때 선택 정보를 의도적으로 초기화함 +- 사용자가 매번 workspace와 project를 다시 선택해야 하는 불편함 + +**상태**: 미해결 + +**우선순위**: Medium + +**발견일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/common/api/services/workspace_api.js` (라인 71-73) +- `front/assets/js/partials/layout/navbar.js` (라인 96-102) +- `front/assets/js/common/storage/sessionstorage.js` + +**문제 원인**: +1. **sessionStorage의 특성**: + - sessionStorage는 각 탭마다 독립적 + - 탭을 닫으면 해당 탭의 sessionStorage만 삭제됨 + - 새 탭은 빈 sessionStorage로 시작 + +2. **현재 로직의 문제**: +```javascript +// workspace_api.js 라인 71-73 +// 새로 조회한 경우 저장된 curworkspace, curproject 는 초기화 +setCurrentWorkspace(""); +setCurrentProject(""); +``` + - workspace 목록을 API에서 새로 조회하면 선택 정보를 빈 문자열로 초기화 + - 새 탭에서는 항상 API를 호출하므로 항상 초기화됨 + +3. **현재 sessionStorage 상태**: +```json +{ + "currentWorkspace": "\"\"", // 빈 문자열 + "currentProject": "\"\"", // 빈 문자열 + "currentWorkspaceProjcetList": "[{...}]" // 목록은 있음 +} +``` + +**기대 동작**: +- 같은 브라우저 세션 내에서 탭을 닫고 새 탭을 열어도 workspace 선택이 유지되어야 함 +- 또는 localStorage를 사용하여 브라우저를 완전히 닫아도 유지 + +**해결 방안** (선택 가능): + +**방안 1**: localStorage 사용 (권장) +- currentWorkspace와 currentProject를 localStorage에 저장 +- 브라우저를 완전히 닫아도 유지됨 +- 장점: 사용자 경험 향상 +- 단점: 명시적으로 초기화하지 않으면 계속 유지 + +**방안 2**: workspace_api.js 로직 수정 +- 라인 71-73의 초기화 로직 제거 또는 조건부 실행 +- sessionStorage에 workspace 목록이 있고 currentWorkspace도 있으면 유지 +- 장점: 최소한의 수정 +- 단점: sessionStorage의 한계로 탭 간 공유는 안됨 + +**방안 3**: 하이브리드 방식 +- workspace 목록은 sessionStorage에 저장 (API 부하 감소) +- currentWorkspace/currentProject는 localStorage에 저장 (선택 유지) +- 장점: 두 방식의 장점 결합 +- 단점: 구현 복잡도 증가 + +**추가 정보**: +- 현재는 workspace 목록(7개)이 정상적으로 로드됨 +- workspace 선택 기능 자체는 정상 작동 +- 단순히 선택 정보가 유지되지 않는 것만이 문제 + +**관련 이슈**: +- #003_Workspace_Project_Session (이미 해결됨 - workspace 목록 로드 문제) +- 이 버그는 workspace 목록은 로드되지만 "선택"이 유지되지 않는 별개의 문제 + +--- + +### 007_Image_Recommendation_Alert_Usage + +**화면**: MCI Workloads - Create MCI - Image Recommendation Modal (이미지 추천 모달) + +**내용**: +- Image Recommendation 모달에서 이미지 검색 결과가 없을 때 브라우저 기본 `alert()` 사용 +- 사용자 경험을 위해 Toast 알림으로 변경 필요 +- 현재는 "No images found for the selected specification and OS type. Please try different criteria." 메시지를 alert로 표시 + +**상태**: 미해결 + +**우선순위**: Medium + +**발견일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/partials/operation/manage/imagerecommendation.js` (라인 219) + +**문제 코드**: +```javascript +// 라인 216-221 +if (imageList.length === 0) { + console.warn("No images found for the selected spec and OS type"); + alert("No images found for the selected specification and OS type. Please try different criteria."); + safeSetTableData([]); + return; +} +``` + +**해결 방법**: +```javascript +// alert 대신 Toast 사용 +if (imageList.length === 0) { + console.warn("No images found for the selected spec and OS type"); + webconsolejs["common/util"].showToast( + "No images found for the selected specification and OS type. Please try different criteria.", + 'warning', + 5000 + ); + safeSetTableData([]); + return; +} +``` + +**테스트 방법**: +1. MCI 생성 화면에서 +SubGroup 클릭 +2. Spec 선택 (예: Azure Standard_B2ts_v2) +3. Image 검색 버튼 클릭 +4. OS Type에 존재하지 않는 값 입력 (예: "ubuntu 22.04") +5. 검색 버튼 클릭 +6. Toast 알림이 표시되는지 확인 + +**관련 이슈**: +- #005_Image_Search_Provider_Filter_Not_Working (이미지 검색 관련) + +--- + +### 008_Image_Search_OS_Type_Not_Cleared + +**화면**: MCI Workloads - Create MCI - Image Recommendation Modal (이미지 추천 모달) + +**내용**: +- Image Recommendation 모달에서 OS Type 입력 필드를 비워도 API 요청에는 이전 값이 계속 전달됨 +- 사용자가 필드를 비웠음에도 불구하고 `osType: "ubuntu 22.04"` 같은 값이 API에 전달되어 검색 결과가 비어있음 +- 필드 값과 실제 API 요청 파라미터가 불일치하는 문제 + +**상태**: 미해결 + +**우선순위**: High + +**발견일**: 2025-11-08 + +**관련 파일**: +- `front/assets/js/partials/operation/manage/imagerecommendation.js` (라인 182, 202) + +**문제 원인**: +1. OS Type 입력 필드 (`#assist_os_type`)를 비워도 (value = "") API 호출 시 이전 값이 사용됨 +2. 가능한 원인: + - 필드 값이 어딘가에 캐시되어 있을 가능성 + - placeholder 값이 실제 값처럼 동작할 가능성 + - 다른 hidden field에서 값을 가져오고 있을 가능성 + +**재현 방법**: +1. MCI 생성 화면에서 Image 검색 모달 열기 +2. OS Type에 "ubuntu 22.04" 입력 후 검색 → 결과 없음 +3. 브라우저 콘솔에서 OS Type 필드 비우기: + ```javascript + document.getElementById('assist_os_type').value = ''; + ``` +4. 다시 검색 버튼 클릭 +5. 네트워크 탭 또는 console.log에서 API 요청 확인 +6. **결과**: `osType: "ubuntu 22.04"`가 여전히 전달됨 + +**Console 로그 증거**: +``` +[LOG] Request Data : {"pathParams":{"nsId":"system"},"request":{ + "includeDeprecatedImage":false, + "isGPUImage":false, + "isKubernetesImage":false, + "osArchitecture":"x86_64", + "osType":"ubuntu 22.04", // 필드를 비웠는데도 전달됨! + "providerName":"azure", + "regionName":"koreacentral" +}} +``` + +**문제 코드**: +```javascript +// 라인 182 +var osType = $("#assist_os_type").val() + +// 라인 198-203 +var searchParams = { + providerName: provider, + regionName: region, + osArchitecture: window.selectedSpecInfo.osArchitecture || "x86_64", + osType: osType // 빈 값이어야 하는데 이전 값이 들어감 +}; +``` + +**기대 동작**: +- OS Type 필드를 비우면 (value = "") API 요청에서도 `osType: ""`로 전달되어야 함 +- 또는 빈 값인 경우 `osType` 파라미터 자체를 제거해야 함 + +**해결 방법 (제안)**: +```javascript +// 라인 182-183 +var osType = $("#assist_os_type").val(); +console.log("OS Type value from input:", osType); // 디버깅용 + +// 라인 198-209 +var searchParams = { + providerName: provider, + regionName: region, + osArchitecture: window.selectedSpecInfo.osArchitecture || "x86_64" +}; + +// osType이 비어있지 않은 경우에만 추가 +if (osType && osType.trim() !== "") { + searchParams.osType = osType; +} +``` + +**추가 조사 필요**: +1. `#assist_os_type` 요소가 실제 input 필드인지 확인 +2. jQuery `.val()` 메서드가 올바른 값을 반환하는지 확인 +3. 모달이 열릴 때 필드가 초기화되는지 확인 (이전 값이 남아있을 가능성) +4. OS Type dropdown 선택 로직 확인 (`selectOSType` 함수, 라인 40-54) + +**테스트 방법**: +1. Image Recommendation 모달 열기 +2. 브라우저 개발자 도구 Console 탭 열기 +3. OS Type 필드 확인: + ```javascript + document.getElementById('assist_os_type').value + ``` +4. 값을 비운 후: + ```javascript + document.getElementById('assist_os_type').value = ''; + document.getElementById('assist_os_type').value // 빈 문자열 확인 + ``` +5. 검색 버튼 클릭 +6. Network 탭에서 Searchimage API 요청의 Payload 확인 +7. osType이 비어있는지 또는 제외되었는지 확인 + +**관련 이슈**: +- #007_Image_Recommendation_Alert_Usage (이미지 검색 alert 문제) +- #005_Image_Search_Provider_Filter_Not_Working (이미지 검색 필터 문제) + +--- + +### 009_Workspace_Project_Selection_Validation + +**화면**: 전체 (모든 페이지 - 화면 전환 시) + +**내용**: +- Workspace와 Project를 선택하도록 되어 있으나, 검증 로직이 Workspace만 체크함 +- Workspace를 선택하지 않으면 경고 창이 표시되지만, Workspace만 선택하고 Project를 선택하지 않은 상태로 다른 화면으로 이동하면 Project 선택 경고 창이 표시되지 않음 +- `checkWorkspaceSelection()` 함수가 `workspaceId`만 확인하고 `projectId`는 확인하지 않음 + +**상태**: 미해결 + +**우선순위**: Low + +**발견일**: 2025-11-09 + +**관련 파일**: +- `front/assets/js/partials/layout/modal.js` (라인 64-69: `checkWorkspaceSelection` 함수) +- `front/assets/js/pages/operation/manage/mci.js` (라인 64) +- `front/assets/js/pages/operation/manage/pmk.js` (라인 81) +- `front/assets/js/pages/operation/manage/monitoring.js` (라인 38) +- `front/assets/js/pages/operation/manage/eventalarm.js` (라인 44) +- `front/assets/js/pages/operation/analytics/monitoringconfig.js` (라인 110) +- `front/assets/js/pages/operation/analytics/logmanage.js` (라인 56) +- `front/assets/js/pages/operation/dashboard/ns.js` (라인 13) +- `front/assets/js/pages/operation/workspace/roles.js` (라인 1398) + +**문제 코드**: +```javascript +// modal.js 라인 64-69 +// workspace selection 여부 확인 function +export function checkWorkspaceSelection(selectedWorkspaceProject) { + if (selectedWorkspaceProject.workspaceId == "") { + commonShowDefaultModal('Workspace Selection Check', 'Please select workspace first') + } + // projectId 검증 누락! +} +``` + +**재현 방법**: +1. 로그인 후 navbar에서 Workspace만 선택 (Project는 선택하지 않음) +2. 다른 화면으로 이동 (예: MCI Workloads, PMK Workloads 등) +3. **결과**: Project 선택 경고 창이 표시되지 않음 +4. **기대**: Project도 선택하지 않았으므로 경고 창이 표시되어야 함 + +**해결 방법 (제안)**: + +**방법 1**: 기존 함수에 Project 검증 추가 (권장) +```javascript +// workspace와 project selection 여부 확인 function +export function checkWorkspaceSelection(selectedWorkspaceProject) { + if (selectedWorkspaceProject.workspaceId == "") { + commonShowDefaultModal('Workspace Selection Check', 'Please select workspace first'); + return false; + } + if (selectedWorkspaceProject.projectId == "") { + commonShowDefaultModal('Project Selection Check', 'Please select project first'); + return false; + } + return true; +} +``` + +**방법 2**: 함수명 변경 및 로직 추가 (더 명확) +```javascript +// workspace와 project selection 여부 확인 function +export function checkWorkspaceProjectSelection(selectedWorkspaceProject) { + if (selectedWorkspaceProject.workspaceId == "") { + commonShowDefaultModal('Workspace Selection Check', 'Please select workspace first'); + return false; + } + if (selectedWorkspaceProject.projectId == "") { + commonShowDefaultModal('Project Selection Check', 'Please select project first'); + return false; + } + return true; +} +``` +- 함수명을 변경하는 경우 9개 파일의 호출부도 함께 수정 필요 + +**테스트 방법**: +1. 로그인 +2. Workspace 선택 (Project는 비워둠) +3. MCI Workloads 화면으로 이동 +4. "Please select project first" 경고 창이 표시되는지 확인 +5. Project 선택 후 다시 화면 이동 +6. 정상적으로 화면이 로드되는지 확인 + +**추가 정보**: +- navbar.js (라인 14-19)에서 이미 workspace와 project가 비어있으면 `is-invalid` 클래스를 추가하는 UI 표시 로직은 존재함 +- 다만 화면 전환 시 검증하는 `checkWorkspaceSelection` 함수에서 project를 체크하지 않아 사용자가 미선택 상태로 화면을 이동할 수 있음 + +--- diff --git a/doc/fix/004_add_spec_name_column.md b/doc/fix/004_add_spec_name_column.md new file mode 100644 index 00000000..62f45fc0 --- /dev/null +++ b/doc/fix/004_add_spec_name_column.md @@ -0,0 +1,244 @@ +# 004_add_spec_name_column + +## 현상 + +- Server Recommendation 결과 테이블에 spec 이름이 표시되지 않음 +- 사용자가 provider, region, price, memory, vCPU 등은 볼 수 있지만 실제 spec 이름을 알 수 없음 +- Network 탭 확인 결과: + - API 응답에는 `cspSpecName` 필드가 포함되어 있음 (예: "t2.small", "Standard_B2s", "n1-standard-2") + - 하지만 테이블 컬럼에는 표시되지 않음 + +## 문제점 + +### 사용성 문제 + +1. **Spec 식별 어려움** + - t2.small, Standard_B2s 같은 실제 spec 이름을 확인할 수 없어 선택에 어려움 + - 사용자가 익숙한 spec 이름으로 확인할 수 없음 + +2. **정보 불일치** + - 다른 화면에서는 spec 이름이 표시되는데 추천 테이블에서만 누락 + - MCI Create와 PMK NodeGroup에서 동일한 문제 발생 + +3. **선택 후 확인 필요** + - Spec을 선택한 후에야 spec 이름을 확인할 수 있음 + - 선택 전에 어떤 spec인지 미리 알 수 없어 비효율적 + +### 현재 테이블 구조 + +**표시되는 컬럼**: +- PROVIDER (providerName) +- REGION (regionName) +- PRICE (costPerHour) +- MEMORY (memoryGiB) +- VCPU (vCPU) + +**숨겨진 컬럼**: +- connectionName +- evaluationScore10 + +**누락된 정보**: +- **SPEC NAME (cspSpecName)** ← 추가 필요 + +## 해결방법 + +### 구현 방안 + +**SPEC NAME 컬럼을 테이블에 추가** +- `cspSpecName` 필드를 사용하여 CSP의 원본 spec 이름 표시 +- REGION 다음, PRICE 이전에 배치하여 자연스러운 정보 흐름 구성 +- 좌측 정렬하여 텍스트 가독성 향상 +- 정렬 기능 활성화하여 spec 이름으로 정렬 가능 +- 툴팁 기능 활성화하여 긴 spec 이름도 전체 확인 가능 + +### 컬럼 스펙 + +```javascript +{ + title: "SPEC NAME", + field: "cspSpecName", + vertAlign: "middle", + hozAlign: "left", // 텍스트는 좌측 정렬 + minWidth: 150, // 최소 150px 너비 + headerSort: true, // 정렬 가능 + tooltip: true // 툴팁 표시 +} +``` + +### 개선 후 테이블 구조 + +**컬럼 순서**: +1. 체크박스 +2. PROVIDER +3. REGION +4. **SPEC NAME** ← 추가 +5. PRICE +6. MEMORY +7. VCPU + +## 수정내역 + +### 수정 대상 파일 + +#### 1. `front/assets/js/partials/operation/manage/serverrecommendation.js` + +**함수**: `initRecommendSpecTable()` (44-103라인) + +**수정 위치**: columns 배열의 REGION 컬럼 다음에 추가 (80-90라인) + +**수정 내용**: +```javascript +{ + title: "REGION", + field: "regionName", + vertAlign: "middle" +}, +// ===== 추가된 부분 ===== +{ + title: "SPEC NAME", + field: "cspSpecName", + vertAlign: "middle", + hozAlign: "left", + minWidth: 150, + headerSort: true, + tooltip: true +}, +// ==================== +{ + title: "PRICE", + field: "costPerHour", + vertAlign: "middle", + hozAlign: "center", +}, +``` + +#### 2. `front/assets/js/partials/operation/manage/pmk_serverrecommendation.js` + +**함수**: `initRecommendSpecTablePmk()` (43-117라인) + +**수정 위치**: columns 배열의 REGION 컬럼 다음에 추가 (82-92라인) + +**수정 내용**: MCI와 동일한 컬럼 정의 추가 + +### 데이터 소스 확인 + +**API 응답 필드**: `cspSpecName` +- 이미 API 응답에 포함되어 있음 +- `applyServerRecommendInfo()` 함수(456라인)에서 사용 중 +- 별도의 API 수정 불필요 + +**예시 데이터**: +- AWS: "t2.small", "t2.medium", "t2.large" +- Azure: "Standard_B2s", "Standard_D2s_v3" +- GCP: "n1-standard-2", "e2-medium" + +## 예상 효과 + +### 사용성 개선 + +1. **즉각적인 Spec 식별** + - 테이블에서 바로 spec 이름 확인 가능 + - 사용자가 익숙한 spec 이름으로 빠른 선택 가능 + +2. **정보 일관성** + - MCI와 PMK 모두에서 동일한 정보 제공 + - 다른 화면과 일관된 정보 표시 + +3. **선택 정확도 향상** + - 선택 전에 spec을 정확히 확인할 수 있어 실수 방지 + - 여러 spec을 비교할 때 spec 이름으로 쉽게 구별 가능 + +### 화면 개선 + +**변경 전**: +``` +| □ | PROVIDER | REGION | PRICE | MEMORY | VCPU | +|----|----------|---------------|--------|--------|------| +| □ | aws | ap-northeast-2| 0.0464 | 2 | 1 | +``` + +**변경 후**: +``` +| □ | PROVIDER | REGION | SPEC NAME | PRICE | MEMORY | VCPU | +|----|----------|---------------|-----------|--------|--------|------| +| □ | aws | ap-northeast-2| t2.small | 0.0464 | 2 | 1 | +``` + +## 테스트 시나리오 + +### 기능 테스트 + +1. **MCI Create - Server Recommendation** + - [ ] Server Recommendation 모달 열기 + - [ ] 검색 조건 입력 후 조회 + - [ ] SPEC NAME 컬럼이 표시되는가 + - [ ] Spec 이름이 정확하게 표시되는가 (예: t2.small, Standard_B2s) + - [ ] SPEC NAME으로 정렬이 작동하는가 + +2. **PMK NodeGroup - Server Recommendation** + - [ ] Server Recommendation 모달 열기 + - [ ] 검색 조건 입력 후 조회 + - [ ] SPEC NAME 컬럼이 표시되는가 + - [ ] Spec 이름이 정확하게 표시되는가 + - [ ] SPEC NAME으로 정렬이 작동하는가 + +3. **다양한 Provider 테스트** + - [ ] AWS spec 이름 표시 확인 + - [ ] Azure spec 이름 표시 확인 + - [ ] GCP spec 이름 표시 확인 + +4. **UI/UX 테스트** + - [ ] 컬럼 너비가 적절한가 (최소 150px) + - [ ] 긴 spec 이름에 툴팁이 표시되는가 + - [ ] 좌측 정렬이 적용되어 가독성이 좋은가 + - [ ] 정렬 아이콘이 헤더에 표시되는가 + +### 회귀 테스트 + +- [ ] 기존 컬럼들이 정상 표시되는가 +- [ ] Spec 선택 기능이 정상 작동하는가 +- [ ] Apply 버튼 클릭 시 spec 정보가 정확하게 전달되는가 +- [ ] 다른 필터 조건들이 정상 작동하는가 + +## 관련 이슈 + +- Image Recommendation 필터 수정 (provider/region 값 전달 문제)과 연계 +- Server Recommendation UI/UX 개선 작업의 일환 + +## 적용 브랜치 + +- **브랜치명**: `fix_050` +- **기반 브랜치**: `develop` +- **관련 커밋**: Add SPEC NAME column to server recommendation tables + +## 코딩 스타일 준수 + +- ✅ 들여쓰기: 2칸 +- ✅ 따옴표: 작은따옴표(') 사용 +- ✅ 세미콜론: 모든 구문 끝에 사용 +- ✅ 변수명: camelCase +- ✅ 주석: 슬래시 두 개(//) 사용 + +## 참고사항 + +### 향후 개선 가능 사항 + +1. **추가 정보 표시** + - Common Spec ID 표시 (디버깅용) + - OS Architecture 정보 표시 + - Storage Type 정보 표시 + +2. **필터 기능 강화** + - Spec 이름으로 검색 기능 추가 + - Spec 패밀리별 필터링 (예: t2 시리즈, Standard_D 시리즈) + +3. **즐겨찾기 기능** + - 자주 사용하는 spec을 즐겨찾기로 저장 + - 즐겨찾기한 spec 우선 표시 + +### 관련 문서 + +- [001_fix_api_error_toast.md](./001_fix_api_error_toast.md) +- [002_fix_workspace_project_mapping.md](./002_fix_workspace_project_mapping.md) +- [003_fix_multiple_api_calls.md](./003_fix_multiple_api_calls.md) + diff --git a/doc/fix/005_fix_postk8snodegroup_validation.md b/doc/fix/005_fix_postk8snodegroup_validation.md new file mode 100644 index 00000000..eb9bce0b --- /dev/null +++ b/doc/fix/005_fix_postk8snodegroup_validation.md @@ -0,0 +1,345 @@ +# Fix 005: Postk8snodegroup Validation 및 sshKeyId 처리 개선 + +## 개요 + +**작성일**: 2025-11-09 +**버그 ID**: 005 +**심각도**: Medium +**상태**: Fixed + +## 문제 발견 + +PMK(Platform Managed Kubernetes) 페이지에서 Node Group 추가 시 다음 문제들이 발견됨: + +1. **Validation Check 누락**: `addNodeFormDone_btn()` 함수에 필수 필드 검증이 없어 빈 값으로 API 호출 가능 +2. **sshKeyId 처리 불안정**: sshKeyId가 빈 값 또는 undefined로 전송될 수 있음 +3. **에러 처리 없음**: API 호출 후 response 처리 및 에러 핸들링이 없음 + +## 문제 분석 + +### 1. Validation Check 누락 + +**파일**: `front/assets/js/partials/operation/manage/clustercreate.js` + +#### 문제 코드 (수정 전) + +```javascript +export function addNodeFormDone_btn() { + $("#n_name").val($("#node_name").val()) + $("#n_specid").val($("#node_specid").val()) + $("#n_imageid").val($("#node_imageid").val()) + // ... 필드 validation 없이 바로 처리 +} +``` + +**문제점**: +- 필수 필드(name, specId, imageId, sshKeyId, autoscaling) 검증 없음 +- 사용자가 빈 값으로 Submit 가능 +- 같은 파일의 `clusterFormDone_btn()` 함수에는 validation이 구현되어 있음 (일관성 없음) + +### 2. sshKeyId 처리 불안정 + +**파일**: `front/assets/js/common/api/services/pmk_api.js` + +#### 문제 코드 (수정 전) + +```javascript +export async function createNode(k8sClusterId, nsId, Create_Node_Config_Arr) { + var obj = {} + obj = Create_Node_Config_Arr[0] + const data = { + request: { + "sshKeyId": obj.sshKeyId // undefined 또는 빈 값 가능 + } + } + + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ) + // response 처리 없음 +} +``` + +**문제점**: +- `obj.sshKeyId`가 undefined 또는 빈 문자열일 수 있음 +- 필수 필드 검증 없음 +- API 호출 후 response 처리 없음 +- try-catch 블록 없어 에러 발생 시 처리 불가 + +## 수정 내용 + +### 1. addNodeFormDone_btn() 함수 개선 + +**파일**: `front/assets/js/partials/operation/manage/clustercreate.js` (라인 761-789) + +#### 수정 후 코드 + +```javascript +export function addNodeFormDone_btn() { + // 1. 필수 필드 검증 + var requiredFields = [ + { id: '#node_name', message: 'NodeGroup name is required' }, + { id: '#node_specid', message: 'Spec is required' }, + { id: '#node_imageid', message: 'Image is required' }, + { id: '#node_sshkey', message: 'SSH Key is required' }, + { id: '#node_autoscaling', message: 'AutoScaling option is required' }, + { id: '#node_minnodesize', message: 'Min Node Size is required' }, + { id: '#node_maxnodesize', message: 'Max Node Size is required' } + ]; + + for (var field of requiredFields) { + if (!$(field.id).val() || $(field.id).val().trim() === '') { + alert(field.message); + $(field.id).focus(); + return; + } + } + + // 2. hidden 필드에 값 설정 + $("#n_name").val($("#node_name").val()) + // ... 나머지 처리 +} +``` + +**개선 사항**: +- 필수 필드 7개에 대한 validation 추가 + - name: NodeGroup 이름 + - specId: 서버 스펙 + - imageId: 이미지 + - sshKeyId: SSH Key + - autoscaling: AutoScaling 설정 + - minNodeSize: 최소 노드 수 + - maxNodeSize: 최대 노드 수 +- validation 실패 시 alert 표시 및 해당 필드로 focus +- validation 통과 후에만 처리 진행 +- `clusterFormDone_btn()` 함수와 일관성 유지 + +### 2. createNode() 함수 개선 + +**파일**: `front/assets/js/common/api/services/pmk_api.js` (라인 347-405) + +#### 수정 후 코드 + +```javascript +export async function createNode(k8sClusterId, nsId, Create_Node_Config_Arr) { + // 1. 배열 검증 + if (!Create_Node_Config_Arr || Create_Node_Config_Arr.length === 0) { + console.error('No node configuration provided'); + webconsolejs["common/util"].showToast('No node configuration to create', 'error'); + return; + } + + var obj = Create_Node_Config_Arr[0]; + + // 2. 필수 필드 검증 + if (!obj.name || !obj.specId || !obj.imageId || !obj.sshKeyId || !obj.minNodeSize || !obj.maxNodeSize || !obj.onAutoScaling) { + console.error('Missing required fields:', obj); + webconsolejs["common/util"].showToast('Missing required fields for node creation', 'error'); + return; + } + + // 3. 데이터 준비 (기본값 포함) + const data = { + pathParams: { + nsId: nsId, + k8sClusterId: k8sClusterId, + }, + request: { + "desiredNodeSize": obj.desiredNodeSize || "1", + "imageId": obj.imageId, + "maxNodeSize": obj.maxNodeSize || obj.desiredNodeSize || "1", + "minNodeSize": obj.minNodeSize || obj.desiredNodeSize || "1", + "name": obj.name, + "onAutoScaling": obj.onAutoScaling || "false", + "rootDiskSize": obj.rootDiskSize || "", + "rootDiskType": obj.rootDiskType || "", + "specId": obj.specId, + "sshKeyId": obj.sshKeyId + } + }; + + var controller = "/api/" + "mc-infra-manager/" + "Postk8snodegroup"; + + // 4. API 호출 및 에러 처리 + try { + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); + + // 성공 처리 + if (response && response.status === 200) { + webconsolejs["common/util"].showToast('Node group creation request completed successfully', 'success'); + return response; + } else { + console.error('Node creation failed:', response); + webconsolejs["common/util"].showToast('Failed to create node group', 'error'); + return response; + } + } catch (error) { + console.error('Error creating node:', error); + webconsolejs["common/util"].showToast('Error creating node group: ' + (error.message || 'Unknown error'), 'error'); + throw error; + } +} +``` + +**개선 사항**: +- 입력 배열 존재 여부 검증 +- 필수 필드 7개(name, specId, imageId, sshKeyId, minNodeSize, maxNodeSize, onAutoScaling) 검증 +- 기본값 설정 추가: + - desiredNodeSize: "1" + - onAutoScaling: "false" + - minNodeSize, maxNodeSize: desiredNodeSize 값 또는 "1" +- try-catch 블록으로 에러 핸들링 +- **alert() 대신 Toast 메시지 사용으로 UX 개선** +- 성공 시 success Toast, 실패 시 error Toast 표시 +- response 객체 반환하여 호출자가 추가 처리 가능 +- commonAPIPost가 자동으로 pageloader 관리 (activePageLoader/deactivePageLoader) + +## 테스트 방법 + +### 1. Validation 테스트 + +1. PMK 페이지에서 기존 클러스터 선택 +2. "Add Node Group" 버튼 클릭 +3. **필수 필드 누락 테스트**: + - NodeGroup name만 입력하고 Done 클릭 + - → "Spec is required" alert 확인 + - Spec 선택 후 Done 클릭 + - → "Image is required" alert 확인 + - Image 선택 후 Done 클릭 + - → "SSH Key is required" alert 확인 + - SSH Key 선택 후 Done 클릭 + - → "AutoScaling option is required" alert 확인 + - AutoScaling 선택 후 Done 클릭 + - → "Min Node Size is required" alert 확인 + - Min Node Size 입력 후 Done 클릭 + - → "Max Node Size is required" alert 확인 + +### 2. 정상 동작 테스트 + +1. 모든 필수 필드 입력: + - NodeGroup name: "test-nodegroup" + - Spec: 추천 spec 선택 + - Image: 추천 image 선택 + - SSH Key: 목록에서 선택 + - AutoScaling: true/false 선택 + - Min Node Size: "1" 입력 + - Max Node Size: "3" 입력 +2. Done 버튼 클릭 +3. → 폼이 닫히고 Node Group 목록에 추가됨 확인 + +### 3. API 요청 검증 + +1. 브라우저 개발자 도구 → Network 탭 열기 +2. Add Node Group 수행 +3. Deploy Node 버튼 클릭 +4. "Postk8snodegroup" API 호출 확인 +5. Request Payload 확인: + ```json + { + "pathParams": { + "nsId": "...", + "k8sClusterId": "..." + }, + "request": { + "name": "test-nodegroup", + "specId": "...", + "imageId": "...", + "sshKeyId": "...", // 빈 값이 아닌지 확인 + "desiredNodeSize": "1", + "minNodeSize": "1", + "maxNodeSize": "1", + "onAutoScaling": "false", + "rootDiskSize": "", + "rootDiskType": "" + } + } + ``` + +### 4. 에러 처리 테스트 + +1. 잘못된 데이터로 API 호출 (콘솔에서 수동 테스트) +2. → "Error creating node group" Toast 메시지 확인 (우측 상단) +3. Console에 에러 로그 출력 확인 +4. "Preparing Data" 로딩 인디케이터가 자동으로 사라지는지 확인 + +### 5. Toast 메시지 확인 + +1. 성공 시: 녹색 Toast "Node group creation request completed successfully" 표시 +2. Validation 실패 시: 빨간색 Toast "Missing required fields for node creation" 표시 +3. API 에러 시: 빨간색 Toast "Failed to create node group" 표시 +4. Toast는 5초 후 자동으로 사라짐 + +## 영향 범위 + +### 수정된 파일 + +1. `front/assets/js/partials/operation/manage/clustercreate.js` + - `addNodeFormDone_btn()` 함수 수정 +2. `front/assets/js/common/api/services/pmk_api.js` + - `createNode()` 함수 수정 + +### 영향받는 기능 + +- PMK 페이지에서 Node Group 추가 기능 +- Node Group 생성 API 호출 + +### 하위 호환성 + +- API 인터페이스 변경 없음 +- 기존 코드와 호환됨 +- 추가된 validation으로 인해 사용자는 필수 필드를 반드시 입력해야 함 (개선) + +## 추가 개선 사항 (2025-11-09 추가) + +### alert()를 Toast로 변경 + +**문제**: +- 기존 `alert()` 사용으로 인해 사용자 경험 저하 +- 에러 발생 시 "Preparing Data" 로딩 메시지가 사라지지 않음 +- 모달 alert는 브라우저를 블로킹하여 비동기 작업 흐름 방해 + +**해결**: +- `alert()` → `webconsolejs["common/util"].showToast()` 로 변경 +- Toast 메시지는 비침투적(non-blocking)으로 우측 상단에 표시 +- commonAPIPost의 내장 pageloader 관리 활용 +- 성공/실패에 따라 적절한 Toast 타입 사용 (success/error) + +**변경 내역**: +```javascript +// 수정 전 +alert('Missing required fields for node creation'); + +// 수정 후 +webconsolejs["common/util"].showToast('Missing required fields for node creation', 'error'); +``` + +### Loading State 관리 + +`commonAPIPost()` 함수가 자동으로 처리: +- API 호출 전: `activePageLoader()` - "Preparing Data" 표시 +- API 호출 후: `deactivePageLoader()` - 로딩 메시지 제거 +- 에러 발생 시에도 자동으로 `deactivePageLoader()` 호출 + +## 추가 개선 사항 + +### 향후 고려 사항 + +1. **서버 사이드 Validation**: 백엔드 API에서도 필수 필드 검증 구현 +2. **Toast 메시지**: alert 대신 toast 메시지로 사용자 경험 개선 +3. **입력 필드 힌트**: placeholder 또는 tooltip으로 필수 필드 표시 +4. **Form Validation 라이브러리**: 통일된 validation 로직을 위한 라이브러리 도입 고려 + +## 관련 문서 + +- API 문서: `conf/api.yaml` - Postk8snodegroup +- 이전 수정: `doc/fix/004_add_spec_name_column.md` + +## 작성자 + +- AI Assistant +- 검토자: [검토자명 추가 필요] + diff --git a/doc/fix/006_fix_pmk_delete_validation.md b/doc/fix/006_fix_pmk_delete_validation.md new file mode 100644 index 00000000..9487f8fd --- /dev/null +++ b/doc/fix/006_fix_pmk_delete_validation.md @@ -0,0 +1,662 @@ +# Fix 006: PMK Delete 및 NodeGroup Delete Validation 추가 + +## 개요 + +**작성일**: 2025-11-09 +**버그 ID**: 006 +**심각도**: High +**상태**: Fixed + +## 문제 발견 + +PMK(Platform Managed Kubernetes) 페이지에서 Delete 버튼 클릭 시 다음 문제들이 발견됨: + +1. **Validation Check 누락**: PMK를 선택하지 않고 Delete 버튼 클릭 시 빈 clusterId로 API 호출 +2. **API 파라미터 검증 없음**: `Deletek8scluster` API 호출 시 필수 파라미터(nsId, k8sClusterId) 검증 없음 +3. **NodeGroup Delete도 동일 문제**: NodeGroup 삭제 시에도 validation 없음 + +## 문제 분석 + +### 1. deletePmk() 함수 - Validation 누락 + +**파일**: `front/assets/js/pages/operation/manage/pmk.js` + +#### 문제 코드 (라인 194-198) + +```javascript +export function deletePmk() { + + var selectedNsId = selectedWorkspaceProject.nsId; + webconsolejs["common/api/services/pmk_api"].pmkDelete(selectedNsId, currentPmkId) +} +``` + +**문제점**: +- `currentPmkId`가 빈 문자열("")인 상태에서도 API 호출 가능 +- PMK를 선택하지 않아도 Delete 버튼 클릭 시 동작 +- `selectedNsId`가 빈 값이어도 검증 없이 API 호출 +- 사용자에게 어떤 피드백도 제공하지 않음 + +#### 변수 초기값 (라인 29) + +```javascript +var currentPmkId = ""; +``` + +- `currentPmkId`는 초기값이 빈 문자열 +- PMK를 선택하기 전까지는 빈 값 유지 +- rowClick 이벤트에서만 값 설정됨 (라인 747) + +### 2. deleteNodeGroup() 함수 - 동일 문제 + +**파일**: `front/assets/js/pages/operation/manage/pmk.js` + +#### 문제 코드 (라인 201-206) + +```javascript +export function deleteNodeGroup() { + + var selectedNsId = selectedWorkspaceProject.nsId; + webconsolejs["common/api/services/pmk_api"].nodeGroupDelete(selectedNsId, currentPmkId, currentNodeGroupName) + +} +``` + +**문제점**: +- `currentNodeGroupName`이 빈 값이어도 API 호출 +- `currentPmkId`가 없어도 API 호출 +- validation 전혀 없음 + +### 3. API 레벨 검증 부재 + +**파일**: `front/assets/js/common/api/services/pmk_api.js` + +#### pmkDelete() 함수 (라인 618-630) + +```javascript +export function pmkDelete(nsId, k8sClusterId) { + let data = { + pathParams: { + nsId: nsId, + k8sClusterId: k8sClusterId, + }, + }; + let controller = "/api/" + "mc-infra-manager/" + "Deletek8scluster"; + let response = webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); +} +``` + +**문제점**: +- 파라미터 검증 없이 바로 API 호출 +- 빈 값이 전달되어도 API 요청 진행 +- response 처리 없음 +- 에러 핸들링 없음 + +#### nodeGroupDelete() 함수 (라인 632-646) + +```javascript +export function nodeGroupDelete(nsId, k8sClusterId, k8sNodeGroupName) { + + let data = { + pathParams: { + nsId: nsId, + k8sClusterId: k8sClusterId, + k8sNodeGroupName: k8sNodeGroupName + }, + }; + let controller = "/api/" + "mc-infra-manager/" + "Deletek8snodegroup"; + let response = webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); +} +``` + +**문제점**: +- 필수 파라미터 3개 모두 검증 없음 +- 에러 처리 없음 + +### 4. 비교: 다른 함수는 Validation 있음 + +**파일**: `front/assets/js/common/api/services/pmk_api.js` + +#### getCluster() 함수 (라인 27-47) - 정상 예시 + +```javascript +export async function getCluster(nsId, clusterId) { + if (nsId == "" || nsId == undefined || clusterId == undefined || clusterId == "") { + alert(" undefined nsId: " + nsId + " clusterId " + clusterId); + return; + } + // ... API 호출 +} +``` + +**특징**: +- 필수 파라미터 검증 있음 +- undefined와 빈 문자열 모두 체크 + +## 재현 방법 + +### 시나리오 1: PMK 선택 없이 삭제 시도 + +1. PMK Workloads 페이지 접속 +2. PMK 목록에서 **아무것도 선택하지 않음** +3. 우측 상단 Action 드롭다운 → Delete 클릭 +4. 확인 모달에서 OK 클릭 +5. **결과**: + - API 호출: `/api/mc-infra-manager/Deletek8scluster` + - Request Payload: + ```json + { + "pathParams": { + "nsId": "ns01", + "k8sClusterId": "" // ← 빈 문자열 + } + } + ``` + - 서버 에러 발생 또는 잘못된 동작 + +### 시나리오 2: Workspace/Project 미선택 상태 + +1. 로그인 직후 (Workspace/Project 선택 안 한 상태) +2. PMK Workloads 페이지 접속 +3. PMK 선택 후 Delete 클릭 +4. **결과**: + - `selectedNsId`가 undefined 또는 빈 값 + - 잘못된 API 호출 + +## 수정 내용 + +### 기존 Modal 시스템 활용 + +프로젝트에서 Workspace 미선택 시 사용하는 `commonShowDefaultModal` 방식 적용: + +**파일**: `front/assets/js/partials/layout/modal.js` (라인 47-54, 65-69) + +```javascript +// default modal show +export function commonShowDefaultModal(title, content) { + const modalId = 'commonDefaultModal'; + const modal = new bootstrap.Modal(document.getElementById(modalId)); + document.getElementById(`${modalId}-title`).innerText = title; + document.getElementById(`${modalId}-content`).innerText = content; + document.getElementById(`${modalId}-confirm-btn`).onclick = modalHide('commonDefaultModal') + modal.show(); +} + +// workspace selection 여부 확인 function +export function checkWorkspaceSelection(selectedWorkspaceProject) { + if (selectedWorkspaceProject.workspaceId == "") { + commonShowDefaultModal('Workspace Selection Check', 'Please select workspace first') + } +} +``` + +### 1. deletePmk() 함수 개선 + +**파일**: `front/assets/js/pages/operation/manage/pmk.js` (라인 194-198) + +#### 수정 후 코드 + +```javascript +// pmk 삭제 +export function deletePmk() { + // Validation 1: PMK가 선택되었는지 확인 + if (!currentPmkId || currentPmkId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'PMK Selection Check', + 'Please select a PMK to delete.' + ); + return; + } + + // Validation 2: Workspace/Project가 선택되었는지 확인 + var selectedNsId = selectedWorkspaceProject.nsId; + if (!selectedNsId || selectedNsId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Workspace Selection Check', + 'Please select a workspace and project first.' + ); + return; + } + + // Validation 통과 후 API 호출 + webconsolejs['common/api/services/pmk_api'].pmkDelete(selectedNsId, currentPmkId); +} +``` + +**개선 사항**: +- PMK 선택 여부 검증 추가 +- Workspace/Project 선택 여부 검증 추가 +- 기존 `checkWorkspaceSelection`과 동일한 모달 스타일 사용 +- `commonDefaultModal` 사용으로 일관된 UI 제공 +- 모달은 중앙에 표시되며 Cancel/Confirm 버튼 제공 + +### 2. deleteNodeGroup() 함수 개선 + +**파일**: `front/assets/js/pages/operation/manage/pmk.js` (라인 201-206) + +#### 수정 후 코드 + +```javascript +// nodegroup 삭제 +export function deleteNodeGroup() { + // Validation 1: NodeGroup이 선택되었는지 확인 + if (!currentNodeGroupName || currentNodeGroupName === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'NodeGroup Selection Check', + 'Please select a NodeGroup to delete.' + ); + return; + } + + // Validation 2: PMK가 선택되었는지 확인 + if (!currentPmkId || currentPmkId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'PMK Selection Check', + 'Please select a PMK first.' + ); + return; + } + + // Validation 3: Workspace/Project가 선택되었는지 확인 + var selectedNsId = selectedWorkspaceProject.nsId; + if (!selectedNsId || selectedNsId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Workspace Selection Check', + 'Please select a workspace and project first.' + ); + return; + } + + // Validation 통과 후 API 호출 + webconsolejs['common/api/services/pmk_api'].nodeGroupDelete( + selectedNsId, + currentPmkId, + currentNodeGroupName + ); +} +``` + +**개선 사항**: +- NodeGroup 선택 여부 검증 +- PMK 선택 여부 검증 +- Workspace/Project 선택 여부 검증 +- 3단계 검증으로 안전성 강화 +- 모달로 명확한 피드백 + +### 3. pmkDelete() API 함수 개선 + +**파일**: `front/assets/js/common/api/services/pmk_api.js` (라인 618-630) + +#### 수정 후 코드 + +```javascript +export function pmkDelete(nsId, k8sClusterId) { + // API 레벨 Validation (추가 안전장치) + if (!nsId || nsId === '' || !k8sClusterId || k8sClusterId === '') { + console.error('Invalid parameters for PMK deletion:', { + nsId: nsId, + k8sClusterId: k8sClusterId + }); + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Invalid Parameters', + 'Invalid parameters for PMK deletion. Please try again.' + ); + return; + } + + let data = { + pathParams: { + nsId: nsId, + k8sClusterId: k8sClusterId, + }, + }; + let controller = '/api/' + 'mc-infra-manager/' + 'Deletek8scluster'; + let response = webconsolejs['common/api/http'].commonAPIPost( + controller, + data + ); + return response; +} +``` + +**개선 사항**: +- API 레벨에서 추가 검증 (방어적 프로그래밍) +- 필수 파라미터 2개 모두 검증 +- undefined와 빈 문자열 모두 체크 +- console.error로 디버깅 정보 제공 +- 모달로 사용자 피드백 +- response 반환하여 호출자가 처리 가능 + +### 4. nodeGroupDelete() API 함수 개선 + +**파일**: `front/assets/js/common/api/services/pmk_api.js` (라인 632-646) + +#### 수정 후 코드 + +```javascript +export function nodeGroupDelete(nsId, k8sClusterId, k8sNodeGroupName) { + // API 레벨 Validation (추가 안전장치) + if (!nsId || nsId === '' || + !k8sClusterId || k8sClusterId === '' || + !k8sNodeGroupName || k8sNodeGroupName === '') { + console.error('Invalid parameters for NodeGroup deletion:', { + nsId: nsId, + k8sClusterId: k8sClusterId, + k8sNodeGroupName: k8sNodeGroupName + }); + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Invalid Parameters', + 'Invalid parameters for NodeGroup deletion. Please try again.' + ); + return; + } + + let data = { + pathParams: { + nsId: nsId, + k8sClusterId: k8sClusterId, + k8sNodeGroupName: k8sNodeGroupName + }, + }; + let controller = '/api/' + 'mc-infra-manager/' + 'Deletek8snodegroup'; + let response = webconsolejs['common/api/http'].commonAPIPost( + controller, + data + ); + return response; +} +``` + +**개선 사항**: +- 필수 파라미터 3개 모두 검증 +- 상세한 에러 로그 +- 모달로 사용자 피드백 +- response 반환 + +## Modal 시스템 사용 + +### commonDefaultModal 개요 + +**템플릿**: `front/templates/partials/layout/_modal.html` + +```html + + +``` + +### Modal 특성 + +| 특성 | 설명 | +|------|------| +| 위치 | 화면 중앙 (modal-dialog-centered) | +| 크기 | Small (modal-sm) | +| 스타일 | 블러 배경 (modal-blur) | +| 버튼 | Cancel (왼쪽), Confirm (오른쪽 파란색) | +| 차단 | Blocking (모달 닫기 전까지 다른 작업 불가) | +| 닫기 | ESC 키, Cancel 버튼, Confirm 버튼, 배경 클릭 | + +### 사용 방법 + +```javascript +webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Title Text', // 모달 제목 + 'Content Text' // 모달 내용 +); +``` + +### 기존 사용 예시 + +**Workspace 선택 체크**: +```javascript +export function checkWorkspaceSelection(selectedWorkspaceProject) { + if (selectedWorkspaceProject.workspaceId == "") { + commonShowDefaultModal('Workspace Selection Check', 'Please select workspace first') + } +} +``` + +**사용 위치**: +- `front/assets/js/pages/operation/manage/pmk.js` (라인 81) +- `front/assets/js/pages/operation/manage/mci.js` (라인 64) +- `front/assets/js/pages/operation/manage/monitoring.js` (라인 38) +- 기타 10개 이상 파일에서 사용 중 + +## 테스트 방법 + +### 1. PMK 삭제 Validation 테스트 + +#### Test Case 1-1: PMK 미선택 상태에서 삭제 시도 + +1. PMK Workloads 페이지 접속 +2. PMK를 **선택하지 않은 상태**에서 Delete 버튼 클릭 +3. **예상 결과**: + - ❌ API 호출 안 됨 + - ✅ 화면 중앙에 모달 표시 + - ✅ 제목: "PMK Selection Check" + - ✅ 내용: "Please select a PMK to delete." + - ✅ Cancel / Confirm 버튼 + - ✅ Network 탭에 API 호출 기록 없음 + +#### Test Case 1-2: Workspace 미선택 상태 + +1. 로그인 직후 (Workspace/Project 선택 전) +2. PMK Workloads 페이지 접속 +3. PMK 선택 후 Delete 버튼 클릭 +4. **예상 결과**: + - ❌ API 호출 안 됨 + - ✅ 모달 제목: "Workspace Selection Check" + - ✅ 모달 내용: "Please select a workspace and project first." + +#### Test Case 1-3: 정상 삭제 + +1. Workspace/Project 선택 +2. PMK 선택 (테이블 행 클릭) +3. Delete 버튼 클릭 +4. 확인 모달에서 OK 클릭 +5. **예상 결과**: + - ✅ API 호출: `/api/mc-infra-manager/Deletek8scluster` + - ✅ Request Payload에 올바른 nsId, k8sClusterId 포함 + - ✅ "Preparing Data" 로딩 표시 + - ✅ 성공 시 PMK 목록 새로고침 + +### 2. NodeGroup 삭제 Validation 테스트 + +#### Test Case 2-1: NodeGroup 미선택 상태 + +1. PMK 선택 +2. NodeGroup을 **선택하지 않고** Delete NodeGroup 버튼 클릭 +3. **예상 결과**: + - ❌ API 호출 안 됨 + - ✅ 모달 제목: "NodeGroup Selection Check" + - ✅ 모달 내용: "Please select a NodeGroup to delete." + +#### Test Case 2-2: PMK 미선택 상태 + +1. PMK를 선택하지 않음 +2. Delete NodeGroup 버튼 클릭 +3. **예상 결과**: + - ❌ API 호출 안 됨 + - ✅ 모달 제목: "PMK Selection Check" + - ✅ 모달 내용: "Please select a PMK first." + +#### Test Case 2-3: 정상 삭제 + +1. PMK 선택 +2. NodeGroup 선택 +3. Delete NodeGroup 버튼 클릭 +4. **예상 결과**: + - ✅ API 호출: `/api/mc-infra-manager/Deletek8snodegroup` + - ✅ Request Payload에 nsId, k8sClusterId, k8sNodeGroupName 포함 + +### 3. API 레벨 Validation 테스트 + +#### Test Case 3-1: 빈 파라미터로 직접 호출 (Console 테스트) + +```javascript +// 브라우저 Console에서 실행 +webconsolejs['common/api/services/pmk_api'].pmkDelete('', ''); +``` + +**예상 결과**: +- ❌ API 호출 안 됨 +- ✅ Console에 에러 로그: "Invalid parameters for PMK deletion" +- ✅ 모달 제목: "Invalid Parameters" +- ✅ 모달 내용: "Invalid parameters for PMK deletion. Please try again." + +#### Test Case 3-2: undefined 파라미터 + +```javascript +webconsolejs['common/api/services/pmk_api'].pmkDelete(undefined, undefined); +``` + +**예상 결과**: +- ❌ API 호출 안 됨 +- ✅ validation에서 차단 + +### 4. Modal UI 테스트 + +#### Test Case 4-1: Modal 위치 확인 + +1. validation 에러 발생시키기 +2. **확인 사항**: + - ✅ Modal이 화면 중앙에 표시 + - ✅ 배경이 블러 처리됨 + - ✅ Modal 크기가 Small (modal-sm) + +#### Test Case 4-2: Modal 닫기 + +1. Modal 표시된 상태에서: + - Cancel 버튼 클릭 → 닫힘 + - Confirm 버튼 클릭 → 닫힘 + - ESC 키 → 닫힘 + - 배경 클릭 → 닫힘 + +#### Test Case 4-3: Modal Blocking + +1. Modal 표시된 상태 +2. **확인 사항**: + - ✅ Modal 뒤의 UI 클릭 안 됨 + - ✅ Modal 닫기 전까지 다른 작업 불가 + +### 5. 브라우저 Console 확인 + +#### 정상 동작 시 + +- ✅ 에러 로그 없음 +- ✅ API 요청 성공 로그 + +#### Validation 실패 시 + +``` +Invalid parameters for PMK deletion: {nsId: "", k8sClusterId: ""} +``` + +## 영향 범위 + +### 수정된 파일 + +1. **front/assets/js/pages/operation/manage/pmk.js** + - `deletePmk()` 함수 (194-198라인) → validation 추가 + - `deleteNodeGroup()` 함수 (201-206라인) → validation 추가 + +2. **front/assets/js/common/api/services/pmk_api.js** + - `pmkDelete()` 함수 (618-630라인) → API 레벨 validation 추가 + - `nodeGroupDelete()` 함수 (632-646라인) → API 레벨 validation 추가 + +### 영향받는 기능 + +- PMK 삭제 기능 +- NodeGroup 삭제 기능 +- 관련 모달 및 확인 다이얼로그 + +### 하위 호환성 + +- ✅ API 인터페이스 변경 없음 +- ✅ 기존 코드와 완벽 호환 +- ✅ 기존 `commonDefaultModal` 활용으로 UI 일관성 유지 +- ✅ 추가된 validation은 선택 사항이 아닌 필수사항으로 개선 + +## 예상 효과 + +### 1. 안정성 향상 + +- 잘못된 API 호출 방지 +- 서버 에러 감소 +- 데이터 무결성 보장 + +### 2. 사용자 경험 개선 + +- 명확한 에러 메시지 제공 +- 기존 Modal 시스템과 일관된 UI +- Workspace 선택 체크와 동일한 스타일 + +### 3. 디버깅 개선 + +- Console에 상세한 에러 로그 출력 +- 문제 원인 파악 용이 + +### 4. UI 일관성 + +- 기존 `checkWorkspaceSelection`과 동일한 방식 +- 프로젝트 전반에 걸쳐 10개 이상 파일에서 사용 중인 검증된 방식 +- 사용자에게 익숙한 인터페이스 + +## 추가 개선 사항 + +### 향후 고려 사항 + +1. **Delete 버튼 비활성화** + - PMK 미선택 시 Delete 버튼 비활성화 + - UI 레벨에서 사전 차단 + +2. **확인 모달 개선** + - 삭제할 PMK 이름 표시 + - "정말 삭제하시겠습니까?" 명확한 문구 + +3. **서버 사이드 Validation** + - 백엔드에서도 필수 파라미터 검증 + - 이중 안전장치 + +4. **Bulk Delete 지원** + - 여러 PMK 동시 삭제 시 validation + - 실패한 항목에 대한 모달 메시지 + +## 관련 문서 + +- `doc/fix/005_fix_postk8snodegroup_validation.md` - NodeGroup 생성 validation +- API 문서: `conf/api.yaml` - Deletek8scluster, Deletek8snodegroup +- Modal 템플릿: `front/templates/partials/layout/_modal.html` + +## 코딩 스타일 준수 + +- ✅ 들여쓰기: 2칸 +- ✅ 따옴표: 작은따옴표(') 사용 +- ✅ 세미콜론: 모든 구문 끝에 사용 +- ✅ 변수명: camelCase +- ✅ 함수명: camelCase +- ✅ 주석: 슬래시 두 개(//) 사용 + +## 작성자 + +- AI Assistant +- 검토자: [검토자명 추가 필요] + diff --git a/doc/fix/007_remove_os_architecture_readonly.md b/doc/fix/007_remove_os_architecture_readonly.md new file mode 100644 index 00000000..17129dc0 --- /dev/null +++ b/doc/fix/007_remove_os_architecture_readonly.md @@ -0,0 +1,432 @@ +# Fix 007: OS Architecture 필드 readonly 속성 제거 + +## 개요 + +**작성일**: 2025-11-09 +**버그 ID**: 007 +**심각도**: Medium +**상태**: Fixed + +## 문제 발견 + +Image Recommendation 모달에서 OS Architecture 필드가 readonly로 설정되어 있어 사용자가 수정할 수 없음: + +1. **수정 불가**: OS Architecture 값이 하드코딩된 기본값 "x86_64"로 설정 +2. **ARM 지원 제한**: arm64, aarch64 등 다른 아키텍처 선택 불가 +3. **사용자 경험 저하**: Spec 정보에서 값을 가져오지만 수정 필요 시 불가능 + +## 문제 분석 + +### 1. OS Architecture 값 설정 메커니즘 + +**파일**: `front/assets/js/partials/operation/manage/imagerecommendation.js` + +#### 값 결정 순서 (라인 188) + +```javascript +var osArchitecture = $("#image-os-architecture").val() || window.selectedSpecInfo.osArchitecture || "x86_64"; +``` + +**동작 방식**: +1. UI 필드 값 확인 (`$("#image-os-architecture").val()`) +2. 선택된 Spec 정보에서 가져오기 (`window.selectedSpecInfo.osArchitecture`) +3. 기본값 사용 (`"x86_64"` - 하드코딩) + +#### 모달 열릴 때 필드 설정 (라인 385-390) + +```javascript +// Spec Information 필드 채우기 (모달 열기 전) +if (window.selectedSpecInfo) { + $("#image-provider").val(window.selectedSpecInfo.provider || ""); + $("#image-region").val(window.selectedSpecInfo.regionName || ""); + $("#image-os-architecture").val(window.selectedSpecInfo.osArchitecture || "x86_64"); +} +``` + +**문제점**: +- Spec에 `osArchitecture` 정보가 있으면 자동 설정 +- 없으면 기본값 "x86_64" 설정 +- 하지만 HTML에서 readonly로 설정되어 있어 수정 불가 + +### 2. HTML readonly 속성 + +**MCI용 파일**: `front/templates/partials/operation/manage/_imagerecommendation.html` + +#### 문제 코드 (라인 46-55) + +```html +
+ + +
+``` + +**PMK용 파일**: `front/templates/partials/operation/manage/_pmk_imagerecommendation.html` + +#### 문제 코드 (라인 45-54) + +```html +
+ + +
+``` + +**문제점**: +- `readonly` 속성으로 인해 사용자가 값을 변경할 수 없음 +- Spec에서 자동으로 값을 가져오는 것은 좋지만, 수정이 필요한 경우 대응 불가 +- Provider와 Region은 Spec에 종속적이지만, OS Architecture는 사용자가 선택할 수 있어야 함 + +### 3. 비교: Provider와 Region 필드 + +**동일하게 readonly 설정**: +```html + + + + + +``` + +**차이점**: +- Provider와 Region: Spec 선택에 따라 결정되므로 readonly가 적절 +- **OS Architecture**: 동일한 Spec에서도 x86_64와 arm64를 모두 지원할 수 있으므로 사용자 선택 필요 + +## 재현 방법 + +### 시나리오 1: OS Architecture 수정 시도 + +1. MCI Workloads 페이지 접속 +2. MCI Create → Server Spec 선택 +3. Image Recommendation 모달 열기 +4. OS Architecture 필드 클릭하여 수정 시도 +5. **결과**: + - ❌ 입력 불가 (readonly) + - OS Architecture 값이 "x86_64"로 고정 + - arm64를 사용하려는 경우 수정 불가능 + +### 시나리오 2: ARM 아키텍처 이미지 검색 + +1. PMK Workloads 페이지 접속 +2. NodeGroup 추가 → Server Spec 선택 +3. Image Recommendation 모달 열기 +4. OS Architecture를 "arm64"로 변경하려고 시도 +5. **결과**: + - ❌ 수정 불가 + - ARM 기반 이미지 검색 불가능 + +## 수정 내용 + +### 1. MCI Image Recommendation 템플릿 수정 + +**파일**: `front/templates/partials/operation/manage/_imagerecommendation.html` + +#### 수정 전 (라인 46-55) + +```html +
+ + +
+``` + +#### 수정 후 + +```html +
+ + +
+``` + +**변경 사항**: +- `readonly` 속성 제거 +- 다른 속성은 그대로 유지 + +### 2. PMK Image Recommendation 템플릿 수정 + +**파일**: `front/templates/partials/operation/manage/_pmk_imagerecommendation.html` + +#### 수정 전 (라인 45-54) + +```html +
+ + +
+``` + +#### 수정 후 + +```html +
+ + +
+``` + +**변경 사항**: +- `readonly` 속성 제거 +- 다른 속성은 그대로 유지 + +## 동작 방식 + +### 수정 후 동작 흐름 + +1. **모달 열릴 때**: + - Spec에서 `osArchitecture` 값을 가져와 필드에 자동 입력 + - 값이 없으면 "x86_64" 기본값 설정 + +2. **사용자 수정**: + - 사용자가 필요 시 필드 값 수정 가능 + - "arm64", "aarch64" 등으로 변경 가능 + +3. **검색 버튼 클릭**: + - 수정된 값으로 이미지 검색 + - `osArchitecture` 파라미터에 사용자가 입력한 값 전달 + +### 값 우선순위 + +```javascript +// getRecommendImageInfo() 함수에서 +var osArchitecture = $("#image-os-architecture").val() || + window.selectedSpecInfo.osArchitecture || + "x86_64"; +``` + +**우선순위**: +1. **사용자 입력값** (가장 높음) +2. Spec 정보 +3. 기본값 "x86_64" + +## 테스트 방법 + +### 1. 기본 동작 테스트 + +#### Test Case 1-1: 자동 입력 확인 + +1. MCI Create → Server Spec 선택 +2. Image Recommendation 모달 열기 +3. **예상 결과**: + - ✅ OS Architecture 필드에 값 자동 입력 + - ✅ Spec에 정보가 있으면 해당 값 표시 + - ✅ 없으면 "x86_64" 표시 + +#### Test Case 1-2: 수정 가능 확인 + +1. OS Architecture 필드 클릭 +2. 값을 "arm64"로 수정 +3. **예상 결과**: + - ✅ 입력 가능 + - ✅ 커서가 표시되고 편집 가능 + - ✅ readonly 상태가 아님 + +### 2. 검색 기능 테스트 + +#### Test Case 2-1: 수정된 값으로 검색 + +1. OS Architecture를 "arm64"로 수정 +2. OS Type 입력 (예: ubuntu 22.04) +3. Search 버튼 클릭 +4. **예상 결과**: + - ✅ API 호출: `/api/mc-infra-manager/SearchImage` + - ✅ Request Payload에 `osArchitecture: "arm64"` 포함 + - ✅ ARM 아키텍처 이미지 검색 결과 표시 + +#### Test Case 2-2: 기본값으로 검색 + +1. OS Architecture 필드 값 확인 (x86_64) +2. 수정하지 않고 Search 버튼 클릭 +3. **예상 결과**: + - ✅ API 호출에 `osArchitecture: "x86_64"` 포함 + - ✅ x86_64 이미지 검색 결과 표시 + +### 3. PMK 이미지 검색 테스트 + +#### Test Case 3-1: PMK용 필드 수정 + +1. PMK Workloads → NodeGroup 추가 +2. Image Recommendation 모달 열기 +3. OS Architecture 필드 수정 +4. **예상 결과**: + - ✅ 수정 가능 + - ✅ 수정된 값으로 검색 가능 + +### 4. 회귀 테스트 + +#### Test Case 4-1: 기존 기능 정상 동작 + +1. Spec 선택 → Image Recommendation 모달 +2. OS Type, GPU 옵션 설정 +3. Search → Image 선택 → Apply +4. **예상 결과**: + - ✅ 모든 기능 정상 동작 + - ✅ Apply 시 이미지 정보 정확히 전달 + - ✅ MCI/PMK 생성 정상 진행 + +### 5. UI/UX 테스트 + +#### Test Case 5-1: 입력 필드 스타일 + +1. OS Architecture 필드 확인 +2. **확인 사항**: + - ✅ Placeholder 표시: "OS Architecture from spec selection" + - ✅ 클릭 시 커서 표시 + - ✅ 포커스 효과 정상 + - ✅ Bootstrap form-control 스타일 적용 + +#### Test Case 5-2: 다양한 값 입력 + +1. 다양한 아키텍처 값 입력 테스트: + - "x86_64" + - "arm64" + - "aarch64" + - "i386" +2. **예상 결과**: + - ✅ 모든 값 입력 가능 + - ✅ 입력한 값으로 검색 가능 + +## 영향 범위 + +### 수정된 파일 + +1. **front/templates/partials/operation/manage/_imagerecommendation.html** + - OS Architecture 필드 (라인 51) - `readonly` 속성 제거 + +2. **front/templates/partials/operation/manage/_pmk_imagerecommendation.html** + - OS Architecture 필드 (라인 50) - `readonly` 속성 제거 + +### 영향받는 기능 + +- MCI Image Recommendation (MCI Create 화면) +- PMK Image Recommendation (PMK NodeGroup 화면) +- Image 검색 API 호출 + +### 하위 호환성 + +- ✅ JavaScript 코드 변경 없음 +- ✅ API 인터페이스 변경 없음 +- ✅ 기존 동작 방식 유지 (자동 입력) +- ✅ 추가 기능만 제공 (수정 가능) + +## 예상 효과 + +### 1. 사용자 경험 개선 + +- ARM 아키텍처 이미지 검색 가능 +- Spec 정보가 부정확한 경우 수정 가능 +- 다양한 아키텍처 지원으로 유연성 증가 + +### 2. 기능 확장성 + +- 향후 새로운 아키텍처 (RISC-V 등) 지원 용이 +- 사용자가 직접 아키텍처를 지정할 수 있어 테스트 및 검증 편리 + +### 3. 일관성 유지 + +- OS Type 필드와 동일하게 수정 가능한 입력 필드 +- Provider, Region은 Spec 종속이지만, OS Architecture는 사용자 선택 가능 + +### 4. 자동화 유지 + +- 기본적으로 Spec에서 값을 자동 입력 +- 수동 수정도 가능하여 자동화와 수동 제어의 균형 + +## 추가 개선 사항 + +### 향후 고려 사항 + +1. **드롭다운 방식 적용** + - OS Architecture를 텍스트 입력 대신 select 드롭다운으로 변경 + - 사전 정의된 값만 선택 가능하게 제한 + + ```html + + ``` + +2. **Validation 추가** + - 유효한 아키텍처 값인지 검증 + - 잘못된 값 입력 시 경고 메시지 + +3. **Spec 정보 개선** + - Spec API 응답에 `supportedArchitectures` 배열 추가 + - 해당 Spec이 지원하는 아키텍처 목록 제공 + +4. **자동 필터링** + - 선택한 Spec이 지원하는 아키텍처만 선택 가능하도록 제한 + - 지원하지 않는 아키텍처 선택 시 경고 + +## 관련 문서 + +- Image Recommendation 모달: `front/templates/partials/operation/manage/_imagerecommendation.html` +- Image Search API: `conf/api.yaml` - SearchImage +- 관련 이슈: Azure ARM 이미지 검색 지원 + +## 코딩 스타일 준수 + +- ✅ HTML 들여쓰기: 2칸 +- ✅ 속성 정렬: 의미있는 순서대로 배치 +- ✅ Placeholder: 명확한 안내 문구 +- ✅ ID 명명: kebab-case 사용 + +## 적용 브랜치 + +- **브랜치명**: `fix_050` +- **기반 브랜치**: `develop` +- **관련 커밋**: Remove readonly attribute from OS Architecture fields + +## 작성자 + +- AI Assistant +- 검토자: [검토자명 추가 필요] + diff --git a/doc/test/014_Azure_VM_Creation_test.md b/doc/test/014_Azure_VM_Creation_test.md new file mode 100644 index 00000000..e88128fc --- /dev/null +++ b/doc/test/014_Azure_VM_Creation_test.md @@ -0,0 +1,223 @@ +# 014_Azure_VM_Creation_test + +## 화면 정보 + +- 메뉴 ID: mciworkloads +- 화면 이름: MCI Workloads - Create MCI +- 파일 경로: front/assets/js/partials/operation/manage/mcicreate.js + +## 테스트 목표 + +1. Azure VM 생성 테스트 - 성공하는 이미지 찾기 +2. SubGroup 수정 기능 - Deploy 전 Spec/Image 재선택 가능 여부 확인 + +## 알려진 이슈 ⚠️ + +### 이슈 1: Image Recommendation 모달에서 Provider 필터링 미작동 + +**문제**: +- Image Recommendation 모달의 Spec Information 필드 (Provider, Region, OS Architecture)가 비어있음 +- Azure Spec을 선택했음에도 검색 시 **모든 Cloud Provider의 이미지가 조회됨** (AWS, Alibaba, NCP, Azure 모두 표시) +- 다른 Provider의 이미지를 선택하면 Deploy 시 "invalid format for image ID" 오류 발생 + +**영향**: +- 사용자가 수동으로 Azure 전용 이미지를 구분해서 선택해야 함 +- 이미지 ID 형식으로 구분 필요: + - ✅ **Azure**: `img-*` 형식 (예: img-487zeit5) - 선택 가능 + - ❌ **AWS**: `ami-*` 형식 - Azure에서 사용 불가 + - ❌ **Alibaba**: `*.vhd` 형식 - Azure에서 사용 불가 + - ❌ **NCP**: 숫자 형식 (예: 23214590) - Azure에서 사용 불가 + +**참조**: +- Bug #005: `doc/bug/buglist.md` 참조 +- 관련 파일: `front/assets/js/partials/operation/manage/imagerecommendation.js` + +**우회 방법**: +- **BASIC 체크 마크** 확인: Azure 전용 이미지는 BASIC 체크가 있을 가능성이 높음 +- **이미지 ID 형식** 확인: `img-*` 형식의 이미지만 선택 +- **OS DISTRIBUTION** 확인: Azure Native 이미지 설명 확인 + +--- + +## 테스트 전제 조건 + +### 로그인 정보 +- URL: http://localhost:3001 +- ID: mcmp +- Password: mcmp_password + +### Workspace & Project +- Workspace: ws01 +- Project: default + +## 테스트 시나리오 + +### 1. MCI 생성 및 SubGroup 추가 + +| 단계 | 작업 | 입력 값 | 결과 | 비고 | +|------|------|---------|------|------| +| 1 | 로그인 | ID: mcmp, PW: mcmp_password | | | +| 2 | Workspace 선택 | ws01 | | | +| 3 | Project 선택 | default | | | +| 4 | Add MCI 버튼 클릭 | | | | +| 5 | MCI Name 입력 | azumci | | | +| 6 | +SubGroup 버튼 클릭 | | | | +| 7 | Server Name 입력 | azuvm1 | | | +| 8 | Spec 검색 (돋보기) 클릭 | | | | +| 9 | Priority Option 선택 | Seoul | | | +| 10 | 검색 버튼 클릭 | | | | +| 11 | Cloud Provider Filter | Azure | | | +| 12 | Spec 선택 | 가격 0.013 | | | +| 13 | Apply 버튼 클릭 | | | | +| 14 | Image 검색 (돋보기) 클릭 | | | | +| 15 | Image Recommendation 검색 | | | 제외 이미지: ami-0eeab253db7e765a9, ami-02620a572e8f54e3c | +| 16 | Image 선택 | (선택한 이미지 기록) | | | +| 17 | Apply 버튼 클릭 | | | | +| 18 | Done 버튼 클릭 | | | VM 입력 폼 숨김, SubGroup 리스트에 추가됨 | + +### 2. SubGroup 수정 기능 테스트 (핵심 기능) + +| 단계 | 작업 | 결과 | 상태 | +|------|------|------|------| +| 1 | SubGroup 리스트의 "azuvm1(1)" 아이템 클릭 | VM 입력 폼이 다시 나타남 | ✅ 구현 완료 | +| 2 | 폼에 기존 데이터가 채워져 있는지 확인 | Server Name, Spec, Image 정보 표시됨 | ✅ 구현 완료 | +| 3 | Spec 돋보기 버튼으로 다른 Spec 선택 | Spec 재선택 가능 | ✅ 구현 완료 | +| 4 | Image 돋보기 버튼으로 다른 Image 선택 | Image 재선택 가능 | ✅ 구현 완료 | +| 5 | Done 버튼 클릭 | 기존 SubGroup 데이터 업데이트, 리스트 텍스트 업데이트 | ✅ 구현 완료 | +| 6 | +SubGroup 버튼 클릭 | 폼이 비어있는 상태로 열림 (신규 추가 모드) | ✅ 구현 완료 | + +### 3. Azure VM 생성 테스트 + +| 시도 | Image ID | Image Name | Spec | Deploy 결과 | 오류 메시지 | 비고 | +|------|----------|------------|------|-------------|-------------|------| +| 1 | 23214590 | ubuntu-22.04-base (Hypervisor:KVM) | Standard_B2ts_v2, 0.013 | ❌ FAILURE | MCI cannot be created due to critical errors in VM configurations (Providers: [azure], Regions: [koreacentral]) | 첫 번째 시도 | +| 2 | img-487zeit5 | Ubuntu Server 22.04 LTS 64bit | Standard_B2ts_v2, 0.013 | ❌ FAILURE | (실패 alert 발생) | 두 번째 시도 (2025-11-08) | +| 3 | ubuntu_22_04_x64_20G_alibase_20250917.vhd | Ubuntu 22.04 64 bit | Standard_B2ts_v2, 0.013 | ⚠️ 미완료 | PostMciDynamicReview 응답 캐치 문제 | Deploy API 호출 안됨 (2025-11-08) | +| 4 | ami-0593272c889084af9 | ubuntu-pro-fips-updates-server | Standard_B2ts_v2, 0.013 | ❌ FAILURE | Image 'ami-0593272c889084af9' not available in CSP: invalid format for image ID (AWS 이미지 ID 형식) | 네 번째 시도 (2025-11-08) | +| 5 | ami-0224cf1060c316eca | ubuntu-pro-fips-updates-server (다른 버전) | Standard_B2ts_v2, 0.013 | ❌ FAILURE | PostMciDynamicReview 200 OK, overallStatus: Error (Toast 알림 정상 표시 확인) | 다섯 번째 시도 (2025-11-08) - 에러 처리 개선 테스트 성공 | +| 6 | ami-05bd437dbad994c42 | ubuntu-minimal (2025.10.17) | Standard_B2ts_v2, 0.013 | ❌ FAILURE | Image not available: invalid format for image ID (AWS 이미지 ID) - Toast 정상 표시 | 여섯 번째 시도 (2025-11-08) | +| 7 | ubuntu_22_04_x64_20G_alibase_20250917.vhd | Ubuntu 22.04 64 bit (Alibaba Cloud) | Standard_B2ts_v2, 0.013 | ❌ FAILURE | PostMciDynamicReview Error (Alibaba Cloud image) - Toast 표시됨 | 일곱 번째 시도 (2025-11-08) | + +## 테스트 결과 + +### SubGroup 수정 기능 + +- **구현 상태**: ✅ 완료 +- **구현 일자**: 2025-11-08 +- **구현 파일**: `front/assets/js/partials/operation/manage/mcicreate.js` + +**구현 내용**: +1. `view_express(cnt)` 함수: SubGroup 클릭 시 폼에 데이터 채우기 +2. `expressDone_btn()` 함수: 신규/수정 모드 구분 로직 +3. `displayNewServerForm()` 함수: +SubGroup 버튼 클릭 시 신규 모드 설정 +4. `currentEditingIndex` 전역 변수: 수정 모드 추적 + +**동작 방식**: +- **신규 모드** (currentEditingIndex = -1): 배열에 추가 + 리스트에 새 아이템 추가 +- **수정 모드** (currentEditingIndex >= 0): 배열 업데이트 + 리스트 아이템 텍스트만 업데이트 + +**지원 기능**: +- ✅ Deploy 전 SubGroup 수정 가능 +- ✅ Spec 재선택 가능 +- ✅ Image 재선택 가능 +- ✅ 모든 설정 항목 수정 가능 +- ✅ 모든 클라우드 (Azure, AWS, GCP 등) 지원 + +### Azure VM 생성 테스트 + +**성공한 이미지 목록**: +| Image ID | Image Name | CSP | Region | 비고 | +|----------|------------|-----|--------|------| +| | | Azure | Seoul | 테스트 후 기록 | + +**실패한 이미지 목록**: +| Image ID | Image Name | CSP | Region | 오류 메시지 | 비고 | +|----------|------------|-----|--------|-------------|------| +| ami-0eeab253db7e765a9 | | | | | 사전 제외 | +| ami-02620a572e8f54e3c | | | | | 사전 제외 | +| 23214590 | ubuntu-22.04-base (Hypervisor:KVM) | Azure | koreacentral | MCI cannot be created due to critical errors in VM configurations | 시도 1 실패 (2025-11-08) | +| img-487zeit5 | Ubuntu Server 22.04 LTS 64bit | Azure | koreacentral | (실패 alert 발생) | 시도 2 실패 (2025-11-08) | +| ami-0593272c889084af9 | ubuntu-pro-fips-updates-server | Azure | koreacentral | invalid format for image ID (AWS 이미지 ID 형식) | 시도 4 실패 (2025-11-08) | +| ami-0224cf1060c316eca | ubuntu-pro-fips-updates-server (다른 버전) | Azure | koreacentral | PostMciDynamicReview 200 OK, overallStatus: Error | 시도 5 실패 (2025-11-08) | +| ami-05bd437dbad994c42 | ubuntu-minimal (2025.10.17) | Azure | koreacentral | invalid format for image ID (AWS 이미지 ID) | 시도 6 실패 (2025-11-08) | +| ubuntu_22_04_x64_20G_alibase_20250917.vhd | Ubuntu 22.04 64 bit | Azure | koreacentral | PostMciDynamicReview Error (Alibaba Cloud image) | 시도 7 실패 (2025-11-08) | + +## 사용 방법 + +### SubGroup 수정 방법 + +1. MCI 생성 화면에서 +SubGroup 버튼으로 VM을 추가 +2. Done 버튼으로 SubGroup 리스트에 추가 +3. **리스트의 SubGroup 아이템을 클릭**하면 수정 모드로 전환됨 +4. Spec, Image 등을 재선택한 후 Done 버튼 클릭 +5. Deploy 버튼으로 MCI 배포 + +### 실패 시 이미지 재선택 방법 (Spec 재사용) + +#### 방법 1: 기존 SubGroup 수정 +1. Deploy 실패 alert 확인 +2. **리스트의 SubGroup (예: azuvm1(1)) 클릭** ← 구현된 기능 활용! +3. Image 돋보기 버튼 클릭 +4. Image Recommendation에서 검색 버튼 클릭 +5. **실패한 이미지를 제외한 다른 이미지 선택**: + - 첫 번째 이미지(ami-0eeab253db7e765a9) 제외 + - 두 번째 이미지(23214590) 제외 - 시도 1 실패 + - **세 번째 이미지(img-487zeit5) 선택** ← 시도 2 +6. Apply 버튼 클릭 +7. Done 버튼 클릭 (SubGroup 업데이트됨) +8. Deploy 버튼으로 재시도 + +#### 방법 2: 새로운 SubGroup 추가 +1. +SubGroup 버튼 클릭 +2. Server Name 입력 (예: azuvm2) +3. **Spec 재선택** (Seoul, Azure, 0.013 동일) +4. **다른 Image 선택** +5. Done → Deploy + +### 연속 이미지 테스트 가이드 + +**Spec 고정**: Standard_B2ts_v2 (Azure, koreacentral, 0.013) + +**테스트할 이미지 순서** (Image Recommendation 검색 결과 순서): +1. ~~ami-0eeab253db7e765a9~~ (사전 제외) +2. ~~23214590 (ubuntu-22.04-base)~~ ✅ 시도 1 완료 - **실패** +3. **img-487zeit5** (Ubuntu Server 22.04 LTS 64bit) ← 다음 시도 +4. ubuntu_22_04_x64_20G_alibase_20250917.vhd (Ubuntu 22.04 64 bit) +5. ami-0593272c889084af9 (ubuntu-pro-fips-updates-server) +6. ... (계속) + +**각 시도마다**: +- Deploy 후 성공/실패 확인 +- 실패 시: 이 문서의 표에 결과 기록 후 다음 이미지로 진행 +- 성공 시: 이미지 ID를 "성공한 이미지 목록"에 추가 + +## 빠른 테스트 가이드 (수동) + +### 🚀 현재 진행 상황 +- ✅ 시도 1: `23214590` (ubuntu-22.04-base) - **실패** +- ⏳ 시도 2: `img-487zeit5` (Ubuntu Server 22.04 LTS 64bit) - **다음 시도** + +### 📋 체크리스트 +1. [ ] http://localhost:3001 접속 +2. [ ] Workspace: ws01, Project: default 선택 +3. [ ] Add MCI 버튼 → MCI Name: azumci +4. [ ] +SubGroup 또는 기존 SubGroup 클릭 +5. [ ] Server Name: azuvm2 입력 +6. [ ] Spec 선택: Seoul, Azure, 0.013 (Standard_B2ts_v2) +7. [ ] **Image 선택: img-487zeit5** ← 현재 시도 +8. [ ] Done → Deploy +9. [ ] 결과 확인 후 이 문서 업데이트 + +### 🔁 실패 시 다음 이미지 +3. ubuntu_22_04_x64_20G_alibase_20250917.vhd +4. ami-0593272c889084af9 +5. ... (Image Recommendation에서 계속) + +## 비고 + +- SubGroup 수정 기능은 2025-11-08에 구현 완료 +- Azure VM 생성 테스트는 실제 환경에서 수행 필요 +- 성공/실패 이미지 정보는 테스트 후 이 문서에 업데이트 +- Image ID가 "ami-"로 시작하는 것은 AWS 이미지이므로 Azure 테스트에서는 제외 +- **테스트 자동화 스크립트**: `doc/test/azure_vm_test_automation.js` (브라우저 콘솔에서 실행 가능) + diff --git a/doc/test/015_Azure_Image_Search_Plan.md b/doc/test/015_Azure_Image_Search_Plan.md new file mode 100644 index 00000000..c571f476 --- /dev/null +++ b/doc/test/015_Azure_Image_Search_Plan.md @@ -0,0 +1,539 @@ +# Azure Image Search Test Plan +## Azure에서 사용 가능한 이미지 찾기 테스트 계획 + +### 작성일 +2025-11-08 + +### 테스트 목표 +1. **Price < 1인 Azure Spec에서 사용 가능한 이미지 찾기** +2. **실제 Deploy가 성공하는 Spec + Image 조합 발견** +3. **테스트 결과를 문서화하여 향후 참고 자료로 활용** + +--- + +## Phase 1: Spec 목록 수집 (Agent Mode) + +### 1.1 목표 +- Azure, koreacentral region, price < 1 인 모든 spec 목록 확인 +- 가격순으로 정렬하여 우선순위 결정 + +### 1.2 실행 단계 +1. MCI Create 화면 접속 +2. +SubGroup → Spec 검색 모달 열기 +3. Priority Option: Seoul 선택 +4. 검색 버튼 클릭 +5. Cloud Provider Filter: Azure 선택 +6. 브라우저 콘솔에서 Spec 목록 추출 + +```javascript +// 브라우저 콘솔에서 실행 +const specs = window.recommendSpecTable.getData() + .filter(row => row.providerName === 'azure' && parseFloat(row.pricePerHour) < 1) + .map(row => ({ + id: row.id, + name: row.name, + price: parseFloat(row.pricePerHour), + memory: row.memGiB, + vcpu: row.numVCPU, + region: row.regionName, + connectionName: row.connectionName, + osArchitecture: row.osArchitecture || 'x86_64' + })) + .sort((a, b) => a.price - b.price); + +console.table(specs); +console.log(JSON.stringify(specs, null, 2)); +``` + +### 1.3 예상 결과 +- 약 50-100개의 Azure spec 목록 +- 가격 범위: $0.005 ~ $0.999 +- 주요 Spec Family: B-series (Burstable), D-series, etc. + +--- + +## Phase 2: Image 검색 스크립트 실행 (Browser Console) + +### 2.1 목표 +- 각 Spec에 대해 사용 가능한 이미지 개수 확인 +- API 직접 호출로 빠른 테스트 + +### 2.2 자동화 스크립트 + +```javascript +// 브라우저 콘솔에서 실행 +async function testAzureImages() { + // Phase 1에서 얻은 spec 목록 (상위 20개만 테스트) + const specsToTest = [ + // 여기에 Phase 1 결과를 붙여넣기 + ]; + + const results = []; + + for (const spec of specsToTest) { + console.log(`\n🔍 Testing ${spec.name} ($${spec.price})...`); + + try { + const response = await fetch('/api/mc-infra-manager/Searchimage', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + pathParams: { nsId: 'system' }, + request: { + providerName: 'azure', + regionName: 'koreacentral', + osArchitecture: spec.osArchitecture || 'x86_64', + includeDeprecatedImage: false, + isGPUImage: false, + isKubernetesImage: false + } + }) + }); + + const data = await response.json(); + + if (data.status.code === 200) { + const imageList = data.responseData?.imageList || []; + const imageCount = imageList.length; + + // Azure 이미지만 필터링 (img-* 형식) + const azureImages = imageList.filter(img => + img.id && (img.id.startsWith('img-') || img.cspImageName?.startsWith('img-')) + ); + + const result = { + spec: spec.name, + price: spec.price, + totalImages: imageCount, + azureImages: azureImages.length, + hasAzureImages: azureImages.length > 0, + firstAzureImage: azureImages[0]?.id || azureImages[0]?.cspImageName || null, + firstImageName: azureImages[0]?.name || null + }; + + results.push(result); + + if (azureImages.length > 0) { + console.log(` ✅ ${azureImages.length} Azure images found`); + console.log(` First: ${result.firstAzureImage} - ${result.firstImageName}`); + } else if (imageCount > 0) { + console.log(` ⚠️ ${imageCount} total images but no Azure (img-*) format`); + } else { + console.log(` ❌ No images found`); + } + + } else { + console.log(` ❌ API Error: ${data.status.code}`); + results.push({ + spec: spec.name, + price: spec.price, + error: data.status.message + }); + } + + // API 부하 방지 대기 + await new Promise(r => setTimeout(r, 1000)); + + } catch (error) { + console.error(` ❌ Exception:`, error.message); + results.push({ + spec: spec.name, + price: spec.price, + error: error.message + }); + } + } + + console.log('\n\n📊 === Test Results Summary ==='); + console.table(results); + + const specsWithImages = results.filter(r => r.hasAzureImages); + console.log(`\n✅ Specs with Azure images: ${specsWithImages.length}/${results.length}`); + console.log('\n🎯 Recommended specs for testing:'); + console.table(specsWithImages); + + console.log('\n📋 Copy this for Phase 3:'); + console.log(JSON.stringify(specsWithImages, null, 2)); + + return results; +} + +// 실행 +await testAzureImages(); +``` + +### 2.3 예상 결과 +- Spec 중 일부는 이미지가 없을 수 있음 +- 이미지가 있는 Spec 목록 확보 +- 각 Spec의 첫 번째 Azure 이미지 ID 수집 + +--- + +## Phase 3: 실제 Deploy 테스트 (Agent Mode - 선택적) + +### 3.1 목표 +- Phase 2에서 이미지가 발견된 Spec 중 상위 3-5개만 실제 Deploy 테스트 +- PostMciDynamicReview까지 실행하여 성공 여부 확인 + +### 3.2 우선순위 기준 +1. **가격이 가장 저렴한 Spec** (비용 효율성) +2. **이미지 개수가 많은 Spec** (선택지가 많음) +3. **일반적인 Spec** (B-series, D-series 등) + +### 3.3 테스트 절차 (각 Spec마다) +1. MCI Create 화면 +2. MCI Name: `test-azure-{spec명}` +3. +SubGroup +4. Server Name: `vm-{spec명}` +5. Spec 선택: Phase 2에서 확인한 Spec +6. Image 선택: Phase 2에서 확인한 첫 번째 Azure 이미지 +7. Done +8. **Deploy 버튼 클릭** (실제 배포 아님, Review까지만) +9. PostMciDynamicReview 응답 확인: + - `overallStatus: "Ready"` → ✅ 성공 + - `overallStatus: "Warning"` → ⚠️ 경고 + - `overallStatus: "Error"` → ❌ 실패 + +### 3.4 결과 기록 +각 테스트마다 다음 정보 기록: + +| Spec | Price | Image ID | Image Name | Review Status | Error Message | 비고 | +|------|-------|----------|------------|---------------|---------------|------| +| (테스트 후 기록) | | | | | | | + +--- + +## Phase 4: 결과 문서화 + +### 4.1 성공한 조합 문서화 +`doc/test/014_Azure_VM_Creation_test.md` 파일 업데이트 + +### 4.2 권장 사항 작성 +- 가장 안정적인 Spec + Image 조합 +- 가격대별 추천 조합 +- 알려진 이슈 및 주의사항 + +--- + +## 실행 순서 요약 + +### Step 1: Spec 목록 수집 (5분) +- **Mode**: Agent mode 또는 수동 +- **도구**: Browser + Console +- **출력**: spec 목록 JSON + +### Step 2: Image 검색 스크립트 (10-20분) +- **Mode**: Browser Console +- **도구**: JavaScript 스크립트 +- **출력**: 이미지가 있는 spec 목록 + +### Step 3: Deploy 테스트 (10-30분) +- **Mode**: Agent mode (선택적, 3-5개만) +- **도구**: Browser automation +- **출력**: 성공/실패 기록 + +### Step 4: 문서 업데이트 (5분) +- **Mode**: Agent mode +- **도구**: File edit +- **출력**: 업데이트된 테스트 문서 + +--- + +## 체크리스트 + +- [ ] Phase 1: Spec 목록 수집 완료 +- [ ] Spec 목록을 JSON으로 저장 +- [ ] Phase 2: Image 검색 스크립트 실행 +- [ ] 이미지가 있는 Spec 목록 확보 +- [ ] Phase 3: 상위 3-5개 Spec으로 Deploy 테스트 (선택적) +- [ ] 성공한 조합 최소 1개 이상 발견 +- [ ] Phase 4: 문서 업데이트 +- [ ] 다른 팀원에게 결과 공유 + +--- + +## 테스트 결과 + +### Phase 1 결과: Spec 목록 ✅ + +**수집 완료**: 2025-11-08 + +**결과**: +- **Total**: 374개의 Azure spec (price < $1, region: koreacentral) +- **가격 범위**: $0.0117 ~ $0.999 + +**가장 저렴한 Top 10 Spec** (화면에서 확인): + +| # | Price | Memory (GB) | vCPU | Spec Name | 비고 | +|----|--------|-------------|------|-----------|------| +| 1 | 0.0117 | 0.98 | 2 | Standard_B2ats_v2 | 최저가, Phase 2 테스트 완료 | +| 2 | 0.013 | 0.98 | 2 | Standard_B2ts_v2 | Phase 2 테스트 완료 | +| 3 | 0.0468 | 3.91 | 2 | Standard_B2ms | Phase 2 테스트 완료 | +| 4 | 0.052 | 3.91 | 2 | Standard_B2s | Phase 2 테스트 완료 | +| 5 | 0.0936 | 7.81 | 2 | Standard_B4ms | Phase 2 테스트 완료 | +| 6+ | ... | ... | ... | ... | 나머지 369개 | + +**Azure Spec 상세 정보** (Phase 2 테스트 대상): + +| Spec Name | Region | Connection | Price/Hour | Memory (GiB) | vCPU | OS Arch | Image 결과 | +|-----------|--------|------------|------------|--------------|------|---------|-----------| +| Standard_B2ats_v2 | koreacentral | azure-koreacentral | $0.0117 | 0.9765625 | 2 | x86_64 | 0개 ❌ | +| Standard_B2ts_v2 | koreacentral | azure-koreacentral | $0.013 | 0.9765625 | 2 | x86_64 | 0개 ❌ | +| Standard_B2ms | koreacentral | azure-koreacentral | $0.0468 | 3.90625 | 2 | x86_64 | 0개 ❌ | +| Standard_B2s | koreacentral | azure-koreacentral | $0.052 | 3.90625 | 2 | x86_64 | 0개 ❌ | +| Standard_B4ms | koreacentral | azure-koreacentral | $0.0936 | 7.8125 | 2 | x86_64 | 0개 ❌ | + +**Spec 특징**: +- **B-series (Burstable)**: 가변 성능 VM으로 비용 효율적 +- **ats_v2, ts_v2**: ARM 기반 또는 특수 목적 인스턴스 +- **ms, s**: 메모리 및 스토리지 최적화 + +**다음 단계**: 위 spec들로 Phase 2 Image 검색 진행 + +--- + +### Phase 2 결과: Image 검색 ❌ + +**실행 완료**: 2025-11-08 + +**결과**: ⚠️ **Azure koreacentral region에서 사용 가능한 이미지 없음** + +**테스트한 Spec** (5개): + +| Spec Name | Price | Total Images | Azure Images (img-*) | 결과 | +|-----------|-------|--------------|----------------------|------| +| Standard_B2ats_v2 | $0.0117 | 0 | 0 | ❌ 이미지 없음 | +| Standard_B2ts_v2 | $0.013 | 0 | 0 | ❌ 이미지 없음 | +| Standard_B2ms | $0.0468 | 0 | 0 | ❌ 이미지 없음 | +| Standard_B2s | $0.052 | 0 | 0 | ❌ 이미지 없음 | +| Standard_B4ms | $0.0936 | 0 | 0 | ❌ 이미지 없음 | + +**발견 사항**: +1. Searchimage API는 정상 응답 (200 OK) +2. `responseData.imageList`가 빈 배열 `[]`로 반환됨 +3. Provider와 Region 필터링은 정상 작동 +4. **문제**: Azure koreacentral의 이미지가 시스템에 등록되어 있지 않음 + +**이전 테스트 기록과 일치**: +- `doc/test/014_Azure_VM_Creation_test.md`의 시도 2-7에서도 같은 문제 발생 +- 모달에서 검색 시 AWS (ami-*), Alibaba (*.vhd), NCP (숫자) 이미지만 표시됨 +- Azure 전용 이미지 (img-*) 형식은 찾을 수 없었음 + +**원인 추정**: +1. **Image Catalog 미등록**: Azure koreacentral region의 이미지가 M-CMP 시스템에 등록되지 않음 +2. **Connection 문제**: azure-koreacentral connection이 이미지 정보를 가져오지 못함 +3. **Provider 설정 문제**: Azure 이미지 수집 설정이 비활성화되어 있을 가능성 + +**다음 조치 필요**: +1. Cloud Resources → Image Catalogs에서 Azure 이미지 등록 상태 확인 +2. azure-koreacentral connection 설정 확인 +3. 다른 Azure region (예: eastus, westus2) 테스트 +4. 또는 다른 provider (AWS, GCP, Alibaba) 테스트 + +### Phase 3 결과: Deploy 테스트 + +**상태**: ⏭️ 스킵됨 + +**사유**: Phase 2에서 Azure koreacentral region에 사용 가능한 이미지가 없음을 확인했으므로, Deploy 테스트를 진행할 수 없음. + +--- + +## 결론 및 권장사항 + +### 테스트 결과 요약 + +✅ **Phase 1 완료**: 374개의 Azure spec (price < $1) 확인 +❌ **Phase 2 실패**: 모든 spec에서 이미지 0개 +⏭️ **Phase 3 스킵**: 이미지 없음으로 인해 Deploy 테스트 불가 + +### 근본 원인 + +**Azure koreacentral region의 이미지가 M-CMP 시스템에 등록되어 있지 않음** + +이는 다음 중 하나의 문제일 가능성이 높습니다: +1. Image Catalog에 Azure 이미지가 수집/등록되지 않음 +2. azure-koreacentral connection 설정 문제 +3. Azure API와의 연동 문제 + +### 권장 조치사항 + +#### 즉시 조치 (High Priority) + +1. **Image Catalog 확인** + - 메뉴: Settings → Environment → Cloud Res Catalogs + - Azure provider의 이미지 목록 확인 + - 이미지가 없다면 Azure API로부터 이미지 수집 실행 + +2. **Connection 설정 확인** + - 메뉴: Settings → Environment → Cloud SPs + - azure-koreacentral connection 상태 확인 + - Test Connection 실행 + +3. **다른 Provider 테스트** + - AWS (이미 이미지 확인됨: ami-* 형식) + - GCP + - Alibaba (이미 이미지 확인됨: *.vhd 형식) + +#### 대안 (Medium Priority) + +4. **다른 Azure Region 테스트** + - eastus, westus2, westeurope 등 + - 해당 region의 spec과 이미지 확인 + +5. **Manual Image 등록** + - Azure Portal에서 직접 이미지 ID 확인 + - M-CMP에 수동으로 이미지 등록 + +### 학습 사항 + +1. **Image Recommendation 모달의 Provider 필터링 문제** + - Bug #005에서 확인한 것처럼, 모달에서 다른 provider의 이미지가 섞여 표시되는 것은 프론트엔드 문제 + - 하지만 API 레벨에서도 Azure 이미지 자체가 없음을 확인 + +2. **자동화 테스트의 가치** + - 이 테스트를 통해 수동 테스트에서 놓쳤던 근본 원인을 명확히 파악 + - 5개 spec을 자동으로 테스트하여 일관된 결과 확인 + +3. **효율적인 디버깅 프로세스** + - Phase 1: Spec 확인 (문제없음) + - Phase 2: Image 확인 (문제 발견) + - Phase 3: Deploy 테스트 (불필요, 스킵) + - 단계별로 진행하여 시간과 리소스 절약 + +### 다음 단계 + +**우선순위 1**: Image Catalog 확인 및 Azure 이미지 등록 +**우선순위 2**: 다른 provider (AWS/GCP)로 VM 생성 테스트 +**우선순위 3**: Azure 다른 region 테스트 + +--- + +## 생성된 파일 + +- `doc/test/015_Azure_Image_Search_Plan.md` - 이 파일 (테스트 계획 및 결과) +- `doc/test/azure_image_search_script.js` - Image 검색 자동화 스크립트 +- `doc/bug/buglist.md` - 업데이트됨 (Bug #007, #008 추가) + +--- + +## 체크리스트 완료 상태 + +- [x] Phase 1: Spec 목록 수집 완료 +- [x] Spec 목록을 JSON으로 저장 (문서에 테이블 형태로 기록) +- [x] Phase 2: Image 검색 스크립트 실행 +- [x] 이미지가 있는 Spec 목록 확보 (결과: 0개) +- [ ] Phase 3: 상위 3-5개 Spec으로 Deploy 테스트 (스킵 - 이미지 없음) +- [ ] 성공한 조합 최소 1개 이상 발견 (불가 - 이미지 없음) +- [x] Phase 4: 문서 업데이트 +- [ ] 다른 팀원에게 결과 공유 (사용자 재량) + +--- + +## 부록: 테스트한 Azure Spec 상세 목록 + +### 테스트 대상 Spec (5개) + +모든 Spec은 다음 조건을 만족합니다: +- **Provider**: Azure +- **Region**: koreacentral +- **Connection**: azure-koreacentral +- **Price**: < $1/hour +- **OS Architecture**: x86_64 + +#### 1. Standard_B2ats_v2 (최저가) + +``` +Spec ID: azure+koreacentral+Standard_B2ats_v2 +Price: $0.0117/hour +Memory: 0.9765625 GiB (~1 GB) +vCPU: 2 +Series: B-series (Burstable) +특징: ARM 기반 가능, 최저가 옵션 +Image 검색 결과: 0개 ❌ +``` + +#### 2. Standard_B2ts_v2 + +``` +Spec ID: azure+koreacentral+Standard_B2ts_v2 +Price: $0.013/hour +Memory: 0.9765625 GiB (~1 GB) +vCPU: 2 +Series: B-series (Burstable) +특징: 이전 테스트(014)에서도 확인됨 +Image 검색 결과: 0개 ❌ +``` + +#### 3. Standard_B2ms + +``` +Spec ID: azure+koreacentral+Standard_B2ms +Price: $0.0468/hour +Memory: 3.90625 GiB (~4 GB) +vCPU: 2 +Series: B-series (Burstable) +특징: 메모리 최적화 (ms = memory) +Image 검색 결과: 0개 ❌ +``` + +#### 4. Standard_B2s + +``` +Spec ID: azure+koreacentral+Standard_B2s +Price: $0.052/hour +Memory: 3.90625 GiB (~4 GB) +vCPU: 2 +Series: B-series (Burstable) +특징: 표준 Burstable VM +Image 검색 결과: 0개 ❌ +``` + +#### 5. Standard_B4ms + +``` +Spec ID: azure+koreacentral+Standard_B4ms +Price: $0.0936/hour +Memory: 7.8125 GiB (~8 GB) +vCPU: 2 +Series: B-series (Burstable) +특징: 메모리 최적화, 가장 높은 메모리 +Image 검색 결과: 0개 ❌ +``` + +### Spec 선정 기준 + +1. **가격 기준**: Price < $1/hour 중 최저가부터 테스트 +2. **다양성**: 메모리 크기가 다른 spec 포함 (1GB ~ 8GB) +3. **실용성**: B-series는 일반적인 워크로드에 적합 +4. **지역**: koreacentral (서울 리전) + +### 테스트 결과 요약 + +| 항목 | 결과 | +|------|------| +| 테스트한 Spec 수 | 5개 | +| 이미지를 찾은 Spec | 0개 ❌ | +| 총 발견한 이미지 | 0개 | +| API 호출 성공률 | 100% (5/5) | +| API 응답 시간 | ~1-2초/spec | + +### 미테스트 Spec (참고) + +koreacentral에는 아직 **369개의 Azure spec**이 더 있습니다 (price < $1 기준). + +상위 10-20개 spec들: +- Standard_D2s_v3, Standard_D2as_v4 등 D-series +- Standard_F2s_v2 등 F-series (Compute optimized) +- Standard_E2s_v3 등 E-series (Memory optimized) + +**권장사항**: 현재 모든 spec에서 이미지가 없으므로, 다른 spec을 테스트하는 것보다 **Image Catalog 문제를 먼저 해결**하는 것이 우선입니다. + +--- + +## 참고 문서 +- `doc/test/014_Azure_VM_Creation_test.md` - 기존 테스트 기록 +- `doc/bug/buglist.md` - 알려진 이슈 +- `front/assets/js/partials/operation/manage/imagerecommendation.js` - Image 검색 로직 + diff --git a/doc/test/azure_image_search_script.js b/doc/test/azure_image_search_script.js new file mode 100644 index 00000000..2f63b46c --- /dev/null +++ b/doc/test/azure_image_search_script.js @@ -0,0 +1,151 @@ +// Azure Image Search Test Script +// 브라우저 콘솔에서 실행하세요 (F12 → Console 탭) + +async function testAzureImages() { + console.log('🚀 Starting Azure Image Search Test...\n'); + + // 테스트할 spec 목록 (가장 저렴한 10개) + // 실제 spec 이름은 UI에서 확인 필요 + const specsToTest = [ + { name: 'Standard_B2ats_v2', price: 0.0117, region: 'koreacentral' }, + { name: 'Standard_B2ts_v2', price: 0.013, region: 'koreacentral' }, // 이미 테스트함 + { name: 'Standard_B2ms', price: 0.0468, region: 'koreacentral' }, + { name: 'Standard_B2s', price: 0.052, region: 'koreacentral' }, + { name: 'Standard_B4ms', price: 0.0936, region: 'koreacentral' }, + ]; + + const results = []; + let successCount = 0; + + for (const spec of specsToTest) { + console.log(`\n${'='.repeat(60)}`); + console.log(`🔍 Testing: ${spec.name} ($${spec.price})`); + console.log(`${'='.repeat(60)}`); + + try { + const response = await fetch('/api/mc-infra-manager/Searchimage', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + pathParams: { nsId: 'system' }, + request: { + providerName: 'azure', + regionName: spec.region, + osArchitecture: 'x86_64', + includeDeprecatedImage: false, + isGPUImage: false, + isKubernetesImage: false + // osType 제외 - 모든 OS 검색 + } + }) + }); + + const data = await response.json(); + + if (data.status.code === 200) { + const imageList = data.responseData?.imageList || []; + const imageCount = imageList.length; + + // Azure 이미지 필터링 (img-* 형식) + const azureImages = imageList.filter(img => { + const id = img.id || img.cspImageName || ''; + return id.startsWith('img-') || id.includes('azure'); + }); + + // 다른 provider 이미지 개수 + const awsImages = imageList.filter(img => (img.id || '').startsWith('ami-')); + const alibabaImages = imageList.filter(img => (img.id || '').includes('.vhd')); + + const result = { + spec: spec.name, + price: spec.price, + totalImages: imageCount, + azureImages: azureImages.length, + awsImages: awsImages.length, + alibabaImages: alibabaImages.length, + hasAzureImages: azureImages.length > 0, + firstAzureImage: azureImages[0]?.id || azureImages[0]?.cspImageName || null, + firstImageName: azureImages[0]?.name || null, + firstImageOSType: azureImages[0]?.osType || null + }; + + results.push(result); + + if (azureImages.length > 0) { + console.log(` ✅ SUCCESS: ${azureImages.length} Azure images found!`); + console.log(` 📸 First Image: ${result.firstAzureImage}`); + console.log(` 📝 Image Name: ${result.firstImageName}`); + console.log(` 💻 OS Type: ${result.firstImageOSType}`); + successCount++; + } else { + console.log(` ⚠️ WARNING: ${imageCount} total images but no Azure (img-*) format`); + if (awsImages.length > 0) console.log(` AWS: ${awsImages.length}, Alibaba: ${alibabaImages.length}`); + } + + } else { + console.log(` ❌ API Error: ${data.status.code} - ${data.status.message}`); + results.push({ + spec: spec.name, + price: spec.price, + error: `API ${data.status.code}: ${data.status.message}` + }); + } + + // API 부하 방지 대기 (1초) + await new Promise(r => setTimeout(r, 1000)); + + } catch (error) { + console.error(` ❌ Exception:`, error.message); + results.push({ + spec: spec.name, + price: spec.price, + error: `Exception: ${error.message}` + }); + } + } + + // 결과 요약 + console.log('\n\n'); + console.log('='.repeat(80)); + console.log('📊 TEST RESULTS SUMMARY'); + console.log('='.repeat(80)); + console.table(results); + + const specsWithImages = results.filter(r => r.hasAzureImages); + console.log(`\n✅ Specs with Azure images: ${successCount}/${results.length}`); + + if (specsWithImages.length > 0) { + console.log('\n🎯 RECOMMENDED SPECS FOR DEPLOY TEST:'); + console.log('='.repeat(80)); + console.table(specsWithImages); + + console.log('\n📋 Copy this data for Phase 3:'); + console.log(JSON.stringify(specsWithImages, null, 2)); + } else { + console.log('\n❌ No specs with Azure images found.'); + console.log('💡 Recommendation: Try different regions or check image availability'); + } + + console.log('\n✨ Test completed!'); + + return { + summary: { + total: results.length, + withImages: successCount, + withoutImages: results.length - successCount + }, + results: results, + recommended: specsWithImages + }; +} + +// 실행 +console.log('💡 Azure Image Search Test Script Loaded'); +console.log('📝 Run: await testAzureImages()'); +console.log(''); + +// 자동 실행 (주석 해제 시) +// await testAzureImages(); + diff --git a/doc/test/azure_vm_test_automation.js b/doc/test/azure_vm_test_automation.js new file mode 100644 index 00000000..8d077fcd --- /dev/null +++ b/doc/test/azure_vm_test_automation.js @@ -0,0 +1,130 @@ +/** + * Azure VM 생성 자동화 테스트 스크립트 + * + * 사용 방법: + * 1. MCI Workloads 페이지에서 브라우저 개발자 도구 콘솔 열기 (F12) + * 2. 이 스크립트를 콘솔에 붙여넣기 + * 3. startAzureVMTest() 함수 실행 + */ + +// 테스트할 이미지 목록 (순서대로 시도) +const testImages = [ + { id: 'img-487zeit5', name: 'Ubuntu Server 22.04 LTS 64bit', attempted: false }, + { id: 'ubuntu_22_04_x64_20G_alibase_20250917.vhd', name: 'Ubuntu 22.04 64 bit', attempted: false }, + { id: 'ami-0593272c889084af9', name: 'ubuntu-pro-fips-updates-server', attempted: false } +]; + +// Spec 정보 (재사용) +const azureSpec = { + provider: 'Azure', + region: 'koreacentral', + price: 0.013, + specName: 'Standard_B2ts_v2' +}; + +// 현재 테스트 인덱스 +let currentImageIndex = 0; + +/** + * 다음 이미지로 테스트 진행 + */ +async function testNextImage() { + if (currentImageIndex >= testImages.length) { + console.log('✅ 모든 이미지 테스트 완료!'); + console.log('테스트 결과:', testImages); + return; + } + + const currentImage = testImages[currentImageIndex]; + console.log(`\n🔍 테스트 ${currentImageIndex + 1}/${testImages.length}: ${currentImage.name} (${currentImage.id})`); + console.log('-------------------------------------------'); + + // 사용자에게 수동 작업 안내 + console.log('📋 다음 단계를 수동으로 진행해주세요:'); + console.log('1. SubGroup 클릭 (또는 +SubGroup)'); + console.log('2. Server Name 입력'); + console.log(`3. Spec 선택: ${azureSpec.specName} (${azureSpec.provider}, ${azureSpec.price})`); + console.log(`4. Image 선택: ${currentImage.id}`); + console.log('5. Done 버튼 클릭'); + console.log('6. Deploy 버튼 클릭'); + console.log('\n배포 완료 후:'); + console.log('- 성공: recordSuccess() 입력'); + console.log('- 실패: recordFailure("오류메시지") 입력'); + + currentImage.attempted = true; +} + +/** + * 성공 기록 + */ +function recordSuccess() { + const currentImage = testImages[currentImageIndex]; + currentImage.result = 'SUCCESS'; + currentImage.timestamp = new Date().toISOString(); + + console.log(`\n✅ 성공! 이미지: ${currentImage.id}`); + console.log('성공한 이미지 정보:'); + console.log(JSON.stringify(currentImage, null, 2)); + console.log('\n🎉 Azure VM 생성 테스트 성공!'); + console.log('\n📝 테스트 문서에 다음 정보를 추가하세요:'); + console.log(`| ${currentImage.id} | ${currentImage.name} | Azure | koreacentral | ✅ SUCCESS |`); +} + +/** + * 실패 기록 및 다음 이미지로 진행 + */ +function recordFailure(errorMessage = '') { + const currentImage = testImages[currentImageIndex]; + currentImage.result = 'FAILURE'; + currentImage.error = errorMessage; + currentImage.timestamp = new Date().toISOString(); + + console.log(`\n❌ 실패! 이미지: ${currentImage.id}`); + console.log(`오류: ${errorMessage}`); + console.log('\n📝 테스트 문서에 다음 정보를 추가하세요:'); + console.log(`| ${currentImageIndex + 2} | ${currentImage.id} | ${currentImage.name} | ${azureSpec.specName}, ${azureSpec.price} | ❌ FAILURE | ${errorMessage} | 시도 ${currentImageIndex + 2} 실패 |`); + + // 다음 이미지로 진행 + currentImageIndex++; + setTimeout(testNextImage, 1000); +} + +/** + * 테스트 시작 + */ +function startAzureVMTest() { + console.log('🚀 Azure VM 생성 자동화 테스트 시작'); + console.log('====================================='); + console.log(`총 테스트 이미지 수: ${testImages.length}`); + console.log(`Spec: ${azureSpec.specName} (${azureSpec.provider}, ${azureSpec.region}, $${azureSpec.price})`); + console.log('=====================================\n'); + + currentImageIndex = 0; + testNextImage(); +} + +/** + * 현재 테스트 상태 확인 + */ +function getTestStatus() { + console.log('\n📊 테스트 현황'); + console.log('====================================='); + testImages.forEach((img, idx) => { + const status = img.result ? (img.result === 'SUCCESS' ? '✅' : '❌') : '⏳'; + console.log(`${idx + 1}. ${status} ${img.name} (${img.id})`); + if (img.result === 'FAILURE' && img.error) { + console.log(` 오류: ${img.error}`); + } + }); + console.log('=====================================\n'); +} + +// 사용 가능한 함수 안내 +console.log('📌 사용 가능한 함수:'); +console.log('- startAzureVMTest() : 테스트 시작'); +console.log('- recordSuccess() : 성공 기록'); +console.log('- recordFailure("오류메시지") : 실패 기록 및 다음 이미지 테스트'); +console.log('- getTestStatus() : 현재 테스트 상태 확인'); +console.log('- testNextImage() : 다음 이미지 테스트 안내'); +console.log('\n준비되면 startAzureVMTest()를 입력하여 시작하세요!'); + diff --git a/front/assets/js/application.js b/front/assets/js/application.js index ef291de7..5bcf0437 100644 --- a/front/assets/js/application.js +++ b/front/assets/js/application.js @@ -1,5 +1,6 @@ require("expose-loader?exposes=$,jQuery!jquery"); -require("bootstrap/dist/js/bootstrap.bundle.js"); +const bootstrap = require("bootstrap/dist/js/bootstrap.bundle.js"); +window.bootstrap = bootstrap; require("@fortawesome/fontawesome-free/js/all.js"); require("jquery-ujs/src/rails.js"); require("~jquery/dist/jquery.js"); diff --git a/front/assets/js/common/api/services/mci_api.js b/front/assets/js/common/api/services/mci_api.js index d8754b3e..393ac91a 100644 --- a/front/assets/js/common/api/services/mci_api.js +++ b/front/assets/js/common/api/services/mci_api.js @@ -362,16 +362,7 @@ export async function searchImage(nsId, searchParams) { pathParams: { nsId: "system" }, - request: { - includeDeprecatedImage: searchParams.includeDeprecatedImage || false, - isGPUImage: searchParams.isGPUImage || false, - isKubernetesImage: searchParams.isKubernetesImage || false, - // isRegisteredByAsset: searchParams.isRegisteredByAsset || false, - osArchitecture: searchParams.osArchitecture || "x86_64", - osType: searchParams.osType || "ubuntu 22.04", - providerName: searchParams.providerName || "", - regionName: searchParams.regionName || "" - } + request: searchParams }; var controller = "/api/" + "mc-infra-manager/" + "Searchimage"; diff --git a/front/assets/js/common/api/services/pmk_api.js b/front/assets/js/common/api/services/pmk_api.js index 9365a193..109c5bec 100644 --- a/front/assets/js/common/api/services/pmk_api.js +++ b/front/assets/js/common/api/services/pmk_api.js @@ -345,35 +345,65 @@ export function calculateConnectionCount(clusterList) { } export async function createNode(k8sClusterId, nsId, Create_Node_Config_Arr) { + // 1. 배열 검증 + if (!Create_Node_Config_Arr || Create_Node_Config_Arr.length === 0) { + console.error('No node configuration provided'); + webconsolejs["common/util"].showToast('No node configuration to create', 'error'); + return; + } - var obj = {} + var obj = Create_Node_Config_Arr[0]; + + // 2. 필수 필드 검증 + if (!obj.name || !obj.specId || !obj.imageId || !obj.sshKeyId || !obj.minNodeSize || !obj.maxNodeSize || !obj.onAutoScaling) { + console.error('Missing required fields:', obj); + webconsolejs["common/util"].showToast('Missing required fields for node creation', 'error'); + return; + } - obj = Create_Node_Config_Arr[0] + // 3. 데이터 준비 (기본값 포함) const data = { pathParams: { nsId: nsId, k8sClusterId: k8sClusterId, }, request: { - "desiredNodeSize": obj.desiredNodeSize, + "desiredNodeSize": obj.desiredNodeSize || "1", "imageId": obj.imageId, - "maxNodeSize": obj.maxNodeSize, - "minNodeSize": obj.minNodeSize, + "maxNodeSize": obj.maxNodeSize || obj.desiredNodeSize || "1", + "minNodeSize": obj.minNodeSize || obj.desiredNodeSize || "1", "name": obj.name, - "onAutoScaling": obj.onAutoScaling, - "rootDiskSize": obj.rootDiskSize, - "rootDiskType": obj.rootDiskType, + "onAutoScaling": obj.onAutoScaling || "false", + "rootDiskSize": obj.rootDiskSize || "", + "rootDiskType": obj.rootDiskType || "", "specId": obj.specId, "sshKeyId": obj.sshKeyId } - } - + }; var controller = "/api/" + "mc-infra-manager/" + "Postk8snodegroup"; - const response = await webconsolejs["common/api/http"].commonAPIPost( - controller, - data - ) + + // 4. API 호출 및 에러 처리 + try { + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); + + // 성공 처리 + if (response && response.status === 200) { + webconsolejs["common/util"].showToast('Node group creation request completed successfully', 'success'); + return response; + } else { + console.error('Node creation failed:', response); + webconsolejs["common/util"].showToast('Failed to create node group', 'error'); + return response; + } + } catch (error) { + console.error('Error creating node:', error); + webconsolejs["common/util"].showToast('Error creating node group: ' + (error.message || 'Unknown error'), 'error'); + throw error; + } } export async function getSshKey(nsId) { @@ -586,20 +616,49 @@ export function calculateVmStatusCount(aPmk) { } export function pmkDelete(nsId, k8sClusterId) { + // API 레벨 Validation (추가 안전장치) + if (!nsId || nsId === '' || !k8sClusterId || k8sClusterId === '') { + console.error('Invalid parameters for PMK deletion:', { + nsId: nsId, + k8sClusterId: k8sClusterId + }); + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Invalid Parameters', + 'Invalid parameters for PMK deletion. Please try again.' + ); + return; + } + let data = { pathParams: { nsId: nsId, k8sClusterId: k8sClusterId, }, }; - let controller = "/api/" + "mc-infra-manager/" + "Deletek8scluster"; - let response = webconsolejs["common/api/http"].commonAPIPost( + let controller = '/api/' + 'mc-infra-manager/' + 'Deletek8scluster'; + let response = webconsolejs['common/api/http'].commonAPIPost( controller, data ); + return response; } export function nodeGroupDelete(nsId, k8sClusterId, k8sNodeGroupName) { + // API 레벨 Validation (추가 안전장치) + if (!nsId || nsId === '' || + !k8sClusterId || k8sClusterId === '' || + !k8sNodeGroupName || k8sNodeGroupName === '') { + console.error('Invalid parameters for NodeGroup deletion:', { + nsId: nsId, + k8sClusterId: k8sClusterId, + k8sNodeGroupName: k8sNodeGroupName + }); + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Invalid Parameters', + 'Invalid parameters for NodeGroup deletion. Please try again.' + ); + return; + } let data = { pathParams: { @@ -608,11 +667,12 @@ export function nodeGroupDelete(nsId, k8sClusterId, k8sNodeGroupName) { k8sNodeGroupName: k8sNodeGroupName }, }; - let controller = "/api/" + "mc-infra-manager/" + "Deletek8snodegroup"; - let response = webconsolejs["common/api/http"].commonAPIPost( + let controller = '/api/' + 'mc-infra-manager/' + 'Deletek8snodegroup'; + let response = webconsolejs['common/api/http'].commonAPIPost( controller, data ); + return response; } // PMK용 Spec 추천 API diff --git a/front/assets/js/common/api/services/workspace_api.js b/front/assets/js/common/api/services/workspace_api.js index a9ba95db..c19e905b 100644 --- a/front/assets/js/common/api/services/workspace_api.js +++ b/front/assets/js/common/api/services/workspace_api.js @@ -56,22 +56,33 @@ export async function getWorkspaceListByUser() { var workspaceList = []; // 세션에서 찾기 let userWorkspaceList = await webconsolejs["common/storage/sessionstorage"].getSessionWorkspaceProjectList(); - if (userWorkspaceList == null) {// 없으면 조회 - var userWorkspaceProjectList = await getWorkspaceProjectListByUserToken()// workspace 목록, project 목록 조회 - setWorkspaceProjectList(userWorkspaceProjectList) - // userWorkspaceProjectList.forEach(item => { - // workspaceList.push(item.workspaceProject.workspace); - // }); - userWorkspaceProjectList.forEach(item => { - workspaceList.push(item); - }); - //console.log("workspaceList", workspaceList) - // 새로 조회한 경우 저장된 curworkspace, curproject 는 초기화 할까? - setCurrentWorkspace("") - setCurrentProject("") + + // sessionStorage에 데이터가 없거나 빈 배열인 경우 API 호출 + if (userWorkspaceList == null || !Array.isArray(userWorkspaceList) || userWorkspaceList.length === 0) { + try { + // workspace 목록, project 목록 조회 + var userWorkspaceProjectList = await getWorkspaceProjectListByUserToken(); + + if (userWorkspaceProjectList && Array.isArray(userWorkspaceProjectList) && userWorkspaceProjectList.length > 0) { + setWorkspaceProjectList(userWorkspaceProjectList); + userWorkspaceProjectList.forEach(item => { + workspaceList.push(item); + }); + // 새로 조회한 경우 저장된 curworkspace, curproject 는 초기화 + setCurrentWorkspace(""); + setCurrentProject(""); + } else { + console.warn("No workspace data available for user"); + // 빈 배열 반환 (에러는 아니므로 조용히 처리) + return []; + } + } catch (error) { + console.error("Failed to fetch workspace list:", error); + // 에러 발생 시 빈 배열 반환 + return []; + } } else { userWorkspaceList.forEach(item => { - //workspaceList.push(item.workspaceProject.workspace); workspaceList.push(item); }); } diff --git a/front/assets/js/pages/operation/manage/pmk.js b/front/assets/js/pages/operation/manage/pmk.js index 577423a3..1c276d1e 100644 --- a/front/assets/js/pages/operation/manage/pmk.js +++ b/front/assets/js/pages/operation/manage/pmk.js @@ -192,17 +192,65 @@ export async function getSelectedPmkData() { // pmk 삭제 export function deletePmk() { + // Validation 1: PMK가 선택되었는지 확인 + if (!currentPmkId || currentPmkId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'PMK Selection Check', + 'Please select a PMK to delete.' + ); + return; + } + + // Validation 2: Workspace/Project가 선택되었는지 확인 + var selectedNsId = selectedWorkspaceProject.nsId; + if (!selectedNsId || selectedNsId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Workspace Selection Check', + 'Please select a workspace and project first.' + ); + return; + } - var selectedNsId = selectedWorkspaceProject.nsId; - webconsolejs["common/api/services/pmk_api"].pmkDelete(selectedNsId, currentPmkId) + // Validation 통과 후 API 호출 + webconsolejs['common/api/services/pmk_api'].pmkDelete(selectedNsId, currentPmkId); } // nodegroup 삭제 export function deleteNodeGroup() { - - var selectedNsId = selectedWorkspaceProject.nsId; - webconsolejs["common/api/services/pmk_api"].nodeGroupDelete(selectedNsId, currentPmkId, currentNodeGroupName) - + // Validation 1: NodeGroup이 선택되었는지 확인 + if (!currentNodeGroupName || currentNodeGroupName === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'NodeGroup Selection Check', + 'Please select a NodeGroup to delete.' + ); + return; + } + + // Validation 2: PMK가 선택되었는지 확인 + if (!currentPmkId || currentPmkId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'PMK Selection Check', + 'Please select a PMK first.' + ); + return; + } + + // Validation 3: Workspace/Project가 선택되었는지 확인 + var selectedNsId = selectedWorkspaceProject.nsId; + if (!selectedNsId || selectedNsId === '') { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Workspace Selection Check', + 'Please select a workspace and project first.' + ); + return; + } + + // Validation 통과 후 API 호출 + webconsolejs['common/api/services/pmk_api'].nodeGroupDelete( + selectedNsId, + currentPmkId, + currentNodeGroupName + ); } // 클릭한 pmk의 info값 세팅 @@ -1422,27 +1470,34 @@ export function validateAndOpenImageModalPmk(event) { console.error("PMK Image recommendation module not found."); } - // 비동기적으로 모달 열기 (MCI와 동일한 패턴) - setTimeout(function () { - try { - // Bootstrap 5 방식으로 모달 열기 - if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { - const imageModalEl = document.getElementById('image-search-pmk'); - if (imageModalEl) { - const imageModal = new bootstrap.Modal(imageModalEl); - imageModal.show(); - } else { - throw new Error("PMK Image modal element not found"); - } + // 비동기적으로 모달 열기 (MCI와 동일한 패턴) + setTimeout(function () { + try { + // Spec Information 필드 채우기 (모달 열기 전) + if (window.selectedPmkSpecInfo) { + $("#image-provider-pmk").val(window.selectedPmkSpecInfo.provider || ""); + $("#image-region-pmk").val(window.selectedPmkSpecInfo.regionName || ""); + $("#image-os-architecture-pmk").val(window.selectedPmkSpecInfo.osArchitecture || ""); + } + + // Bootstrap 5 방식으로 모달 열기 + if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { + const imageModalEl = document.getElementById('image-search-pmk'); + if (imageModalEl) { + const imageModal = new bootstrap.Modal(imageModalEl); + imageModal.show(); } else { - console.error("Bootstrap is not loaded"); - alert("could not open modal because Bootstrap is not loaded"); + throw new Error("PMK Image modal element not found"); } - } catch (error) { - console.error("failed to open PMK image modal:", error); - alert("Error opening PMK image recommendation modal. Please try again."); + } else { + console.error("Bootstrap is not loaded"); + alert("could not open modal because Bootstrap is not loaded"); } - }, 100); // 100ms 지연으로 이벤트 처리 완료 후 모달 열기 + } catch (error) { + console.error("failed to open PMK image modal:", error); + alert("Error opening PMK image recommendation modal. Please try again."); + } + }, 100); // 100ms 지연으로 이벤트 처리 완료 후 모달 열기 } catch (error) { console.error("failed to open PMK image modal:", error); diff --git a/front/assets/js/partials/layout/navbar.js b/front/assets/js/partials/layout/navbar.js index 5638ec30..7aaf83d4 100644 --- a/front/assets/js/partials/layout/navbar.js +++ b/front/assets/js/partials/layout/navbar.js @@ -82,8 +82,18 @@ export async function setPrjSelectBox(workspaceId) { // 기본은 local storage에 저장된 값 사용 -> 없으면 조회 // navbar에 workspace 목록 selectbox와 project 목록 select box set export async function workspaceProjectInit() { - let userWorkspaceList = await webconsolejs["common/api/services/workspace_api"].getWorkspaceListByUser() - let curWorkspace = webconsolejs["common/api/services/workspace_api"].getCurrentWorkspace() + let userWorkspaceList = await webconsolejs["common/api/services/workspace_api"].getWorkspaceListByUser(); + + // workspace 목록이 비어있는 경우 처리 + if (!userWorkspaceList || !Array.isArray(userWorkspaceList) || userWorkspaceList.length === 0) { + console.warn("No workspace data available"); + // 빈 selectbox 설정 + webconsolejs["common/api/services/workspace_api"].setWorkspaceSelectBox([], ""); + webconsolejs["common/api/services/workspace_api"].setPrjSelectBox(null, ""); + return { workspaceId: "", workspaceName: "", projectId: "", projectName: "", nsId: "" }; + } + + let curWorkspace = webconsolejs["common/api/services/workspace_api"].getCurrentWorkspace(); let curWorkspaceId = ""; let curWorkspaceName = ""; if (curWorkspace) { @@ -91,22 +101,22 @@ export async function workspaceProjectInit() { curWorkspaceName = curWorkspace.Name; } - webconsolejs["common/api/services/workspace_api"].setWorkspaceSelectBox(userWorkspaceList, curWorkspaceId) + webconsolejs["common/api/services/workspace_api"].setWorkspaceSelectBox(userWorkspaceList, curWorkspaceId); let curProjectId = ""; let curProjectName = ""; let curNsId = ""; if (curWorkspaceId == "" || curWorkspaceId == undefined) { - webconsolejs["common/api/services/workspace_api"].setPrjSelectBox(null, "") + webconsolejs["common/api/services/workspace_api"].setPrjSelectBox(null, ""); } else { - let userProjectList = await webconsolejs["common/api/services/workspace_api"].getUserProjectList(curWorkspaceId) + let userProjectList = await webconsolejs["common/api/services/workspace_api"].getUserProjectList(curWorkspaceId); let curProject = await webconsolejs["common/api/services/workspace_api"].getCurrentProject(); if (curProject) { curProjectId = curProject.Id; curProjectName = curProject.Name; curNsId = curProject.NsId; } - webconsolejs["common/api/services/workspace_api"].setPrjSelectBox(userProjectList, curProjectId) + webconsolejs["common/api/services/workspace_api"].setPrjSelectBox(userProjectList, curProjectId); } return { workspaceId: curWorkspaceId, workspaceName: curWorkspaceName, projectId: curProjectId, projectName: curProjectName, nsId: curNsId }; diff --git a/front/assets/js/partials/operation/manage/clustercreate.js b/front/assets/js/partials/operation/manage/clustercreate.js index 49d2f4bc..0114981c 100644 --- a/front/assets/js/partials/operation/manage/clustercreate.js +++ b/front/assets/js/partials/operation/manage/clustercreate.js @@ -759,7 +759,26 @@ export function clusterFormDone_btn() { } export function addNodeFormDone_btn() { + // 1. 필수 필드 검증 + var requiredFields = [ + { id: '#node_name', message: 'NodeGroup name is required' }, + { id: '#node_specid', message: 'Spec is required' }, + { id: '#node_imageid', message: 'Image is required' }, + { id: '#node_sshkey', message: 'SSH Key is required' }, + { id: '#node_autoscaling', message: 'AutoScaling option is required' }, + { id: '#node_minnodesize', message: 'Min Node Size is required' }, + { id: '#node_maxnodesize', message: 'Max Node Size is required' } + ]; + + for (var field of requiredFields) { + if (!$(field.id).val() || $(field.id).val().trim() === '') { + alert(field.message); + $(field.id).focus(); + return; + } + } + // 2. hidden 필드에 값 설정 $("#n_name").val($("#node_name").val()) $("#n_specid").val($("#node_specid").val()) $("#n_imageid").val($("#node_imageid").val()) diff --git a/front/assets/js/partials/operation/manage/imagerecommendation.js b/front/assets/js/partials/operation/manage/imagerecommendation.js index 0f49f056..69e471ca 100644 --- a/front/assets/js/partials/operation/manage/imagerecommendation.js +++ b/front/assets/js/partials/operation/manage/imagerecommendation.js @@ -179,30 +179,64 @@ export async function getRecommendImageInfo() { return; } - var osType = $("#assist_os_type").val() - var isGPUImage = $("#gpu_image_value").val() + var osType = $("#assist_os_type").val(); + var isGPUImage = $("#gpu_image_value").val(); - // 전역 변수에서 정보 가져오기 - var specId = window.selectedSpecInfo.id; // spec의 전체 ID (예: "aws+ap-northeast-2+t2.small") - var provider = window.selectedSpecInfo.provider; - var region = window.selectedSpecInfo.regionName; + // UI 필드에서 값 가져오기 + var provider = $("#image-provider").val() || window.selectedSpecInfo.provider; + var region = $("#image-region").val() || window.selectedSpecInfo.regionName; + var osArchitecture = $("#image-os-architecture").val(); var connectionName = window.selectedSpecInfo.connectionName; + // 디버깅 로그 - 상세 정보 + console.log("=== MCI Image Search Debug ==="); + console.log("Provider:", provider); + console.log("Region:", region); + console.log("OS Architecture raw value:", osArchitecture, "| Type:", typeof osArchitecture, "| Length:", osArchitecture ? osArchitecture.length : 0); + console.log("OS Type raw value:", osType, "| Type:", typeof osType, "| Length:", osType ? osType.length : 0); + // 현재 workspace/project 정보 가져오기 try { var selectedWorkspaceProject = await webconsolejs["partials/layout/navbar"].workspaceProjectInit(); var nsId = selectedWorkspaceProject.nsId; - // API 호출을 위한 파라미터 구성 (간단화된 방식) + // API 호출을 위한 파라미터 구성 - 필수 항목만 포함 var searchParams = { - matchedSpecId: specId, // spec ID를 사용하여 provider, region, architecture 자동 매칭 - osType: osType + providerName: provider, + regionName: region, + maxResults: 100 }; - // GPU 이미지가 필요한 경우에만 추가 + // 선택적 파라미터 - 값이 있을 때만 추가 + if (osArchitecture && typeof osArchitecture === 'string' && osArchitecture.trim() !== "") { + console.log("Adding osArchitecture:", osArchitecture); + searchParams.osArchitecture = osArchitecture.trim(); + } else { + console.log("Skipping osArchitecture - empty or invalid"); + } + + if (osType && typeof osType === 'string' && osType.trim() !== "") { + console.log("Adding osType:", osType); + searchParams.osType = osType.trim(); + } else { + console.log("Skipping osType - empty or invalid"); + } + if (isGPUImage === "true") { + console.log("Adding isGPUImage: true"); searchParams.isGPUImage = true; } + + var matchedSpecId = $("#matched_spec_id").val(); + if (matchedSpecId && typeof matchedSpecId === 'string' && matchedSpecId.trim() !== "") { + console.log("Adding matchedSpecId:", matchedSpecId); + searchParams.matchedSpecId = matchedSpecId.trim(); + } else { + console.log("Skipping matchedSpecId - empty or invalid"); + } + + console.log("Final searchParams:", JSON.stringify(searchParams)); + console.log("=== End Debug ==="); // 이미지 검색 API 호출 var response = await webconsolejs["common/api/services/mci_api"].searchImage(nsId, searchParams); @@ -213,7 +247,7 @@ export async function getRecommendImageInfo() { // 이미지가 없는 경우 안내 메시지 if (imageList.length === 0) { console.warn("No images found for the selected spec and OS type"); - alert("No images found for the selected specification and OS type. Please try different criteria."); + webconsolejs["common/util"].showToast("No images found for the selected specification and OS type. Please try different criteria.", 'warning', 5000); safeSetTableData([]); return; } @@ -372,6 +406,14 @@ export function validateAndOpenImageModal(event) { // 비동기적으로 모달 열기 (PMK와 동일한 방식으로 단순화) setTimeout(function() { try { + // Spec Information 필드 채우기 (모달 열기 전) + if (window.selectedSpecInfo) { + $("#image-provider").val(window.selectedSpecInfo.provider || ""); + $("#image-region").val(window.selectedSpecInfo.regionName || ""); + $("#image-os-architecture").val(window.selectedSpecInfo.osArchitecture || ""); + $("#matched_spec_id").val(window.selectedSpecInfo.id || ""); + } + // Bootstrap 5 방식으로 모달 열기 if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { var imageModalEl = document.getElementById('image-search'); diff --git a/front/assets/js/partials/operation/manage/mcicreate.js b/front/assets/js/partials/operation/manage/mcicreate.js index 3929ff07..4c7bc26a 100644 --- a/front/assets/js/partials/operation/manage/mcicreate.js +++ b/front/assets/js/partials/operation/manage/mcicreate.js @@ -302,58 +302,62 @@ var createMciListObj = new Object(); var isVm = false // mci 생성(false) / vm 추가(true) var Express_Server_Config_Arr = new Array(); var express_data_cnt = 0 +var currentEditingIndex = -1 // 현재 수정 중인 서버의 인덱스 (-1: 신규 추가 모드) // 서버 더하기버튼 클릭시 서버정보 입력area 보이기/숨기기 // isExpert의 체크 여부에 따라 바뀜. // newServers 와 simpleServers가 있음. export async function displayNewServerForm() { - var deploymentAlgo = $("#mci_deploy_algorithm").val(); - - if (deploymentAlgo == "express") { - // 폼을 열기 전에 추가 초기화 - $("#ep_name").val(""); - $("#ep_description").val(""); - $("#ep_imageId_input").val(""); - $("#ep_root_disk_type").val(""); - $("#ep_root_disk_size").val(""); - $("#ep_vm_add_cnt").val("1"); // 기본값 1로 설정 - $("#ep_data_disk").val(""); - $("#ep_command").val(""); - - // 모달들 초기화 - resetModals(); - - var div = document.getElementById("server_configuration"); - webconsolejs["partials/layout/navigatePages"].toggleSubElement(div) - - } else if (deploymentAlgo == "simple") { - // var div = document.getElementById("server_configuration"); - // webconsolejs["partials/layout/navigatePages"].toggleElement(div) - - } else if (deploymentAlgo == "expert") { - // call getProviderList API - var providerList = await webconsolejs["common/api/services/mci_api"].getProviderList() - // provider set - await setProviderList(providerList) - - // call getRegion API - var regionList = await webconsolejs["common/api/services/mci_api"].getRegionList() - // region set - await setRegionList(regionList) - - // call cloudconnection - var connectionList = await webconsolejs["common/api/services/mci_api"].getCloudConnection() - // cloudconnection set - await setCloudConnection(connectionList) - - // toggle expert form - var div = document.getElementById("expert_server_configuration"); - webconsolejs["partials/layout/navigatePages"].toggleSubElement(div) - - } else { - console.error(e) - } + // +SubGroup 버튼 클릭 시 수정 모드 플래그 초기화 (신규 추가 모드) + currentEditingIndex = -1; + + var deploymentAlgo = $("#mci_deploy_algorithm").val(); + + if (deploymentAlgo == "express") { + // 폼을 열기 전에 추가 초기화 + $("#ep_name").val(""); + $("#ep_description").val(""); + $("#ep_imageId_input").val(""); + $("#ep_root_disk_type").val(""); + $("#ep_root_disk_size").val(""); + $("#ep_vm_add_cnt").val("1"); // 기본값 1로 설정 + $("#ep_data_disk").val(""); + $("#ep_command").val(""); + + // 모달들 초기화 + resetModals(); + + var div = document.getElementById("server_configuration"); + webconsolejs["partials/layout/navigatePages"].toggleSubElement(div) + + } else if (deploymentAlgo == "simple") { + // var div = document.getElementById("server_configuration"); + // webconsolejs["partials/layout/navigatePages"].toggleElement(div) + + } else if (deploymentAlgo == "expert") { + // call getProviderList API + var providerList = await webconsolejs["common/api/services/mci_api"].getProviderList() + // provider set + await setProviderList(providerList) + + // call getRegion API + var regionList = await webconsolejs["common/api/services/mci_api"].getRegionList() + // region set + await setRegionList(regionList) + + // call cloudconnection + var connectionList = await webconsolejs["common/api/services/mci_api"].getCloudConnection() + // cloudconnection set + await setCloudConnection(connectionList) + + // toggle expert form + var div = document.getElementById("expert_server_configuration"); + webconsolejs["partials/layout/navigatePages"].toggleSubElement(div) + + } else { + console.error(e) + } // var expressServerConfig = $("#expressServerConfig"); @@ -394,113 +398,137 @@ export async function displayNewServerForm() { // express모드 -> Done버튼 클릭 시 export function expressDone_btn() { - // 1. 필수 필드 검증 - var requiredFields = [ - { id: '#ep_name', message: 'SubGroup name is required' }, - { id: '#ep_vm_add_cnt', message: 'VM count is required' }, - { id: '#ep_commonSpecId', message: 'Spec is required' }, - { id: '#ep_commonImageId', message: 'Image is required' } - ]; - - for (var field of requiredFields) { - if (!$(field.id).val() || $(field.id).val().trim() === '') { - alert(field.message); - $(field.id).focus(); - return; - } - } - - // 2. VM 개수 숫자 검증 - var vmAddCnt = $("#ep_vm_add_cnt").val(); - if (isNaN(vmAddCnt) || parseInt(vmAddCnt) < 1) { - alert('VM count must be a positive number'); - $("#ep_vm_add_cnt").focus(); - return; - } - - // express 는 common resource를 하므로 별도로 처리(connection, spec만) - $("#p_provider").val($("#ep_provider").val()) - $("#p_connectionName").val($("#ep_connectionName").val()) - $("#p_name").val($("#ep_name").val()) - $("#p_description").val($("#ep_description").val()) - $("#p_imageId").val($("#ep_imageId").val()) - $("#p_commonImageId").val($("#ep_commonImageId").val()) - $("#ep_imageId_input").val($("#ep_imageId").val()) // 이미지 입력 필드도 업데이트 - $("#p_commonSpecId").val($("#ep_commonSpecId").val()) - $("#p_root_disk_type").val($("#ep_root_disk_type").val()) - $("#p_root_disk_size").val($("#ep_root_disk_size").val()) - $("#p_specId").val($("#ep_specId").val()) - $("#p_command").val($("#ep_command").val()) - // ep_vm_add_cnt가 비어있으면 기본값 1로 설정 - var vmAddCnt = $("#ep_vm_add_cnt").val(); - if (!vmAddCnt || vmAddCnt.trim() === "") { - vmAddCnt = "1"; - } - $("#p_subGroupSize").val(vmAddCnt) - $("#p_vm_cnt").val(vmAddCnt) - - // commonSpec 으로 set 해야하므로 재설정 - var express_form = {} - express_form["name"] = $("#p_name").val(); - express_form["description"] = $("#p_description").val(); - express_form["subGroupSize"] = $("#p_subGroupSize").val(); - express_form["rootDiskSize"] = $("#p_root_disk_size").val(); - express_form["rootDiskType"] = $("#p_root_disk_type").val(); - express_form["rootDiskType"] = $("#p_root_disk_type").val(); - express_form["commonSpec"] = $("#p_commonSpecId").val(); - express_form["commonImage"] = $("#p_commonImageId").val(); - express_form["command"] = $("#p_command").val(); - - var server_name = express_form.name; - var server_cnt = parseInt(express_form.subGroupSize); - - var add_server_html = ""; - - Express_Server_Config_Arr.push(express_form); - - var displayServerCnt = '(' + server_cnt + ')'; - add_server_html += '
  • ' - + server_name + displayServerCnt - + '
  • '; - - var div = document.getElementById("server_configuration"); - webconsolejs["partials/layout/navigatePages"].toggleSubElement(div); - - var vmEleId = "vm"; - if (!isVm) { - vmEleId = "mci"; - } - $("#" + vmEleId + "_plusVmIcon").remove(); - $("#" + vmEleId + "_server_list").append(add_server_html); - $("#" + vmEleId + "_server_list").prepend(getPlusVm(vmEleId)); - - express_data_cnt++; - - // 폼 초기화 - 모든 입력 필드 초기화 - $("#express_form").each(function () { - this.reset(); - }); - - // 숨겨진 필드들 초기화 - $("#ep_provider").val(""); - $("#ep_connectionName").val(""); - $("#ep_imageId").val(""); - $("#ep_commonImageId").val(""); - $("#ep_commonSpecId").val(""); - $("#ep_specId").val(""); - - // 직접 입력 필드들 초기화 - $("#ep_name").val(""); - $("#ep_description").val(""); - $("#ep_imageId_input").val(""); - $("#ep_root_disk_type").val(""); - $("#ep_root_disk_size").val(""); - $("#ep_vm_add_cnt").val("1"); // 기본값 1로 설정 - $("#ep_data_disk").val(""); - $("#ep_command").val(""); - - // 모달들 초기화 - resetModals(); + // 1. 필수 필드 검증 + var requiredFields = [ + { id: '#ep_name', message: 'SubGroup name is required' }, + { id: '#ep_vm_add_cnt', message: 'VM count is required' }, + { id: '#ep_commonSpecId', message: 'Spec is required' }, + { id: '#ep_commonImageId', message: 'Image is required' } + ]; + + for (var field of requiredFields) { + if (!$(field.id).val() || $(field.id).val().trim() === '') { + alert(field.message); + $(field.id).focus(); + return; + } + } + + // 2. VM 개수 숫자 검증 + var vmAddCnt = $("#ep_vm_add_cnt").val(); + if (isNaN(vmAddCnt) || parseInt(vmAddCnt) < 1) { + alert('VM count must be a positive number'); + $("#ep_vm_add_cnt").focus(); + return; + } + + // express 는 common resource를 하므로 별도로 처리(connection, spec만) + $("#p_provider").val($("#ep_provider").val()) + $("#p_connectionName").val($("#ep_connectionName").val()) + $("#p_name").val($("#ep_name").val()) + $("#p_description").val($("#ep_description").val()) + $("#p_imageId").val($("#ep_imageId").val()) + $("#p_commonImageId").val($("#ep_commonImageId").val()) + $("#ep_imageId_input").val($("#ep_imageId").val()) // 이미지 입력 필드도 업데이트 + $("#p_commonSpecId").val($("#ep_commonSpecId").val()) + $("#p_root_disk_type").val($("#ep_root_disk_type").val()) + $("#p_root_disk_size").val($("#ep_root_disk_size").val()) + $("#p_specId").val($("#ep_specId").val()) + $("#p_command").val($("#ep_command").val()) + // ep_vm_add_cnt가 비어있으면 기본값 1로 설정 + var vmAddCnt = $("#ep_vm_add_cnt").val(); + if (!vmAddCnt || vmAddCnt.trim() === "") { + vmAddCnt = "1"; + } + $("#p_subGroupSize").val(vmAddCnt) + $("#p_vm_cnt").val(vmAddCnt) + + // commonSpec 으로 set 해야하므로 재설정 + var express_form = {} + express_form["provider"] = $("#ep_provider").val(); // provider 추가 + express_form["connectionName"] = $("#ep_connectionName").val(); // connectionName 추가 + express_form["name"] = $("#p_name").val(); + express_form["description"] = $("#p_description").val(); + express_form["subGroupSize"] = $("#p_subGroupSize").val(); + express_form["rootDiskSize"] = $("#p_root_disk_size").val(); + express_form["rootDiskType"] = $("#p_root_disk_type").val(); + express_form["rootDiskType"] = $("#p_root_disk_type").val(); + express_form["commonSpec"] = $("#p_commonSpecId").val(); + express_form["commonImage"] = $("#p_commonImageId").val(); + express_form["imageId"] = $("#p_imageId").val(); // imageId 추가 + express_form["specId"] = $("#p_specId").val(); // specId 추가 + express_form["command"] = $("#p_command").val(); + + var server_name = express_form.name; + var server_cnt = parseInt(express_form.subGroupSize); + + // 수정 모드인지 신규 추가 모드인지 판단 + if (currentEditingIndex >= 0 && currentEditingIndex < Express_Server_Config_Arr.length) { + // 수정 모드: 기존 데이터를 업데이트 + Express_Server_Config_Arr[currentEditingIndex] = express_form; + + // 리스트 아이템 텍스트 업데이트 + var vmEleId = "vm"; + if (!isVm) { + vmEleId = "mci"; + } + var displayServerCnt = '(' + server_cnt + ')'; + var listItem = $("#" + vmEleId + "_server_list li").eq(currentEditingIndex + 1); // +1은 plusIcon 때문 + listItem.text(server_name + displayServerCnt); + + // 수정 모드 플래그 초기화 + currentEditingIndex = -1; + } else { + // 신규 추가 모드: 배열에 추가하고 리스트에 추가 + var add_server_html = ""; + + Express_Server_Config_Arr.push(express_form); + + var displayServerCnt = '(' + server_cnt + ')'; + add_server_html += '
  • ' + + server_name + displayServerCnt + + '
  • '; + + var vmEleId = "vm"; + if (!isVm) { + vmEleId = "mci"; + } + $("#" + vmEleId + "_plusVmIcon").remove(); + $("#" + vmEleId + "_server_list").append(add_server_html); + $("#" + vmEleId + "_server_list").prepend(getPlusVm(vmEleId)); + + express_data_cnt++; + } + + // 서버 입력 폼 숨기기 + var div = document.getElementById("server_configuration"); + webconsolejs["partials/layout/navigatePages"].toggleSubElement(div); + + // 폼 초기화 - 모든 입력 필드 초기화 + $("#express_form").each(function () { + this.reset(); + }); + + // 숨겨진 필드들 초기화 + $("#ep_provider").val(""); + $("#ep_connectionName").val(""); + $("#ep_imageId").val(""); + $("#ep_commonImageId").val(""); + $("#ep_commonSpecId").val(""); + $("#ep_specId").val(""); + + // 직접 입력 필드들 초기화 + $("#ep_name").val(""); + $("#ep_description").val(""); + $("#ep_imageId_input").val(""); + $("#ep_root_disk_type").val(""); + $("#ep_root_disk_size").val(""); + $("#ep_vm_add_cnt").val("1"); // 기본값 1로 설정 + $("#ep_data_disk").val(""); + $("#ep_command").val(""); + + // 모달들 초기화 + resetModals(); } // 모달들 초기화 함수 @@ -536,16 +564,54 @@ function resetModals() { } export function view_express(cnt) { - // var select_form_data = Simple_Server_Config_Arr[cnt] - // $(".express_servers_config").addClass("active") - // $(".simple_servers_config").removeClass("active") - // $(".expert_servers_config").removeClass("active") - // $(".import_servers_config").removeClass("active") - - var div = document.getElementById("server_configuration"); - webconsolejs["partials/layout/navigatePages"].toggleElement(div) - - + // SubGroup 리스트 아이템 클릭 시 해당 서버 정보를 폼에 채워서 수정 모드로 전환 + + currentEditingIndex = parseInt(cnt); // 수정 모드 플래그 설정 + + if (currentEditingIndex < 0 || currentEditingIndex >= Express_Server_Config_Arr.length) { + console.error('Invalid server index:', currentEditingIndex); + return; + } + + var select_form_data = Express_Server_Config_Arr[currentEditingIndex]; + + // 폼 필드에 기존 데이터 채우기 + $("#ep_name").val(select_form_data.name || ""); + $("#ep_description").val(select_form_data.description || ""); + $("#ep_vm_add_cnt").val(select_form_data.subGroupSize || "1"); + $("#ep_root_disk_type").val(select_form_data.rootDiskType || ""); + $("#ep_root_disk_size").val(select_form_data.rootDiskSize || ""); + $("#ep_command").val(select_form_data.command || ""); + + // 숨겨진 필드들 (commonSpec, connectionName 등) + $("#ep_provider").val(select_form_data.provider || ""); + $("#ep_connectionName").val(select_form_data.connectionName || ""); + $("#ep_commonSpecId").val(select_form_data.commonSpec || ""); + $("#ep_specId").val(select_form_data.specId || ""); + $("#ep_commonImageId").val(select_form_data.commonImage || ""); + $("#ep_imageId").val(select_form_data.imageId || ""); + $("#ep_imageId_input").val(select_form_data.commonImage || select_form_data.imageId || ""); + + // p_* 필드들도 함께 설정 (호환성) + $("#p_provider").val(select_form_data.provider || ""); + $("#p_connectionName").val(select_form_data.connectionName || ""); + $("#p_name").val(select_form_data.name || ""); + $("#p_description").val(select_form_data.description || ""); + $("#p_imageId").val(select_form_data.imageId || ""); + $("#p_commonImageId").val(select_form_data.commonImage || ""); + $("#p_commonSpecId").val(select_form_data.commonSpec || ""); + $("#p_root_disk_type").val(select_form_data.rootDiskType || ""); + $("#p_root_disk_size").val(select_form_data.rootDiskSize || ""); + $("#p_specId").val(select_form_data.specId || ""); + $("#p_command").val(select_form_data.command || ""); + $("#p_subGroupSize").val(select_form_data.subGroupSize || "1"); + $("#p_vm_cnt").val(select_form_data.subGroupSize || "1"); + + // 서버 입력 폼 표시 + var div = document.getElementById("server_configuration"); + if (!div.classList.contains("active")) { + webconsolejs["partials/layout/navigatePages"].toggleSubElement(div); + } } @@ -726,6 +792,36 @@ export async function createMciDynamic() { if (validationResult && validationResult.status === 200) { const reviewData = validationResult.data.responseData; + // overallStatus 우선 체크 - Error 상태 처리 + if (reviewData.overallStatus === "Error" || !reviewData.creationViable) { + // 오류 정보 수집 + let errorMessage = `MCI 생성 오류\n\n${reviewData.overallMessage}\n\n`; + + if (reviewData.vmReviews && reviewData.vmReviews.length > 0) { + reviewData.vmReviews.forEach(vm => { + if (vm.status === "Error" && vm.errors) { + errorMessage += `VM: ${vm.vmName} (SubGroup Size: ${vm.subGroupSize})\n`; + errorMessage += `Provider: ${vm.providerName}\n`; + errorMessage += `Region: ${vm.regionName}\n`; + + if (vm.imageValidation && vm.imageValidation.resourceId) { + errorMessage += `Image: ${vm.imageValidation.resourceId}\n`; + } + + errorMessage += `\n오류:\n`; + vm.errors.forEach(err => { + errorMessage += `- ${err}\n`; + }); + errorMessage += `\n`; + } + }); + } + + // Toast로 표시 + webconsolejs["common/util"].showToast(errorMessage, 'error', 8000); + return; + } + // 검증 결과에 따른 처리 if (reviewData.creationViable) { if (reviewData.overallStatus === "Ready") { @@ -737,13 +833,7 @@ export async function createMciDynamic() { if (confirm(confirmMessage)) { webconsolejs["common/api/services/mci_api"].mciDynamic(mciName, mciDesc, Express_Server_Config_Arr, selectedNsId, policyOnPartialFailure); } - } else { - // Error 상태 - alert(`MCI 생성 검증 실패:\n${reviewData.overallMessage}`); } - } else { - // 생성 불가능 - alert(`MCI 생성이 불가능합니다:\n${reviewData.overallMessage}`); } } else { // API 호출 실패 diff --git a/front/assets/js/partials/operation/manage/pmk_serverrecommendation.js b/front/assets/js/partials/operation/manage/pmk_serverrecommendation.js index a0fe4f57..cc472767 100644 --- a/front/assets/js/partials/operation/manage/pmk_serverrecommendation.js +++ b/front/assets/js/partials/operation/manage/pmk_serverrecommendation.js @@ -74,17 +74,26 @@ function initRecommendSpecTablePmk() { headerSort: true, maxWidth: 100, }, - { - title: "REGION", - field: "regionName", - vertAlign: "middle" - }, - { - title: "PRICE", - field: "costPerHour", - vertAlign: "middle", - hozAlign: "center", - }, + { + title: "REGION", + field: "regionName", + vertAlign: "middle" + }, + { + title: "SPEC NAME", + field: "cspSpecName", + vertAlign: "middle", + hozAlign: "left", + minWidth: 200, + headerSort: true, + tooltip: true + }, + { + title: "PRICE", + field: "costPerHour", + vertAlign: "middle", + hozAlign: "center", + }, { title: "MEMORY", field: "memoryGiB", @@ -92,14 +101,14 @@ function initRecommendSpecTablePmk() { hozAlign: "center", maxWidth: 150, }, - { - title: "VCPU", - field: "vCPU", - vertAlign: "middle", - hozAlign: "center", - headerHozAlign: "center", - maxWidth: 135, - } + { + title: "VCPU", + field: "vCPU", + vertAlign: "middle", + hozAlign: "center", + headerHozAlign: "center", + maxWidth: 80, + } ]; const tableElement = document.getElementById("spec-table-pmk"); diff --git a/front/assets/js/partials/operation/manage/serverrecommendation.js b/front/assets/js/partials/operation/manage/serverrecommendation.js index f3de82c8..bb2b7fa6 100644 --- a/front/assets/js/partials/operation/manage/serverrecommendation.js +++ b/front/assets/js/partials/operation/manage/serverrecommendation.js @@ -72,17 +72,26 @@ function initRecommendSpecTable() { headerSort: true, maxWidth: 100, }, - { - title: "REGION", - field: "regionName", - vertAlign: "middle" - }, - { - title: "PRICE", - field: "costPerHour", - vertAlign: "middle", - hozAlign: "center", - }, + { + title: "REGION", + field: "regionName", + vertAlign: "middle" + }, + { + title: "SPEC NAME", + field: "cspSpecName", + vertAlign: "middle", + hozAlign: "left", + minWidth: 200, + headerSort: true, + tooltip: true + }, + { + title: "PRICE", + field: "costPerHour", + vertAlign: "middle", + hozAlign: "center", + }, { title: "MEMORY", field: "memoryGiB", @@ -90,14 +99,14 @@ function initRecommendSpecTable() { hozAlign: "center", maxWidth: 150, }, - { - title: "VCPU", - field: "vCPU", - vertAlign: "middle", - hozAlign: "center", - headerHozAlign: "center", - maxWidth: 135, - } + { + title: "VCPU", + field: "vCPU", + vertAlign: "middle", + hozAlign: "center", + headerHozAlign: "center", + maxWidth: 80, + } ]; //recommendTable = setSpecTabulator("spec-table", tableObjParams, columns); diff --git a/front/templates/partials/operation/manage/_imagerecommendation.html b/front/templates/partials/operation/manage/_imagerecommendation.html index ea71528b..47720840 100644 --- a/front/templates/partials/operation/manage/_imagerecommendation.html +++ b/front/templates/partials/operation/manage/_imagerecommendation.html @@ -22,6 +22,7 @@
    +
    @@ -49,7 +50,6 @@ type="text" class="form-control" id="image-os-architecture" - readonly placeholder="OS Architecture from spec selection" />
    diff --git a/front/templates/partials/operation/manage/_pmk_imagerecommendation.html b/front/templates/partials/operation/manage/_pmk_imagerecommendation.html index 3dd8fdee..ff38632f 100644 --- a/front/templates/partials/operation/manage/_pmk_imagerecommendation.html +++ b/front/templates/partials/operation/manage/_pmk_imagerecommendation.html @@ -48,7 +48,6 @@ type="text" class="form-control" id="image-os-architecture-pmk" - readonly placeholder="OS Architecture from spec selection" />
    From 0eef551baa5460a0d76ef393fe72470219a5e190 Mon Sep 17 00:00:00 2001 From: dogfootman Date: Mon, 10 Nov 2025 09:32:28 +0900 Subject: [PATCH 7/7] Refactor image recommendation API parameters - Simplified parameter structure for API calls in getRecommendImageInfoPmk function. - Added maxResults parameter to limit the number of results returned. - Made osType an optional parameter, ensuring it is only included if provided. - Improved code readability by clarifying comments and organizing parameter assignments. --- .../operation/manage/pmk_imagerecommendation.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/front/assets/js/partials/operation/manage/pmk_imagerecommendation.js b/front/assets/js/partials/operation/manage/pmk_imagerecommendation.js index b585c627..752c57e9 100644 --- a/front/assets/js/partials/operation/manage/pmk_imagerecommendation.js +++ b/front/assets/js/partials/operation/manage/pmk_imagerecommendation.js @@ -204,13 +204,20 @@ export async function getRecommendImageInfoPmk() { var selectedWorkspaceProject = await webconsolejs["partials/layout/navbar"].workspaceProjectInit(); var nsId = selectedWorkspaceProject.nsId; - // API 호출을 위한 파라미터 구성 (간단화된 방식) + // API 호출을 위한 파라미터 구성 var searchParams = { - matchedSpecId: specId, // spec ID를 사용하여 provider, region, architecture 자동 매칭 - osType: osType, - isKubernetesImage: true // PMK는 Kubernetes 이미지 필수 + providerName: provider, // 개별 전달 + regionName: region, // 개별 전달 + matchedSpecId: specId, // 유지 + isKubernetesImage: true, // PMK 필수 + maxResults: 100 }; + // 선택적 파라미터 + if (osType && osType.trim() !== "") { + searchParams.osType = osType.trim(); + } + // GPU 이미지가 필요한 경우에만 추가 if (isGPUImage === "true") { searchParams.isGPUImage = true;