This is an automated email from the ASF dual-hosted git repository.

joao pushed a commit to branch 4.19
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.19 by this push:
     new 49cd5ba64ad Fix link to removed volumes being shown in info card and 
list view (#8833)
49cd5ba64ad is described below

commit 49cd5ba64ad6f3a145f03592903d07a8dffa4578
Author: Fabricio Duarte <fabricio.duarte...@gmail.com>
AuthorDate: Wed Jul 24 09:09:07 2024 -0300

    Fix link to removed volumes being shown in info card and list view (#8833)
    
    * Framework for validating links in the front-end
    
    * Rename valid links map in the list view
---
 .../org/apache/cloudstack/api/ApiConstants.java    |  1 +
 .../cloudstack/api/response/SnapshotResponse.java  |  8 +++++
 .../META-INF/db/views/cloud.snapshot_view.sql      |  1 +
 .../main/java/com/cloud/api/ApiResponseHelper.java |  1 +
 .../cloud/api/query/dao/SnapshotJoinDaoImpl.java   |  1 +
 .../com/cloud/api/query/vo/SnapshotJoinVO.java     |  8 +++++
 ui/src/components/view/InfoCard.vue                |  8 +++--
 ui/src/components/view/ListView.vue                | 16 +++++++++-
 ui/src/utils/links.js                              | 36 ++++++++++++++++++++++
 9 files changed, 77 insertions(+), 3 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java 
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 2324b861830..d6099ac4717 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -576,6 +576,7 @@ public class ApiConstants {
     public static final String AGGREGATE_NAME = "aggregatename";
     public static final String POOL_NAME = "poolname";
     public static final String VOLUME_NAME = "volumename";
+    public static final String VOLUME_STATE = "volumestate";
     public static final String SNAPSHOT_POLICY = "snapshotpolicy";
     public static final String SNAPSHOT_RESERVATION = "snapshotreservation";
     public static final String IP_NETWORK_LIST = "iptonetworklist";
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java
index e160f64ebe9..02132416b8a 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java
@@ -71,6 +71,10 @@ public class SnapshotResponse extends 
BaseResponseWithTagInformation implements
     @Param(description = "type of the disk volume")
     private String volumeType;
 
+    @SerializedName(ApiConstants.VOLUME_STATE)
+    @Param(description = "state of the disk volume")
+    private String volumeState;
+
     @SerializedName(ApiConstants.CREATED)
     @Param(description = "  the date the snapshot was created")
     private Date created;
@@ -199,6 +203,10 @@ public class SnapshotResponse extends 
BaseResponseWithTagInformation implements
         this.volumeType = volumeType;
     }
 
+    public void setVolumeState(String volumeState) {
+        this.volumeState = volumeState;
+    }
+
     public void setCreated(Date created) {
         this.created = created;
     }
diff --git 
a/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql 
b/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql
index c6b8d6b4d05..d0eddc1fc4b 100644
--- a/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.snapshot_view.sql
@@ -48,6 +48,7 @@ SELECT
     `volumes`.`uuid` AS `volume_uuid`,
     `volumes`.`name` AS `volume_name`,
     `volumes`.`volume_type` AS `volume_type`,
+    `volumes`.`state` AS `volume_state`,
     `volumes`.`size` AS `volume_size`,
     `data_center`.`id` AS `data_center_id`,
     `data_center`.`uuid` AS `data_center_uuid`,
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java 
b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index e801d1f9b31..eec767d7b5e 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -659,6 +659,7 @@ public class ApiResponseHelper implements ResponseGenerator 
{
             snapshotResponse.setVolumeId(volume.getUuid());
             snapshotResponse.setVolumeName(volume.getName());
             snapshotResponse.setVolumeType(volume.getVolumeType().name());
+            snapshotResponse.setVolumeState(volume.getState().name());
             snapshotResponse.setVirtualSize(volume.getSize());
             DataCenter zone = 
ApiDBUtils.findZoneById(volume.getDataCenterId());
             if (zone != null) {
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java
index b08fb4529f4..8b951c174f4 100644
--- a/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/SnapshotJoinDaoImpl.java
@@ -128,6 +128,7 @@ public class SnapshotJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<Snapsh
             snapshotResponse.setVolumeId(snapshot.getVolumeUuid());
             snapshotResponse.setVolumeName(snapshot.getVolumeName());
             snapshotResponse.setVolumeType(snapshot.getVolumeType().name());
+            snapshotResponse.setVolumeState(snapshot.getVolumeState().name());
             snapshotResponse.setVirtualSize(snapshot.getVolumeSize());
             VolumeVO volume = 
ApiDBUtils.findVolumeById(snapshot.getVolumeId());
             if (volume != null && volume.getVolumeType() == Type.ROOT && 
volume.getInstanceId() != null) {
diff --git a/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java 
b/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java
index 9ec74dac128..2dc2f7a810f 100644
--- a/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/SnapshotJoinVO.java
@@ -130,6 +130,10 @@ public class SnapshotJoinVO extends 
BaseViewWithTagInformationVO implements Cont
     @Enumerated(EnumType.STRING)
     Volume.Type volumeType = Volume.Type.UNKNOWN;
 
+    @Column(name = "volume_state")
+    @Enumerated(EnumType.STRING)
+    Volume.State volumeState;
+
     @Column(name = "volume_size")
     Long volumeSize;
 
@@ -297,6 +301,10 @@ public class SnapshotJoinVO extends 
BaseViewWithTagInformationVO implements Cont
         return volumeType;
     }
 
+    public Volume.State getVolumeState() {
+        return volumeState;
+    }
+
     public Long getVolumeSize() {
         return volumeSize;
     }
diff --git a/ui/src/components/view/InfoCard.vue 
b/ui/src/components/view/InfoCard.vue
index bb6726d75f1..98c26bf5565 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -442,7 +442,8 @@
           <div class="resource-detail-item__label">{{ $t('label.volume') 
}}</div>
           <div class="resource-detail-item__details">
             <hdd-outlined />
-            <router-link :to="{ path: '/volume/' + resource.volumeid }">{{ 
resource.volumename || resource.volume || resource.volumeid }} </router-link>
+            <router-link v-if="validLinks.volume" :to="{ path: '/volume/' + 
resource.volumeid }">{{ resource.volumename || resource.volume || 
resource.volumeid }} </router-link>
+            <span v-else>{{ resource.volumename || resource.volume || 
resource.volumeid }}</span>
           </div>
         </div>
         <div class="resource-detail-item" v-if="resource.associatednetworkid">
@@ -783,6 +784,7 @@
 <script>
 import { api } from '@/api'
 import { createPathBasedOnVmType } from '@/utils/plugins'
+import { validateLinks } from '@/utils/links'
 import Console from '@/components/widgets/Console'
 import OsLogo from '@/components/widgets/OsLogo'
 import Status from '@/components/widgets/Status'
@@ -848,7 +850,8 @@ export default {
         vpc: '',
         network: ''
       },
-      newResource: {}
+      newResource: {},
+      validLinks: {}
     }
   },
   watch: {
@@ -865,6 +868,7 @@ export default {
         this.newResource = newData
         this.showKeys = false
         this.setData()
+        this.validLinks = validateLinks(this.$router, this.isStatic, 
this.resource)
 
         if ('apikey' in this.resource) {
           this.getUserKeys()
diff --git a/ui/src/components/view/ListView.vue 
b/ui/src/components/view/ListView.vue
index cf3d9361638..8512dab7c05 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -167,7 +167,8 @@
         <router-link :to="{ path: getVmRouteUsingType(record) + 
record.virtualmachineid }">{{ text }}</router-link>
       </template>
       <template v-if="column.key === 'volumename'">
-        <router-link :to="{ path: '/volume/' + record.volumeid }">{{ text 
}}</router-link>
+        <router-link v-if="resourceIdToValidLinksMap[record.id]?.volume" 
:to="{ path: '/volume/' + record.volumeid }">{{ text }}</router-link>
+        <span v-else>{{ text }}</span>
       </template>
       <template v-if="column.key === 'size'">
         <span v-if="text && $route.path === '/kubernetes'">
@@ -488,6 +489,7 @@ import TooltipButton from 
'@/components/widgets/TooltipButton'
 import ResourceIcon from '@/components/view/ResourceIcon'
 import ResourceLabel from '@/components/widgets/ResourceLabel'
 import { createPathBasedOnVmType } from '@/utils/plugins'
+import { validateLinks } from '@/utils/links'
 import cronstrue from 'cronstrue/i18n'
 import moment from 'moment-timezone'
 
@@ -576,6 +578,18 @@ export default {
           notification: 'storageallocatedthreshold',
           disable: 'storageallocateddisablethreshold'
         }
+      },
+      resourceIdToValidLinksMap: {}
+    }
+  },
+  watch: {
+    items: {
+      deep: true,
+      handler (newData, oldData) {
+        if (newData === oldData) return
+        this.items.forEach(record => {
+          this.resourceIdToValidLinksMap[record.id] = 
validateLinks(this.$router, false, record)
+        })
       }
     }
   },
diff --git a/ui/src/utils/links.js b/ui/src/utils/links.js
new file mode 100644
index 00000000000..fa650cdd7b8
--- /dev/null
+++ b/ui/src/utils/links.js
@@ -0,0 +1,36 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+export function validateLinks (router, isStatic, resource) {
+  const validLinks = {
+    volume: false
+  }
+
+  if (isStatic) {
+    return validLinks
+  }
+
+  if (resource.volumeid && router.resolve('/volume/' + 
resource.volumeid).matched[0].redirect !== '/exception/404') {
+    if (resource.volumestate) {
+      validLinks.volume = resource.volumestate !== 'Expunged'
+    } else {
+      validLinks.volume = true
+    }
+  }
+
+  return validLinks
+}

Reply via email to