Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,12 @@ const fetchStorageClasses = async () => {
selectedStorageClass.value = ""
storageClassLoadError.value = false

if (selectInfra.value !== 'K8S' || _.isEmpty(selectNsId.value) || _.isEmpty(selectCluster.value)) {
if (
selectInfra.value !== 'K8S'
|| !supportsStorageClassConfig.value
|| _.isEmpty(selectNsId.value)
|| _.isEmpty(selectCluster.value)
) {
return
}

Expand Down Expand Up @@ -1136,14 +1141,24 @@ const selectedCatalogChartName = computed(() => {
return String(selectedCatalogInfo.value?.helmChart?.chartName || '').toLowerCase()
})

const OBJECT_STORAGE_CAPABILITY = 'object-storage'
const STORAGE_CLASS_CAPABILITY = 'storage-class'
const CONFIG_CAPABILITY_REF_TYPES = ['CAPABILITY', 'TAG']

const isLokiCatalog = computed(() => selectedCatalogChartName.value === 'loki')

const supportsStorageClassConfig = computed(() => {
if (selectInfra.value !== 'K8S') return false
if (!selectedCatalogInfo.value?.helmChart) return false
return hasCatalogCapability(selectedCatalogInfo.value, STORAGE_CLASS_CAPABILITY)
})

const storageClassRequired = computed(() => {
return selectInfra.value === 'K8S' && isLokiCatalog.value
return supportsStorageClassConfig.value && isLokiCatalog.value
})

const showStorageClassConfig = computed(() => {
return selectInfra.value === 'K8S'
return supportsStorageClassConfig.value
&& modalTitle.value === 'Application Installation'
&& (storageClassRequired.value || storageClassList.value.length > 0 || storageClassLoadError.value)
})
Expand Down Expand Up @@ -1222,11 +1237,15 @@ const objectStorageRequired = computed(() => {
})

function hasObjectStorageCapability(catalog: SoftwareCatalog) {
return hasCatalogCapability(catalog, OBJECT_STORAGE_CAPABILITY)
}

function hasCatalogCapability(catalog: SoftwareCatalog, capability: string) {
const refs = catalog.catalogRefs || []
return refs.some((ref: any) => {
const refType = String(ref.refType || '').toUpperCase()
const refValue = String(ref.refValue || '').toLowerCase()
return refValue === 'object-storage' && (refType === 'CAPABILITY' || refType === 'TAG')
return refValue === capability && CONFIG_CAPABILITY_REF_TYPES.includes(refType)
})
}

Expand All @@ -1246,7 +1265,7 @@ function buildObjectStorageConfig() {

function buildK8sAdditionalConfig() {
const config = {} as Record<string, any>
if (!_.isEmpty(selectedStorageClass.value)) {
if (supportsStorageClassConfig.value && !_.isEmpty(selectedStorageClass.value)) {
config.storageClass = selectedStorageClass.value
}
if (showObjectStorageConfig.value && objectStorageData.value.enabled) {
Expand Down Expand Up @@ -1312,33 +1331,33 @@ const filteredCatalogList = computed(() => {
return catalogList.value
})

const onChangeCatalog = () => {
const onChangeCatalog = async () => {
if(modalTitle.value === 'Application Installation') specCheckFlag.value = true

catalogList.value.forEach((catalogInfo) => {
if (inputApplications.value === catalogInfo.name) {
selectedCatalogIdx.value = catalogInfo.id
inputServicePort.value = catalogInfo.defaultPort ? String(catalogInfo.defaultPort) : ""
hpaData.value = {
hpaEnabled: Boolean(catalogInfo.hpaEnabled),
hpaMinReplicas: catalogInfo.minReplicas || 1,
hpaMaxReplicas: catalogInfo.maxReplicas || 10,
hpaCpuUtilization: catalogInfo.cpuThreshold || 60,
hpaMemoryUtilization: catalogInfo.memoryThreshold || 80
}
ingressData.value = {
ingressEnabled: Boolean(catalogInfo.ingressEnabled),
ingressHost: catalogInfo.ingressHost || '',
ingressPath: catalogInfo.ingressPath || '/',
ingressClass: catalogInfo.ingressClass || 'nginx',
ingressTlsEnabled: Boolean(catalogInfo.ingressTlsEnabled),
ingressTlsSecret: catalogInfo.ingressTlsSecret || ''
}
objectStorageData.value = getDefaultObjectStorageData()
objectStorageCheckResult.value = null
return;
const catalogInfo = catalogList.value.find((catalog) => inputApplications.value === catalog.name)
if (catalogInfo) {
selectedCatalogIdx.value = catalogInfo.id
inputServicePort.value = catalogInfo.defaultPort ? String(catalogInfo.defaultPort) : ""
hpaData.value = {
hpaEnabled: Boolean(catalogInfo.hpaEnabled),
hpaMinReplicas: catalogInfo.minReplicas || 1,
hpaMaxReplicas: catalogInfo.maxReplicas || 10,
hpaCpuUtilization: catalogInfo.cpuThreshold || 60,
hpaMemoryUtilization: catalogInfo.memoryThreshold || 80
}
})
ingressData.value = {
ingressEnabled: Boolean(catalogInfo.ingressEnabled),
ingressHost: catalogInfo.ingressHost || '',
ingressPath: catalogInfo.ingressPath || '/',
ingressClass: catalogInfo.ingressClass || 'nginx',
ingressTlsEnabled: Boolean(catalogInfo.ingressTlsEnabled),
ingressTlsSecret: catalogInfo.ingressTlsSecret || ''
}
objectStorageData.value = getDefaultObjectStorageData()
objectStorageCheckResult.value = null
}

await fetchStorageClasses()
}

const onChangeCluster = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,9 @@ const openDetailModal = (cell: any) => {


const infraFormatter = (cell: any) => {
const infraType = cell.getRow().getData().deploymentType
const infraName =
cell.getRow().getData().vmId ? cell.getRow().getData().vmId :
cell.getRow().getData().clusterName ? cell.getRow().getData().clusterName : '-'
const rowData = cell.getRow().getData()
const infraType = rowData.deploymentType
const infraName = getInfraDisplayName(rowData)
return `
<div style="cursor: pointer;">
<p style="margin: 0;">
Expand All @@ -295,6 +294,16 @@ const infraFormatter = (cell: any) => {
`
}

const getInfraDisplayName = (rowData: any) => {
if (rowData.deploymentType === 'VM') {
return [rowData.namespace, rowData.mciId, rowData.vmId].filter(Boolean).join(' / ') || '-'
}
if (rowData.deploymentType === 'K8S') {
return [rowData.namespace, rowData.clusterName].filter(Boolean).join(' / ') || '-'
}
return rowData.vmId || rowData.clusterName || '-'
}

const isActionDisabledStatus = (status: string) =>
isApplicationActionDisabledStatus(status)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public class OperationHistory {
@JoinColumn(name = "application_status_id")
private ApplicationStatus applicationStatus; // ApplicationStatus와의 외래키 관계

@Column(name = "deployment_history_id")
private Long deploymentHistoryId;

@ManyToOne
@JoinColumn(name = "executed_by")
private User executedBy;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ public interface ApplicationStatusRepository extends JpaRepository<ApplicationSt
List<ApplicationStatus> findByNamespaceAndMciIdAndVmId(String namespace, String mciId, String vmId);
Optional<ApplicationStatus> findTopByCatalogIdOrderByCheckedAtDesc(Long catalogId);

// VM별 ApplicationStatus 검색 (catalogId + vmId 조합)
Optional<ApplicationStatus> findByCatalogIdAndVmId(Long catalogId, String vmId);
// VM별 ApplicationStatus 검색 (catalogId + namespace + mciId + vmId 조합)
Optional<ApplicationStatus> findByCatalogIdAndNamespaceAndMciIdAndVmId(
Long catalogId,
String namespace,
String mciId,
String vmId);
List<ApplicationStatus> findByCatalogIdAndNamespaceAndMciId(Long catalogId, String namespace, String mciId);

@Query("SELECT a FROM ApplicationStatus a " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ List<DeploymentHistory> findByNamespaceAndClusterNameAndActionTypeNotAndStatus(
DeploymentHistory findTopByCatalogIdAndClusterNameAndNamespaceAndActionTypeOrderByExecutedAtDesc(
Long catalogId, String clusterName, String namespace, ActionType actionType);

Optional<DeploymentHistory> findTopByCatalogIdAndVmIdAndActionTypeInAndStatusInOrderByExecutedAtDesc(
Long catalogId, String vmId, List<ActionType> actionTypes, List<String> statuses);
Optional<DeploymentHistory> findTopByCatalogIdAndNamespaceAndMciIdAndVmIdAndActionTypeInAndStatusInOrderByExecutedAtDesc(
Long catalogId, String namespace, String mciId, String vmId, List<ActionType> actionTypes, List<String> statuses);

Optional<DeploymentHistory> findTopByCatalogIdAndClusterNameAndNamespaceAndActionTypeInAndStatusInOrderByExecutedAtDesc(
Long catalogId, String clusterName, String namespace, List<ActionType> actionTypes, List<String> statuses);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ public interface OperationHistoryRepository extends JpaRepository<OperationHisto
List<OperationHistory> findByApplicationStatusId(Long applicationStatusId);

Optional<OperationHistory> findTopByApplicationStatusIdOrderByCreatedAtDesc(Long applicationStatusId);

List<OperationHistory> findByDeploymentHistoryIdOrderByCreatedAtAsc(Long deploymentHistoryId);

Optional<OperationHistory> findTopByDeploymentHistoryIdOrderByCreatedAtDesc(Long deploymentHistoryId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,34 +89,63 @@ public DeploymentHistory createDeploymentHistory(DeploymentRequest request, User

@Override
public void updateApplicationStatus(DeploymentHistory history, String status, User user) {
List<ApplicationStatus> appStatusList = applicationStatusRepository.findByCatalogId(history.getCatalog().getId());
ApplicationStatus appStatus = appStatusList.isEmpty() ? new ApplicationStatus() : appStatusList.get(0);
ApplicationStatus appStatus = findApplicationStatusForHistory(history)
.orElse(new ApplicationStatus());

appStatus.setCatalog(history.getCatalog());
appStatus.setStatus(status);
appStatus.setDeploymentType(history.getDeploymentType());
appStatus.setCheckedAt(LocalDateTime.now());
appStatus.setDeploymentHistoryId(history.getId());
appStatus.setExecutedBy(user);

if (history.getDeploymentType() == DeploymentType.VM) {
appStatus.setNamespace(history.getNamespace());
appStatus.setMciId(history.getMciId());
appStatus.setVmId(history.getVmId());
appStatus.setPublicIp(history.getPublicIp());
appStatus.setServicePort(history.getServicePort());
} else if (history.getDeploymentType() == DeploymentType.K8S) {
appStatus.setNamespace(history.getNamespace());
appStatus.setClusterName(history.getClusterName());
}

applicationStatusRepository.save(appStatus);
}

private Optional<ApplicationStatus> findApplicationStatusForHistory(DeploymentHistory history) {
if (history == null || history.getCatalog() == null || history.getCatalog().getId() == null) {
return Optional.empty();
}

Long catalogId = history.getCatalog().getId();
if (history.getDeploymentType() == DeploymentType.VM) {
return applicationStatusRepository.findByCatalogIdAndNamespaceAndMciIdAndVmId(
catalogId,
history.getNamespace(),
history.getMciId(),
history.getVmId());
}
if (history.getDeploymentType() == DeploymentType.K8S) {
return applicationStatusRepository.findLatestByNamespaceAndClusterNameAndCatalogId(
history.getNamespace(),
history.getClusterName(),
catalogId);
}
return Optional.empty();
}

/**
* VM별 ApplicationStatus를 생성합니다. (다중 VM 배포용)
*/
public void createApplicationStatusForVm(DeploymentHistory history, String vmId, String publicIp,
Integer servicePort, String status, User user) {
ApplicationStatus appStatus = applicationStatusRepository
.findByCatalogIdAndVmId(history.getCatalog().getId(), vmId)
.findByCatalogIdAndNamespaceAndMciIdAndVmId(
history.getCatalog().getId(),
history.getNamespace(),
history.getMciId(),
vmId)
.orElse(new ApplicationStatus());

appStatus.setCatalog(history.getCatalog());
Expand Down Expand Up @@ -188,7 +217,7 @@ public boolean hasExistingInstallation(String namespace, String mciId, String vm
try {
// 해당 VM에 같은 카탈로그로 설치된 ApplicationStatus가 있는지 확인
Optional<ApplicationStatus> existingStatus = applicationStatusRepository
.findByCatalogIdAndVmId(catalogId, vmId);
.findByCatalogIdAndNamespaceAndMciIdAndVmId(catalogId, namespace, mciId, vmId);

if (existingStatus.isPresent()) {
ApplicationStatus status = existingStatus.get();
Expand Down Expand Up @@ -241,6 +270,7 @@ public void insertOperationHistory(ApplicationStatus applicationStatus, String u

OperationHistory operationHistory = OperationHistory.builder()
.applicationStatus(applicationStatus)
.deploymentHistoryId(applicationStatus.getDeploymentHistoryId())
.reason(finalReason)
.detailReason(finalDetailReason)
.operationType(actionType.name())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,20 +144,15 @@ private boolean isEffectivelyUninstalled(ApplicationStatus status) {
if (status.getId() == null) {
return false;
}
Optional<OperationHistory> latestOperation = operationHistoryRepository
.findTopByApplicationStatusIdOrderByCreatedAtDesc(status.getId());
if (latestOperation.isEmpty() || !ActionType.UNINSTALL.name().equalsIgnoreCase(latestOperation.get().getOperationType())) {
if (status.getDeploymentHistoryId() == null) {
return false;
}

if (status.getDeploymentHistoryId() == null) {
return true;
}
Optional<DeploymentHistory> deploymentHistory = deploymentHistoryRepository.findById(status.getDeploymentHistoryId());
return deploymentHistory
.map(history -> history.getExecutedAt() == null
|| !latestOperation.get().getCreatedAt().isBefore(history.getExecutedAt()))
.orElse(true);
Optional<OperationHistory> latestOperation = operationHistoryRepository
.findTopByDeploymentHistoryIdOrderByCreatedAtDesc(status.getDeploymentHistoryId());
return latestOperation
.map(operation -> ActionType.UNINSTALL.name().equalsIgnoreCase(operation.getOperationType()))
.orElse(false);
}

@Override
Expand Down
Loading
Loading