diff --git a/front/assets/js/common/api/services/workspace_api.js b/front/assets/js/common/api/services/workspace_api.js index c19e905b..2b7d2cb4 100644 --- a/front/assets/js/common/api/services/workspace_api.js +++ b/front/assets/js/common/api/services/workspace_api.js @@ -195,13 +195,20 @@ export function setPrjSelectBox(projectList, curProjectId) { // handle workspace -export async function createWorkspace(name, description) { +export async function createWorkspace(name, description, projects = null) { const controller = '/api/mc-iam-manager/CreateWorkspace' + var requestData = { + "name": name, + "description": description + }; + + // projects가 제공된 경우 배열 형태로 추가 + if (projects && Array.isArray(projects) && projects.length > 0) { + requestData.projects = projects.map(projectId => ({ "id": Number(projectId) })); + } + var data = { - request: { - "name": name, - "description": description - }, + request: requestData }; const response = await webconsolejs["common/api/http"].commonAPIPost(controller, data, null) try { @@ -252,7 +259,7 @@ export async function deleteWorkspaceById(wsId) { const controller = '/api/mc-iam-manager/deleteWorkspace' var data = { pathParams: { - "workspaceId": wsId + "workspaceId": wsId.toString() }, }; const response = await webconsolejs["common/api/http"].commonAPIPost(controller, data, null) diff --git a/front/assets/js/pages/operation/analytics/monitoringconfig.js b/front/assets/js/pages/operation/analytics/monitoringconfig.js index 17443107..07cd835f 100644 --- a/front/assets/js/pages/operation/analytics/monitoringconfig.js +++ b/front/assets/js/pages/operation/analytics/monitoringconfig.js @@ -144,11 +144,16 @@ async function getWorkloadList(nsId){ $("#workloadlist").append(html); } -// workload(mci,pmk) 선택했을 때 monitoring 정보 조회 -$("#workloadlist").on('change', async function () { +// 서버 목록 조회 함수 (workload 선택 및 refresh 버튼에서 사용) +export async function refreshServerList() { + // workload가 선택되지 않은 경우 처리 + if (!currentWorkloadId || currentWorkloadId === "") { + alert("Please select a Workload first."); + return; + } + // 현재 mci만 monitoring 하므로 mci/pmk 구분없이 mci 호출 var currentNsId = selectedWorkspaceProject.nsId; - currentWorkloadId = $("#workloadlist").val() var currentWorkloadName = $("#workloadlist option:selected").text(); var vmMap = new Map(); @@ -204,6 +209,19 @@ $("#workloadlist").on('change', async function () { // 4. table에 필요한 data set monitorConfigListTable.setData(Array.from(vmMap.values())); +} + +// workload(mci,pmk) 선택했을 때 monitoring 정보 조회 +$("#workloadlist").on('change', async function () { + currentWorkloadId = $("#workloadlist").val() + + // workload가 선택되지 않은 경우 테이블 초기화 + if (!currentWorkloadId || currentWorkloadId === "") { + monitorConfigListTable.setData([]); + return; + } + + await refreshServerList(); }) // tabulator Table 초기값 설정 @@ -562,11 +580,22 @@ async function setMonitorConfigInfoData() { // 클릭한 mci의 info값 세팅 async function setMonitorMetricsTable() { try { + // Monitoring Agent나 Log Agent가 설치되지 않은 경우 조회하지 않음 + if (!selectedServerNode || + selectedServerNode.monitoringAgentStatus !== "SUCCESS" || + selectedServerNode.logAgentStatus !== "SUCCESS") { + // 테이블 데이터 초기화 + monitorMetricsTable.setData([]); + return; + } + var currentNsId = selectedWorkspaceProject.nsId; var response = await webconsolejs["common/api/services/monitoring_api"].GetMetricitems(currentNsId, selectedServerNode.workloadName, selectedServerNode.id); monitorMetricsTable.setData(response.data); } catch (e) { console.error(e); + // 에러 발생 시 테이블 데이터 초기화 + monitorMetricsTable.setData([]); } } diff --git a/front/assets/js/pages/operation/manage/monitoring.js b/front/assets/js/pages/operation/manage/monitoring.js index 3dfa127f..8991fd1b 100644 --- a/front/assets/js/pages/operation/manage/monitoring.js +++ b/front/assets/js/pages/operation/manage/monitoring.js @@ -16,6 +16,12 @@ export function commoncallbac(val) { var selectedWorkspaceProject = new Object(); +// 차트 인스턴스와 현재 메트릭 목록을 저장할 전역 변수 +var monitoringChartInstance = null; +var currentMetrics = []; // 현재 표시 중인 메트릭 목록 +var currentMeasurement = null; // 현재 measurement +var currentVMId = null; // 현재 VM ID + //DOMContentLoaded 는 Page에서 1개만. // init + 파일명 () : ex) initMonitoring() 를 호출하도록 한다. document.addEventListener("DOMContentLoaded", initMonitoring); @@ -297,36 +303,128 @@ export async function startMonitoring() { $("#selected_vm_name").text("(" + selectedVMName + ")"); } + // Start Monitoring 버튼 클릭 시 항상 차트 초기화 + if (monitoringChartInstance) { + monitoringChartInstance.destroy(); + monitoringChartInstance = null; + } + currentMetrics = []; + currentMeasurement = selectedMeasurement; + currentVMId = selectedVMId; + var response = await webconsolejs["common/api/services/monitoring_api"].getInfluxDBMetrics(selectedMeasurement, selectedMetric, selectedRange, selectedPeriod, selectedNsId, selectedMci, selectedVMId); + // Debug: Log response structure + console.log('startMonitoring - response:', response); + console.log('startMonitoring - response.responseData:', response?.responseData); + console.log('startMonitoring - response.responseData.data:', response?.responseData?.data); + // 응답 데이터의 구조를 검증 if (response && response.responseData && response.responseData.data) { var respMonitoringData = response.responseData.data; - drawMonitoringGraph(respMonitoringData, selectedNsId, selectedMci, selectedVMId, selectedMeasurement); + console.log('startMonitoring - respMonitoringData:', respMonitoringData); + drawMonitoringGraph(respMonitoringData, selectedNsId, selectedMci, selectedVMId, selectedMeasurement, selectedMetric); } else { console.error("Invalid response structure:", response); } } -async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measurement) { +async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measurement, metric) { const chartDataList = []; const chartLabels = []; + // Debug: Log input parameters + console.log('drawMonitoringGraph - MonitoringData:', MonitoringData); + console.log('drawMonitoringGraph - measurement:', measurement); + console.log('drawMonitoringGraph - metric:', metric); + // MonitoringData.data가 존재하는지 확인 if (MonitoringData && Array.isArray(MonitoringData)) { MonitoringData.forEach(data => { + // Debug: Log each data series + console.log('drawMonitoringGraph - data series:', data); + console.log('drawMonitoringGraph - data.name:', data.name); + console.log('drawMonitoringGraph - data.columns:', data.columns); + console.log('drawMonitoringGraph - data.values:', data.values); + + // 데이터 구조 확인: columns 배열을 확인하여 어떤 컬럼이 어떤 인덱스인지 확인 + if (data.columns && data.columns.length >= 2) { + console.log('drawMonitoringGraph - Column 0:', data.columns[0], 'Column 1:', data.columns[1]); + // 첫 번째 값 샘플 확인 + if (data.values && data.values.length > 0) { + console.log('drawMonitoringGraph - First value sample:', data.values[0]); + console.log('drawMonitoringGraph - value[0] type:', typeof data.values[0][0], 'value:', data.values[0][0]); + console.log('drawMonitoringGraph - value[1] type:', typeof data.values[0][1], 'value:', data.values[0][1]); + } + } + // null 값을 skip하고 유효한 데이터만 필터링 const validData = data.values .filter(value => value[1] !== null && value[1] !== undefined) - .map(value => ({ - x: value[0], // timestamp - y: parseFloat(value[1]).toFixed(2) - })); + .map((value, index, array) => { + // 타임스탬프를 Date 객체로 변환 (ISO 문자열이거나 Unix 타임스탬프일 수 있음) + let timestamp = value[0]; + if (typeof timestamp === 'string') { + // ISO 문자열인 경우 + timestamp = new Date(timestamp).getTime(); + } else if (typeof timestamp === 'number' && timestamp < 10000000000) { + // Unix 타임스탬프 (초 단위)인 경우 밀리초로 변환 + timestamp = timestamp * 1000; + } + + // 실제 값 추출 + let yValue = parseFloat(value[1]); + + // server_time 메트릭인 경우: 타임스탬프 값을 시간 차이(초)로 변환 + // columns 배열의 두 번째 컬럼이 'server_time'인지 확인 + if (metric === 'server_time' && data.columns && data.columns.length >= 2) { + const columnName = data.columns[1]; + console.log('drawMonitoringGraph - Processing server_time metric, column name:', columnName); + + // server_time 컬럼인 경우 처리 + if (columnName === 'server_time' || columnName === metric) { + // server_time은 타임스탬프 값이므로, 첫 번째 값과의 차이를 초 단위로 계산 + if (index === 0) { + // 첫 번째 값은 0으로 설정 (기준점) + yValue = 0; + } else { + // 이전 값과의 차이를 초 단위로 계산 + const prevValue = parseFloat(array[index - 1][1]); + const currentValue = parseFloat(value[1]); + + // 타임스탬프 차이 계산 + const timeDiff = Math.abs(currentValue - prevValue); + + // 타임스탬프가 밀리초 단위인지 초 단위인지 판단 + // 일반적으로 Unix 타임스탬프는 10자리(초) 또는 13자리(밀리초) + if (currentValue > 1000000000000) { + // 밀리초 단위 (13자리 이상) + yValue = timeDiff / 1000; // 밀리초를 초로 변환 + } else if (currentValue > 1000000000) { + // 초 단위 (10자리) + yValue = timeDiff; // 이미 초 단위 + } else { + // 매우 작은 값인 경우 그대로 사용 + yValue = timeDiff; + } + } + } + } + + return { + x: timestamp, // timestamp (milliseconds) + y: yValue // 실제 값 또는 server_time의 경우 시간 차이(초) + }; + }); + + console.log('drawMonitoringGraph - validData (first 3):', validData.slice(0, 3)); // 유효한 데이터가 있을 때만 시리즈에 추가 if (validData.length > 0) { + // 시리즈 이름을 메트릭 기반으로 설정 (data.name이 있으면 사용, 없으면 metric 사용) + const seriesName = metric || data.name || 'Unknown'; const seriesData = { - name: data.name, + name: seriesName, data: validData }; chartDataList.push(seriesData); @@ -347,12 +445,24 @@ async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measuremen return; } + console.log('drawMonitoringGraph - chartDataList:', chartDataList); + // 유효한 데이터가 없으면 사용자에게 알림 if (chartDataList.length === 0 || chartDataList.every(series => series.data.length === 0)) { alert("No valid data available for the selected metric. Please try a different time range or metric."); return; } + // 동적 차트 제목 생성: Measurement - Metric 형식 + const chartTitle = `${measurement.toUpperCase()} - ${metric || 'Unknown'}`; + + // 기존 차트가 있으면 제거 + if (monitoringChartInstance) { + monitoringChartInstance.destroy(); + monitoringChartInstance = null; + } + + // 새 차트 생성 const options = { chart: { type: "area", @@ -365,7 +475,7 @@ async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measuremen } }, title: { - text: "CPU Usage Idle (cpu0, cpu1, cpu2, cpu3)", + text: chartTitle, align: "center", style: { fontSize: "14px", @@ -396,7 +506,7 @@ async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measuremen curve: "smooth", width: 2 }, - colors: ['#FFD700', '#33FF57', '#3357FF', '#FF33A6'], + colors: ['#FFD700', '#33FF57', '#3357FF', '#FF33A6', '#FF6B9D', '#C44569', '#F8B500', '#6C5CE7'], legend: { show: true, position: "top", @@ -412,7 +522,7 @@ async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measuremen theme: "dark", y: { formatter: function (val) { - return val; // 툴팁에서도 소수점 둘째 자리까지 표시 + return val; } } }, @@ -421,8 +531,8 @@ async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measuremen } }; - const chart = new ApexCharts(document.getElementById("monitoring_chart_1"), options); - chart.render(); + monitoringChartInstance = new ApexCharts(document.getElementById("monitoring_chart_1"), options); + monitoringChartInstance.render(); // Prediction Switch 체크 여부 확인 if ($('#monitoring_predictionSwitch').is(':checked')) { @@ -446,8 +556,17 @@ async function drawMonitoringGraph(MonitoringData, nsId, mciId, vmId, measuremen color: '#FF5733' }; - // 기존 데이터와 함께 업데이트 - chart.updateSeries([...chartDataList, predictionSeries]); + // 기존 차트 인스턴스가 있으면 예측 데이터 추가 + if (monitoringChartInstance) { + const existingSeries = monitoringChartInstance.w.globals.series.map((series, index) => { + return { + name: monitoringChartInstance.w.globals.seriesNames[index], + data: series + }; + }); + + monitoringChartInstance.updateSeries([...existingSeries, predictionSeries]); + } } else { } } catch (error) { diff --git a/front/assets/js/pages/operation/manage/pmk.js b/front/assets/js/pages/operation/manage/pmk.js index 2f7b9474..dcf43bca 100644 --- a/front/assets/js/pages/operation/manage/pmk.js +++ b/front/assets/js/pages/operation/manage/pmk.js @@ -325,6 +325,7 @@ export async function getSelectedPmkData() { // Check if pmkResp exists if (!pmkResp) { + console.error('getSelectedPmkData - pmkResp is null or undefined'); webconsolejs["common/util"].showToast( 'Failed to retrieve cluster information. The cluster may not exist or the API is not responding.', 'error', @@ -334,7 +335,10 @@ export async function getSelectedPmkData() { } // Check response status + // Note: axios response has status at top level, not in data if (pmkResp.status != 200) { + console.error('getSelectedPmkData - Response status is not 200:', pmkResp.status); + console.error('getSelectedPmkData - Full response:', JSON.stringify(pmkResp, null, 2)); webconsolejs["common/util"].showToast( 'Failed to load cluster information. Status: ' + (pmkResp.status || 'Unknown'), 'error', @@ -343,6 +347,18 @@ export async function getSelectedPmkData() { return; } + // Check if responseData exists in the expected location + if (!pmkResp.data || !pmkResp.data.responseData) { + console.error('getSelectedPmkData - responseData not found in expected location'); + console.error('getSelectedPmkData - pmkResp.data structure:', Object.keys(pmkResp.data || {})); + webconsolejs["common/util"].showToast( + 'Invalid response structure from API. Please check console for details.', + 'error', + 5000 + ); + return; + } + // SET PMK Info page setPmkInfoData(pmkResp.data); @@ -569,7 +585,7 @@ function setPmkInfoData(pmkData) { // displayNodeGroupStatusList(pmkID, clusterData) if (Array.isArray(nodeGroupList) && nodeGroupList.length > 0) { - displayNodeGroupStatusList(pmkID, clusterProvider, clusterData); + displayNodeGroupStatusList(currentPmkId, clusterProvider, clusterData); } // Add NodeGroup 버튼 상태 업데이트 diff --git a/front/assets/js/pages/operation/workspace/workspaces.js b/front/assets/js/pages/operation/workspace/workspaces.js index d12e9c6e..2ed7f23a 100644 --- a/front/assets/js/pages/operation/workspace/workspaces.js +++ b/front/assets/js/pages/operation/workspace/workspaces.js @@ -606,6 +606,36 @@ function updateSummary() { document.getElementById('projects_count').textContent = workspaceListInfoSummary.projectsCount; document.getElementById('groupMembers_count').textContent = workspaceListInfoSummary.groupCount + " / " + workspaceListInfoSummary.memberCount; } + +/** + * Workspace 목록 및 관련 데이터 새로고침 + * Refresh workspace list and related data + */ +async function refreshWorkspaceData() { + // 상세정보 영역 숨기기 + webconsolejs["partials/layout/navigatePages"].deactiveElement(document.getElementById("workspace-info-card")); + + // 선택 상태 초기화 + checked_array = []; + currentClickedWorkspaceId = ""; + + // 테이블 선택 해제 + if (workspacesListTable) { + workspacesListTable.deselectRow(); + } + + // 데이터 갱신 + await updateInitData(); + + // 요약 정보 갱신 + updateSummary(); + + // 프로젝트 모달 셀렉터 재초기화 (새 프로젝트가 추가되었을 수 있음) + initProjectModalSeletor(); + + // 테이블 데이터 갱신 + await setWokrspaceTableData(); +} // DOMContentLoaded area end @@ -811,30 +841,61 @@ async function setWokrspaceUserTableData(wsId) { export async function creatworkspaceProject() { let wsName = document.getElementById("workspace-modal-add-name").value let wsDesc = document.getElementById("workspace-modal-add-description").value - const createdWorkspace = await webconsolejs["common/api/services/workspace_api"].createWorkspace(wsName, wsDesc); + + // 프로젝트 선택 여부 확인 + let projects = null; + if (document.getElementById('workspace-modal-add-withprojects').checked) { + let multiprojectSelect = document.getElementById('workspace-modal-add-multiproject'); + projects = Array.from(multiprojectSelect.selectedOptions, option => option.value); + } + + const createdWorkspace = await webconsolejs["common/api/services/workspace_api"].createWorkspace(wsName, wsDesc, projects); + + // 결과를 먼저 toast로 표시 if (!createdWorkspace.success) { webconsolejs["common/util"].showToast("Failed to create workspace: " + JSON.stringify(createdWorkspace.message), 'error'); - return + return; } - 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); - const createdWPmapping = await webconsolejs["common/api/services/workspace_api"].createWPmapping(createdWorkspace.message.id, multiprojects); - if (!createdWPmapping.success) { - webconsolejs["common/util"].showToast("Failed to map projects to workspace: " + JSON.stringify(createdWPmapping.message), 'error'); - return - } + + // 성공 시 toast 표시 + const projectCount = projects ? projects.length : 0; + const successMessage = projectCount > 0 + ? `Workspace "${wsName}" created successfully with ${projectCount} project(s) mapped.` + : `Workspace "${wsName}" created successfully.`; + webconsolejs["common/util"].showToast(successMessage, 'success'); + + // 모달 닫기 + const modal = bootstrap.Modal.getInstance(document.getElementById('workspace-modal-add')); + if (modal) { + modal.hide(); } - location.reload() + + // 데이터 새로고침 + await refreshWorkspaceData(); } export async function deleteWorkspaces() { if (checked_array.length === 0) { - webconsolejs['partials/layout/modal'].commonShowDefaultModal("Delete Workspaces", "No Checked Workspace") + webconsolejs['partials/layout/modal'].commonShowDefaultModal("Delete Workspaces", "No Checked Workspace"); + return; } - checked_array.forEach(async function (checkedWorkspace) { - await webconsolejs["common/api/services/workspace_api"].deleteWorkspaceById(checkedWorkspace.id); - }) + + // forEach 대신 for...of 사용하여 모든 삭제 완료 대기 + for (const checkedWorkspace of checked_array) { + try { + await webconsolejs["common/api/services/workspace_api"].deleteWorkspaceById(checkedWorkspace.id); + } catch (error) { + console.error("Failed to delete workspace:", checkedWorkspace.id, error); + webconsolejs["common/util"].showToast( + `Failed to delete workspace: ${checkedWorkspace.name}`, + 'error', + 5000 + ); + } + } + + // 삭제 성공 후 페이지 새로고침 + location.reload(); } //// workspace table Modal diff --git a/front/templates/pages/operations/analytics/monitorings/monitoringconfig.html b/front/templates/pages/operations/analytics/monitorings/monitoringconfig.html index d6b74111..135237f9 100644 --- a/front/templates/pages/operations/analytics/monitorings/monitoringconfig.html +++ b/front/templates/pages/operations/analytics/monitorings/monitoringconfig.html @@ -106,7 +106,7 @@