diff --git a/README.md b/README.md index 9f49c43f..c764c061 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ $ docker-compose up --build -d ## This warning sign is a natural occurrence when running an existing MCIAMMANAGER with docker components. WARNING: Found orphan containers (mciammanager, mciammanager-keycloak, mciammanager-nginx, mciammanager-certbot) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up. Building mcwebconsole -Step 1/32 : FROM golang:1.22.3-alpine AS builder +Step 1/32 : FROM golang:1.25-alpine AS builder ---> 0594d7786b7c Step 2/32 : RUN apk add --no-cache gcc libc-dev musl-dev curl npm wget ---> Using cache @@ -151,7 +151,7 @@ MC-WEB-CONSOLE has been successfully deployed if the screen below is visible dur **[설치 환경]** -mc-web-console은 1.19 이상의 Go 버전이 설치된 다양한 환경에서 실행 가능하지만 최종 동작을 검증한 OS는 Ubuntu 22.0.4입니다. +mc-web-console은 1.25 이상의 Go 버전이 설치된 다양한 환경에서 실행 가능하지만 최종 동작을 검증한 OS는 Ubuntu 22.0.4입니다. **[의존성]** @@ -167,7 +167,7 @@ mc-web-console은 내부적으로 mc-iam-manager & mc-infra-manager의 개방형 - Git 설치 - `$ sudo apt update` - `$ sudo apt install git` -- Go 1.19 이상의 버전 설치 ( 공식 문서 참고 ) +- Go 1.25 이상의 버전 설치 ( 공식 문서 참고 ) - https://go.dev/doc/install - mc-web-console 설치 diff --git a/api/Dockerfile.mcwebconsoleapi b/api/Dockerfile.mcwebconsoleapi index f29be297..60644b7a 100644 --- a/api/Dockerfile.mcwebconsoleapi +++ b/api/Dockerfile.mcwebconsoleapi @@ -1,5 +1,5 @@ ## Stage 1 - Go Build Env -FROM golang:1.23-alpine AS build +FROM golang:1.25-alpine AS build RUN apk add --no-cache gcc libc-dev musl-dev curl wget diff --git a/api/go.mod b/api/go.mod index 269fbdba..268dde50 100644 --- a/api/go.mod +++ b/api/go.mod @@ -1,8 +1,6 @@ module mc_web_console_api -go 1.22.0 - -toolchain go1.23.0 +go 1.25.0 require ( github.com/gobuffalo/buffalo v1.1.0 diff --git a/doc/fix/pmk-nodegroup-status-validation.md b/doc/fix/pmk-nodegroup-status-validation.md new file mode 100644 index 00000000..9a49ee44 --- /dev/null +++ b/doc/fix/pmk-nodegroup-status-validation.md @@ -0,0 +1,424 @@ +# PMK NodeGroup 추가 상태 검증 기능 + +## 개요 +PMK Cluster의 NodeGroup을 추가할 때, Cluster의 상태를 확인하여 Active 상태일 때만 NodeGroup 추가가 가능하도록 검증 로직을 추가한 기능입니다. + +## 문제점 +기존에는 PMK Cluster의 상태와 관계없이 NodeGroup 추가를 시도할 수 있었습니다. 이로 인해: +- Creating 상태의 Cluster에 NodeGroup을 추가하려고 시도하면 실패 +- Deleting 상태의 Cluster에 NodeGroup을 추가하려고 시도하면 실패 +- 사용자에게 명확한 피드백이 제공되지 않음 + +## 해결 방법 +`addNewNodeGroup()` 함수에 Cluster 상태 검증 로직을 추가하여, Active 상태일 때만 NodeGroup 추가 폼이 표시되도록 구현했습니다. + +## 구현 내용 + +### 1. Cluster 선택 여부 확인 +- `selectedPmkObj`가 존재하는지 확인 +- 선택되지 않은 경우 에러 메시지 표시 + +### 2. Cluster 상태 조회 +- **API**: `GetK8sCluster` (operationId) +- **함수**: `webconsolejs["common/api/services/pmk_api"].getCluster(nsId, clusterId)` +- **Response Path**: `response.data.responseData.spiderViewK8sClusterDetail.Status` + +### 3. 상태 검증 로직 +- **허용되는 상태**: `Active` +- **거부되는 상태**: `Creating`, `Deleting` +- 거부 시 메시지: "NodeGroup can only be added when the cluster is in Active status. Current status is: {status}" + +### 4. 에러 처리 +- API 호출 실패 시 에러 메시지 표시 +- try-catch 블록으로 예외 상황 처리 +- 모든 에러 상황에서 사용자에게 명확한 피드백 제공 + +## 변경된 파일 + +### 1. front/assets/js/partials/operation/manage/clustercreate.js +**함수**: `addNewNodeGroup()` +**변경 내용**: +```javascript +// 추가된 검증 로직 +1. Cluster 선택 여부 확인 +2. GetK8sCluster API 호출하여 현재 상태 조회 +3. Status 값이 'Active'인 경우만 기존 로직 실행 +4. Active가 아닌 경우 commonShowDefaultModal로 에러 메시지 표시 +``` + +**위치**: 394번째 줄 ~ 459번째 줄 + +## API 정보 + +### GetK8sCluster +- **Method**: GET +- **Resource Path**: `/ns/{nsId}/k8sCluster/{k8sClusterId}` +- **Description**: K8s Cluster 정보를 조회합니다 +- **Parameters**: + - `nsId`: Namespace ID + - `k8sClusterId`: K8s Cluster ID +- **Response**: + ```json + { + "status": 200, + "data": { + "responseData": { + "spiderViewK8sClusterDetail": { + "Status": "Active" | "Creating" | "Deleting" + } + } + } + } + ``` + +## 테스트 시나리오 + +### Case 1: Cluster가 선택되지 않은 경우 +1. PMK 목록 페이지에서 Cluster를 선택하지 않음 +2. "Add NodeGroup" 버튼 클릭 +3. **예상 결과**: "Cluster Selection Required" 모달 표시 + +### Case 2: Cluster 상태가 Creating인 경우 +1. Creating 상태의 Cluster 선택 +2. "Add NodeGroup" 버튼 클릭 +3. **예상 결과**: "NodeGroup can only be added when the cluster is in Active status. Current status is: Creating" 모달 표시 +4. NodeGroup 추가 폼이 표시되지 않음 + +### Case 3: Cluster 상태가 Deleting인 경우 +1. Deleting 상태의 Cluster 선택 +2. "Add NodeGroup" 버튼 클릭 +3. **예상 결과**: "NodeGroup can only be added when the cluster is in Active status. Current status is: Deleting" 모달 표시 +4. NodeGroup 추가 폼이 표시되지 않음 + +### Case 4: Cluster 상태가 Active인 경우 +1. Active 상태의 Cluster 선택 +2. "Add NodeGroup" 버튼 클릭 +3. **예상 결과**: NodeGroup 추가 폼이 정상적으로 표시됨 +4. 기존 기능과 동일하게 동작 + +### Case 5: API 호출 실패 +1. 네트워크 문제 또는 API 오류 발생 +2. "Add NodeGroup" 버튼 클릭 +3. **예상 결과**: "Failed to retrieve cluster status. Please try again." 또는 "An error occurred while checking cluster status. Please try again." 모달 표시 + +## 사용자 영향 +- **긍정적 영향**: + - Cluster 상태에 따른 명확한 피드백 제공 + - 불필요한 API 호출 실패 방지 + - 사용자 경험 개선 +- **부정적 영향**: + - NodeGroup 추가 시 약간의 지연 발생 (API 호출로 인한) + - 하지만 사용성 개선이 더 큰 이점 + +## 브랜치 정보 +- **Base Branch**: develop +- **Feature Branch**: feature/pmk-nodegroup-status-validation + +## 관련 이슈 +- PMK NodeGroup 추가 시 상태 검증 필요 + +## 작성자 +- Date: 2025-11-11 +- Feature: PMK NodeGroup Status Validation + +--- + +## Known Issues and Fixes + +### Issue 1: TypeError when clicking on Cluster +**Problem**: +- Cluster 목록에서 Cluster를 클릭하면 `TypeError: Cannot read properties of undefined (reading 'status')` 에러 발생 +- 특히 Creating 상태의 Cluster를 클릭할 때 발생 +- `getCluster()` API가 `undefined`를 반환하는 경우 처리되지 않음 + +**Root Cause**: +1. `getCluster()` 함수에서 validation 실패 시 `return;`만 실행하여 `undefined` 반환 +2. `getSelectedPmkData()`에서 `pmkResp.status` 접근 시 null check 없음 +3. API 호출 실패 시 예외 처리 누락 +4. alert 사용으로 사용자 경험 저하 + +**Solution (2025-11-11)**: + +#### 1. getSelectedPmkData() 함수 개선 +**File**: `front/assets/js/pages/operation/manage/pmk.js` + +**Changes**: +- `pmkResp` 존재 여부 체크 추가 +- try-catch 블록으로 예외 처리 +- Toast 알림으로 사용자 피드백 개선 + +```javascript +export async function getSelectedPmkData() { + if (currentPmkId != undefined && currentPmkId != "") { + var selectedNsId = selectedWorkspaceProject.nsId; + + try { + var pmkResp = await webconsolejs["common/api/services/pmk_api"].getCluster(selectedNsId, currentPmkId); + + // Check if pmkResp exists + if (!pmkResp) { + webconsolejs["common/util"].showToast( + 'Failed to retrieve cluster information. The cluster may not exist or the API is not responding.', + 'error', + 5000 + ); + return; + } + + // Check response status + if (pmkResp.status != 200) { + webconsolejs["common/util"].showToast( + 'Failed to load cluster information. Status: ' + (pmkResp.status || 'Unknown'), + 'error', + 5000 + ); + return; + } + + // SET PMK Info page + setPmkInfoData(pmkResp.data); + // ... 나머지 코드 + } catch (error) { + console.error('Error in getSelectedPmkData:', error); + webconsolejs["common/util"].showToast( + 'An error occurred while loading cluster information. Please try again.', + 'error', + 5000 + ); + } + } +} +``` + +#### 2. getCluster() 함수 개선 +**File**: `front/assets/js/common/api/services/pmk_api.js` + +**Changes**: +- alert → Modal 변경으로 UX 개선 +- 에러 객체 반환 (undefined 대신) +- try-catch로 API 호출 실패 처리 +- 일관된 에러 응답 형식 + +```javascript +export async function getCluster(nsId, clusterId) { + // Validation: Check nsId + if (!nsId || nsId === "") { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Project Selection Required', + 'Please select a project first before viewing cluster details.' + ); + return { status: 400, error: 'No project selected' }; + } + + // Validation: Check clusterId + if (!clusterId || clusterId === "") { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Cluster Selection Required', + 'Please select a cluster first.' + ); + return { status: 400, error: 'No cluster selected' }; + } + + // API call with error handling + try { + const data = { + pathParams: { + nsId: nsId, + k8sClusterId: clusterId + } + }; + + var controller = "/api/" + "mc-infra-manager/" + "Getk8scluster"; + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); + + return response; + } catch (error) { + console.error('Error in getCluster API call:', error); + return { + status: 500, + error: 'API call failed: ' + (error.message || 'Unknown error') + }; + } +} +``` + +### Error Handling Improvements + +| Scenario | Before | After | +|----------|--------|-------| +| Project not selected | alert → undefined → TypeError | Modal → Error object → Toast notification | +| Cluster not selected | alert → undefined → TypeError | Modal → Error object → Toast notification | +| API call fails | undefined → TypeError | Error object → Toast notification | +| Creating status Cluster | TypeError occurs | Normal handling + Status display | + +### Additional Test Scenarios + +#### Case 6: Project가 선택되지 않은 상태에서 Cluster 클릭 +1. Project 선택하지 않음 +2. Cluster 클릭 +3. **예상 결과**: "Project Selection Required" 모달 표시 +4. Toast로 "Failed to load cluster information" 표시 + +#### Case 7: Creating 상태 Cluster 클릭 +1. GetAllK8sCluster로 목록 조회 +2. Creating 상태의 Cluster 클릭 +3. **예상 결과**: + - 에러 없이 정상 처리 + - Cluster 정보 표시 (Status: Creating) + - 데이터가 없으면 Toast로 에러 알림 + +#### Case 8: API 호출 중 네트워크 에러 +1. Cluster 클릭 +2. 네트워크 연결 끊김 또는 API 서버 응답 없음 +3. **예상 결과**: + - Toast로 "An error occurred while loading cluster information" 표시 + - 콘솔에 에러 로그 출력 + +## User Feedback Methods + +### Toast Notifications +- **Function**: `webconsolejs["common/util"].showToast(message, type, duration)` +- **Types**: 'info', 'success', 'error', 'warning' +- **Duration**: 5000ms (5 seconds) +- **Position**: Top-right corner +- **Usage**: For non-blocking error notifications + +### Modal Dialogs +- **Function**: `webconsolejs['partials/layout/modal'].commonShowDefaultModal(title, content)` +- **Usage**: For critical errors requiring user acknowledgment +- **Actions**: OK button to dismiss + +## Change History + +### 2025-11-11 - Initial Implementation +- Added cluster status validation for NodeGroup addition +- Only Active clusters allow NodeGroup addition + +### 2025-11-11 - Error Handling Improvements +- Fixed TypeError when clicking clusters +- Replaced alert with modal for better UX +- Added toast notifications for error scenarios +- Improved API error handling with try-catch blocks +- Return error objects instead of undefined + +### 2025-11-11 - UI/UX Improvements (Button State Management) +- Changed Add NodeGroup button behavior to prevent form flash +- Implemented button enable/disable based on cluster status +- Button only enabled when cluster is Active +- Added tooltip to show current cluster status +- Removed href to prevent automatic form display +- Simplified addNewNodeGroup() function by removing redundant status checks +- Following existing pattern: updateClusterRemoteCmdButtonState() + +## UI/UX Improvements + +### Problem +이전에는 Add NodeGroup 버튼을 클릭하면: +1. `href="#addnode"`로 인해 URL이 변경되고 폼이 즉시 표시됨 +2. 그 후 상태 체크가 실행됨 +3. 상태가 Active가 아니면 모달이 표시되고 폼이 사라짐 +4. 사용자 혼란 발생 (폼이 잠깐 보였다가 사라짐) + +### Solution: Button State Management +**패턴**: Cluster Terminal 버튼과 동일한 방식 사용 + +#### 구현 내용 + +1. **updateAddNodeGroupButtonState() 함수 추가** + - **File**: `front/assets/js/pages/operation/manage/pmk.js` + - **Location**: Line 1789-1811 + - Cluster 상태에 따라 버튼 활성화/비활성화 + - Active 상태일 때만 버튼 활성화 + - Tooltip으로 현재 상태 표시 + +```javascript +function updateAddNodeGroupButtonState(clusterStatus) { + const addNodeGroupBtns = document.querySelectorAll('a[onclick*="addNewNodeGroup"]'); + + addNodeGroupBtns.forEach(btn => { + if (!currentPmkId) { + btn.classList.add('disabled'); + btn.style.pointerEvents = 'none'; + btn.title = 'Please select a cluster first'; + } else if (clusterStatus === 'Active') { + btn.classList.remove('disabled'); + btn.style.pointerEvents = 'auto'; + btn.title = 'Add NodeGroup to this cluster'; + } else { + btn.classList.add('disabled'); + btn.style.pointerEvents = 'none'; + btn.title = 'NodeGroup can only be added when cluster is Active. Current status: ' + clusterStatus; + } + }); +} +``` + +2. **setPmkInfoData() 함수 수정** + - **File**: `front/assets/js/pages/operation/manage/pmk.js` + - **Location**: Line 343 + - Cluster 정보 표시 후 버튼 상태 업데이트 + +```javascript +// Add NodeGroup 버튼 상태 업데이트 +updateAddNodeGroupButtonState(pmkStatus); +``` + +3. **addNewNodeGroup() 함수 단순화** + - **File**: `front/assets/js/partials/operation/manage/clustercreate.js` + - **Removed**: Lines 425-459 (상태 체크 로직 전체 제거) + - Cluster 선택 여부만 확인 + - 버튼이 이미 비활성화되어 있으므로 상태 체크 불필요 + +4. **HTML 수정** + - **File**: `front/templates/partials/operation/manage/_nodegrouplist_status.html` + - **Changes**: + - `href="#addnode"` 제거 → 폼 자동 표시 방지 + - `class="btn btn-outline-primary disabled"` 추가 + - `style="pointer-events: none;"` 추가 + - `title="Please select an Active cluster first"` 추가 + +```html + + + + + +``` + +### User Experience Comparison + +| 시나리오 | 이전 동작 | 개선 후 동작 | +|---------|----------|-------------| +| Cluster 미선택 | 버튼 활성화 → 클릭 → 모달 표시 | 버튼 비활성화 (tooltip: "Please select a cluster first") | +| Creating 상태 | 버튼 활성화 → 클릭 → 폼 표시 → 모달 → 폼 사라짐 | 버튼 비활성화 (tooltip: "Current status: Creating") | +| Deleting 상태 | 버튼 활성화 → 클릭 → 폼 표시 → 모달 → 폼 사라짐 | 버튼 비활성화 (tooltip: "Current status: Deleting") | +| Active 상태 | 버튼 활성화 → 클릭 → API 호출 → 상태 체크 → 폼 표시 | 버튼 활성화 → 클릭 → 바로 폼 표시 | + +### Benefits + +1. **예방적 접근**: 불가능한 상태에서 시도를 미연에 방지 +2. **명확한 피드백**: Tooltip으로 버튼이 비활성화된 이유 즉시 확인 가능 +3. **성능 개선**: 불필요한 API 호출 및 validation 제거 +4. **일관성**: 기존 Terminal 버튼들과 동일한 패턴 사용 +5. **혼란 제거**: 폼이 잠깐 나타났다가 사라지는 현상 완전히 제거 + +### Pattern Consistency + +이 구현은 기존 코드와 일관성을 유지합니다: +- `updateClusterRemoteCmdButtonState()` - Cluster Terminal 버튼 +- `updateMciRemoteCmdButtonState()` - MCI Terminal 버튼 +- `updateSubGroupRemoteCmdButtonState()` - SubGroup Terminal 버튼 +- `updateAddNodeGroupButtonState()` - Add NodeGroup 버튼 (신규) + diff --git a/front/Dockerfile.mcwebconsolefront b/front/Dockerfile.mcwebconsolefront index f2cf0ae9..a761d444 100644 --- a/front/Dockerfile.mcwebconsolefront +++ b/front/Dockerfile.mcwebconsolefront @@ -1,5 +1,5 @@ ## Stage 1 - Go Build Env -FROM golang:1.23-alpine AS build +FROM golang:1.25-alpine AS build RUN apk add --no-cache gcc libc-dev musl-dev curl npm wget RUN npm install --global yarn diff --git a/front/assets/js/common/api/services/pmk_api.js b/front/assets/js/common/api/services/pmk_api.js index 109c5bec..30da3227 100644 --- a/front/assets/js/common/api/services/pmk_api.js +++ b/front/assets/js/common/api/services/pmk_api.js @@ -25,25 +25,49 @@ export async function getClusterList(nsId) { } export async function getCluster(nsId, clusterId) { - if (nsId == "" || nsId == undefined || clusterId == undefined || clusterId == "") { - alert(" undefined nsId: " + nsId + " clusterId " + clusterId); - return; + // Validation: Check nsId + if (!nsId || nsId === "") { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Project Selection Required', + 'Please select a project first before viewing cluster details.' + ); + return { status: 400, error: 'No project selected' }; } - const data = { - pathParams: { - nsId: nsId, - k8sClusterId: clusterId - } + + // Validation: Check clusterId + if (!clusterId || clusterId === "") { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Cluster Selection Required', + 'Please select a cluster first.' + ); + return { status: 400, error: 'No cluster selected' }; } - var controller = "/api/" + "mc-infra-manager/" + "Getk8scluster"; - const response = await webconsolejs["common/api/http"].commonAPIPost( - controller, - data - ); + // API call with error handling + try { + const data = { + pathParams: { + nsId: nsId, + k8sClusterId: clusterId + } + }; + + var controller = "/api/" + "mc-infra-manager/" + "Getk8scluster"; + const response = await webconsolejs["common/api/http"].commonAPIPost( + controller, + data + ); - // error check를 위해 response를 return - return response + // error check를 위해 response를 return + return response; + } catch (error) { + console.error('Error in getCluster API call:', error); + // API 호출 실패 시에도 에러 객체 반환 + return { + status: 500, + error: 'API call failed: ' + (error.message || 'Unknown error') + }; + } } export async function CreateCluster(clusterName, selectedConnection, clusterVersion, selectedVpc, selectedSubnet, selectedSecurityGroup, Create_Cluster_Config_Arr, selectedNsId) { @@ -406,7 +430,7 @@ export async function createNode(k8sClusterId, nsId, Create_Node_Config_Arr) { } } -export async function getSshKey(nsId) { +export async function getSshKey(nsId, providerName) { if (nsId == "") { alert("Project has not set") @@ -419,6 +443,14 @@ export async function getSshKey(nsId) { }, }; + // Add provider filter if provided + if (providerName && providerName !== "") { + data.queryParams = { + filterKey: "providerName", + filterVal: providerName.toLowerCase() // e.g., "aws", "azure", "gcp" + }; + } + var controller = "/api/" + "mc-infra-manager/" + "Getallsshkey"; const response = webconsolejs["common/api/http"].commonAPIPost( controller, diff --git a/front/assets/js/pages/operation/manage/pmk.js b/front/assets/js/pages/operation/manage/pmk.js index 1c276d1e..1becbac5 100644 --- a/front/assets/js/pages/operation/manage/pmk.js +++ b/front/assets/js/pages/operation/manage/pmk.js @@ -149,6 +149,8 @@ function mappingTablePmkData(totalPmkListObj) { return { name: item.name, id: item.id, + description: item.description || "", + connectionName: item.connectionName || "N/A", resourceType: item.resourceType, systemLabel: item.systemLabel || "N/A", systemMessage: item.systemMessage || "N/A", @@ -171,21 +173,46 @@ export async function getSelectedPmkData() { if (currentPmkId != undefined && currentPmkId != "") { var selectedNsId = selectedWorkspaceProject.nsId; - var pmkResp = await webconsolejs["common/api/services/pmk_api"].getCluster(selectedNsId, currentPmkId) + try { + var pmkResp = await webconsolejs["common/api/services/pmk_api"].getCluster(selectedNsId, currentPmkId); + + // Check if pmkResp exists + if (!pmkResp) { + webconsolejs["common/util"].showToast( + 'Failed to retrieve cluster information. The cluster may not exist or the API is not responding.', + 'error', + 5000 + ); + return; + } - if (pmkResp.status != 200) { - // failed. // TODO : Error Popup 처리 - return; - } - // SET PMK Info page - setPmkInfoData(pmkResp.data) - - // Toggle PMK Info - var div = document.getElementById("cluster_info"); - const hasActiveClass = div.classList.contains("active"); - if (!hasActiveClass) { - // cluster_info 가 active면 toggle 필요 없음 - webconsolejs["partials/layout/navigatePages"].toggleElement(div) + // Check response status + if (pmkResp.status != 200) { + webconsolejs["common/util"].showToast( + 'Failed to load cluster information. Status: ' + (pmkResp.status || 'Unknown'), + 'error', + 5000 + ); + return; + } + + // SET PMK Info page + setPmkInfoData(pmkResp.data); + + // Toggle PMK Info + var div = document.getElementById("cluster_info"); + const hasActiveClass = div.classList.contains("active"); + if (!hasActiveClass) { + // cluster_info 가 active면 toggle 필요 없음 + webconsolejs["partials/layout/navigatePages"].toggleElement(div); + } + } catch (error) { + console.error('Error in getSelectedPmkData:', error); + webconsolejs["common/util"].showToast( + 'An error occurred while loading cluster information. Please try again.', + 'error', + 5000 + ); } } } @@ -260,12 +287,16 @@ function setPmkInfoData(pmkData) { var pmkNetwork = clusterDetailData?.Network || {}; var clusterProvider = clusterData.connectionConfig.providerName currentProvider = clusterProvider + + // pmkStatus를 try 블록 밖에서 선언 + var pmkStatus = "N/A"; + try { var pmkName = clusterData.name; var pmkID = clusterData.id var pmkVersion = clusterDetailData?.Version || "N/A"; - var pmkStatus = clusterDetailData?.Status || "N/A"; + pmkStatus = clusterDetailData?.Status || "N/A"; // 네트워크 정보 var pmkVpc = (pmkNetwork.VpcIID && pmkNetwork.VpcIID.SystemId) || "N/A"; @@ -309,6 +340,9 @@ function setPmkInfoData(pmkData) { if (Array.isArray(nodeGroupList) && nodeGroupList.length > 0) { displayNodeGroupStatusList(pmkID, clusterProvider, clusterData); } + + // Add NodeGroup 버튼 상태 업데이트 + updateAddNodeGroupButtonState(pmkStatus); } // pmk life cycle 변경 @@ -1759,4 +1793,28 @@ function updateClusterRemoteCmdButtonState() { clusterRemoteCmdBtn.title = 'Please select a Cluster first'; } } +} + +// Add NodeGroup 버튼 상태 업데이트 +function updateAddNodeGroupButtonState(clusterStatus) { + const addNodeGroupBtns = document.querySelectorAll('a[onclick*="addNewNodeGroup"]'); + + addNodeGroupBtns.forEach(btn => { + if (!currentPmkId) { + // Cluster가 선택되지 않은 경우 + btn.classList.add('disabled'); + btn.style.pointerEvents = 'none'; + btn.title = 'Please select a cluster first'; + } else if (clusterStatus === 'Active') { + // Active 상태인 경우 활성화 + btn.classList.remove('disabled'); + btn.style.pointerEvents = 'auto'; + btn.title = 'Add NodeGroup to this cluster'; + } else { + // Active가 아닌 경우 비활성화 + btn.classList.add('disabled'); + btn.style.pointerEvents = 'none'; + btn.title = 'NodeGroup can only be added when cluster is Active. Current status: ' + clusterStatus; + } + }); } \ No newline at end of file diff --git a/front/assets/js/partials/operation/manage/clustercreate.js b/front/assets/js/partials/operation/manage/clustercreate.js index 0114981c..4256414d 100644 --- a/front/assets/js/partials/operation/manage/clustercreate.js +++ b/front/assets/js/partials/operation/manage/clustercreate.js @@ -290,18 +290,25 @@ export async function displayNewNodeForm() { var selectedWorkspaceProject = await webconsolejs["partials/layout/navbar"].workspaceProjectInit(); var selectedNsId = selectedWorkspaceProject.nsId; - // getSSHKEY - var sshKeyList = await webconsolejs["common/api/services/pmk_api"].getSshKey(selectedNsId) - var mysshKeyList = sshKeyList.data.responseData.sshKey + // Get selected cluster's provider information for SSH Key filtering + var selectedCluster = webconsolejs["pages/operation/manage/pmk"].selectedPmkObj; + var clusterProvider = null; + if (selectedCluster && selectedCluster.length > 0) { + clusterProvider = selectedCluster[0].provider; // e.g., "aws", "azure", "gcp" + } + + // getSSHKEY with provider filter + var sshKeyList = await webconsolejs["common/api/services/pmk_api"].getSshKey(selectedNsId, clusterProvider); + var mysshKeyList = sshKeyList.data.responseData.sshKey; if (mysshKeyList && mysshKeyList.length > 0) { - var html = ''; - mysshKeyList.forEach(item => { - html += ''; - }); - - $("#node_sshkey").empty(); - $("#node_sshkey").append(html); - } else { + var html = ''; + mysshKeyList.forEach(item => { + html += ''; + }); + + $("#node_sshkey").empty(); + $("#node_sshkey").append(html); + } else { } //recommendVm으로 k8s spec @@ -391,47 +398,97 @@ export async function createNode() { webconsolejs["common/api/services/pmk_api"].createNode(k8sClusterId, selectedNsId, Create_Node_Config_Arr) } -export async function addNewNodeGroup() { - // isNode = false - - // var providerList = await webconsolejs["common/api/services/pmk_api"].getProviderList() - // // provider set - // await setProviderList(providerList) +// Extract region from connectionName +// e.g., "aws-ap-northeast-2" -> "[aws] aws-ap-northeast-2" +function extractRegionFromConnection(connectionName, provider) { + if (!connectionName || !provider) return ''; - // // call getRegion API - // var regionList = await webconsolejs["common/api/services/pmk_api"].getRegionList() - // // region set - // await setRegionList(regionList) - - // // call cloudconnection - // var connectionList = await webconsolejs["common/api/services/pmk_api"].getCloudConnection() - // // cloudconnection set - // await setCloudConnection(connectionList) + // Return in the format: [provider] connectionName + return '[' + provider + '] ' + connectionName; +} +export async function addNewNodeGroup() { Create_Cluster_Config_Arr = new Array(); Create_Node_Config_Arr = new Array(); - var selectedCluster = webconsolejs["pages/operation/manage/pmk"].selectedPmkObj - var cluster_name = selectedCluster[0].name - var cluster_desc = selectedCluster[0].description - var cluster_connection = selectedCluster[0].provider// 임시 - var cluster_vpc = selectedCluster[0].vpc - var cluster_subnet = selectedCluster[0].subnet - var cluster_securitygroup = selectedCluster[0].securitygroup - var cluster_version = selectedCluster[0].version - - $("#node_cluster_name").val(cluster_name) - $("#node_cluster_desc").val(cluster_desc) - // $("#node_cluster_connection").val(cluster_connection) - $("#node_cluster_cloudconnection").html(''); - // provider, region, connection, vpc, subnet, sg, cluster version 채워넣어 펼치기 - $("#node_cluster_vpc").html(''); - $("#node_cluster_subnet").html(''); - $("#node_cluster_sg").html(''); - $("#node_cluster_version").html(''); - - - isNodeGroup = true + var selectedCluster = webconsolejs["pages/operation/manage/pmk"].selectedPmkObj; + + // Validation: Check if cluster is selected + if (!selectedCluster || selectedCluster.length === 0) { + webconsolejs['partials/layout/modal'].commonShowDefaultModal( + 'Cluster Selection Required', + 'Please select a cluster first before adding a NodeGroup.' + ); + return; + } + + // Note: Cluster status check is handled by button state management + // The button is only enabled when cluster status is Active + // See updateAddNodeGroupButtonState() in pmk.js + + var cluster_name = selectedCluster[0].name; + var cluster_desc = selectedCluster[0].description; + var cluster_provider = selectedCluster[0].provider; + var cluster_connection = selectedCluster[0].connectionName; + var cluster_vpc = selectedCluster[0].vpc; + var cluster_subnet = selectedCluster[0].subnet; + var cluster_securitygroup = selectedCluster[0].securitygroup; + var cluster_version = selectedCluster[0].version; + + // Extract region from connectionName + var cluster_region = extractRegionFromConnection(cluster_connection, cluster_provider); + + // Set basic cluster information + $("#node_cluster_name").val(cluster_name); + $("#node_cluster_desc").val(cluster_desc); + + // Set Provider (fixed, single option, disabled - display only) + $("#node_cluster_provider").html( + '' + ); + $("#node_cluster_provider").prop('disabled', true); + + // Set Region (fixed, single option, disabled - display only) + $("#node_cluster_region").html( + '' + ); + $("#node_cluster_region").prop('disabled', true); + + // Set other fields + $("#node_cluster_cloudconnection").html( + '' + ); + $("#node_cluster_vpc").html( + '' + ); + $("#node_cluster_subnet").html( + '' + ); + $("#node_cluster_sg").html( + '' + ); + $("#node_cluster_version").html( + '' + ); + + // Navigate to Add NodeGroup section (following existing pattern) + window.location.hash = "#addnode"; + + isNodeGroup = true; } export async function addNewPmk() { diff --git a/front/go.mod b/front/go.mod index 6fa1737a..137a62a0 100644 --- a/front/go.mod +++ b/front/go.mod @@ -1,6 +1,6 @@ module front -go 1.21.0 +go 1.25.0 require ( github.com/gobuffalo/buffalo v1.1.0 diff --git a/front/templates/partials/operation/manage/_nodegrouplist_status.html b/front/templates/partials/operation/manage/_nodegrouplist_status.html index 91b5ef70..cb797ab1 100644 --- a/front/templates/partials/operation/manage/_nodegrouplist_status.html +++ b/front/templates/partials/operation/manage/_nodegrouplist_status.html @@ -332,8 +332,9 @@