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 @@