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
This commit is contained in:
Fabricio Duarte 2024-07-24 09:09:07 -03:00 committed by GitHub
parent 54c8b71fb5
commit 49cd5ba64a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 77 additions and 3 deletions

View File

@ -576,6 +576,7 @@ public class ApiConstants {
public static final String AGGREGATE_NAME = "aggregatename"; public static final String AGGREGATE_NAME = "aggregatename";
public static final String POOL_NAME = "poolname"; public static final String POOL_NAME = "poolname";
public static final String VOLUME_NAME = "volumename"; 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_POLICY = "snapshotpolicy";
public static final String SNAPSHOT_RESERVATION = "snapshotreservation"; public static final String SNAPSHOT_RESERVATION = "snapshotreservation";
public static final String IP_NETWORK_LIST = "iptonetworklist"; public static final String IP_NETWORK_LIST = "iptonetworklist";

View File

@ -71,6 +71,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
@Param(description = "type of the disk volume") @Param(description = "type of the disk volume")
private String volumeType; private String volumeType;
@SerializedName(ApiConstants.VOLUME_STATE)
@Param(description = "state of the disk volume")
private String volumeState;
@SerializedName(ApiConstants.CREATED) @SerializedName(ApiConstants.CREATED)
@Param(description = " the date the snapshot was created") @Param(description = " the date the snapshot was created")
private Date created; private Date created;
@ -199,6 +203,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements
this.volumeType = volumeType; this.volumeType = volumeType;
} }
public void setVolumeState(String volumeState) {
this.volumeState = volumeState;
}
public void setCreated(Date created) { public void setCreated(Date created) {
this.created = created; this.created = created;
} }

View File

@ -48,6 +48,7 @@ SELECT
`volumes`.`uuid` AS `volume_uuid`, `volumes`.`uuid` AS `volume_uuid`,
`volumes`.`name` AS `volume_name`, `volumes`.`name` AS `volume_name`,
`volumes`.`volume_type` AS `volume_type`, `volumes`.`volume_type` AS `volume_type`,
`volumes`.`state` AS `volume_state`,
`volumes`.`size` AS `volume_size`, `volumes`.`size` AS `volume_size`,
`data_center`.`id` AS `data_center_id`, `data_center`.`id` AS `data_center_id`,
`data_center`.`uuid` AS `data_center_uuid`, `data_center`.`uuid` AS `data_center_uuid`,

View File

@ -659,6 +659,7 @@ public class ApiResponseHelper implements ResponseGenerator {
snapshotResponse.setVolumeId(volume.getUuid()); snapshotResponse.setVolumeId(volume.getUuid());
snapshotResponse.setVolumeName(volume.getName()); snapshotResponse.setVolumeName(volume.getName());
snapshotResponse.setVolumeType(volume.getVolumeType().name()); snapshotResponse.setVolumeType(volume.getVolumeType().name());
snapshotResponse.setVolumeState(volume.getState().name());
snapshotResponse.setVirtualSize(volume.getSize()); snapshotResponse.setVirtualSize(volume.getSize());
DataCenter zone = ApiDBUtils.findZoneById(volume.getDataCenterId()); DataCenter zone = ApiDBUtils.findZoneById(volume.getDataCenterId());
if (zone != null) { if (zone != null) {

View File

@ -128,6 +128,7 @@ public class SnapshotJoinDaoImpl extends GenericDaoBaseWithTagInformation<Snapsh
snapshotResponse.setVolumeId(snapshot.getVolumeUuid()); snapshotResponse.setVolumeId(snapshot.getVolumeUuid());
snapshotResponse.setVolumeName(snapshot.getVolumeName()); snapshotResponse.setVolumeName(snapshot.getVolumeName());
snapshotResponse.setVolumeType(snapshot.getVolumeType().name()); snapshotResponse.setVolumeType(snapshot.getVolumeType().name());
snapshotResponse.setVolumeState(snapshot.getVolumeState().name());
snapshotResponse.setVirtualSize(snapshot.getVolumeSize()); snapshotResponse.setVirtualSize(snapshot.getVolumeSize());
VolumeVO volume = ApiDBUtils.findVolumeById(snapshot.getVolumeId()); VolumeVO volume = ApiDBUtils.findVolumeById(snapshot.getVolumeId());
if (volume != null && volume.getVolumeType() == Type.ROOT && volume.getInstanceId() != null) { if (volume != null && volume.getVolumeType() == Type.ROOT && volume.getInstanceId() != null) {

View File

@ -130,6 +130,10 @@ public class SnapshotJoinVO extends BaseViewWithTagInformationVO implements Cont
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
Volume.Type volumeType = Volume.Type.UNKNOWN; Volume.Type volumeType = Volume.Type.UNKNOWN;
@Column(name = "volume_state")
@Enumerated(EnumType.STRING)
Volume.State volumeState;
@Column(name = "volume_size") @Column(name = "volume_size")
Long volumeSize; Long volumeSize;
@ -297,6 +301,10 @@ public class SnapshotJoinVO extends BaseViewWithTagInformationVO implements Cont
return volumeType; return volumeType;
} }
public Volume.State getVolumeState() {
return volumeState;
}
public Long getVolumeSize() { public Long getVolumeSize() {
return volumeSize; return volumeSize;
} }

View File

@ -442,7 +442,8 @@
<div class="resource-detail-item__label">{{ $t('label.volume') }}</div> <div class="resource-detail-item__label">{{ $t('label.volume') }}</div>
<div class="resource-detail-item__details"> <div class="resource-detail-item__details">
<hdd-outlined /> <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> </div>
<div class="resource-detail-item" v-if="resource.associatednetworkid"> <div class="resource-detail-item" v-if="resource.associatednetworkid">
@ -783,6 +784,7 @@
<script> <script>
import { api } from '@/api' import { api } from '@/api'
import { createPathBasedOnVmType } from '@/utils/plugins' import { createPathBasedOnVmType } from '@/utils/plugins'
import { validateLinks } from '@/utils/links'
import Console from '@/components/widgets/Console' import Console from '@/components/widgets/Console'
import OsLogo from '@/components/widgets/OsLogo' import OsLogo from '@/components/widgets/OsLogo'
import Status from '@/components/widgets/Status' import Status from '@/components/widgets/Status'
@ -848,7 +850,8 @@ export default {
vpc: '', vpc: '',
network: '' network: ''
}, },
newResource: {} newResource: {},
validLinks: {}
} }
}, },
watch: { watch: {
@ -865,6 +868,7 @@ export default {
this.newResource = newData this.newResource = newData
this.showKeys = false this.showKeys = false
this.setData() this.setData()
this.validLinks = validateLinks(this.$router, this.isStatic, this.resource)
if ('apikey' in this.resource) { if ('apikey' in this.resource) {
this.getUserKeys() this.getUserKeys()

View File

@ -167,7 +167,8 @@
<router-link :to="{ path: getVmRouteUsingType(record) + record.virtualmachineid }">{{ text }}</router-link> <router-link :to="{ path: getVmRouteUsingType(record) + record.virtualmachineid }">{{ text }}</router-link>
</template> </template>
<template v-if="column.key === 'volumename'"> <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>
<template v-if="column.key === 'size'"> <template v-if="column.key === 'size'">
<span v-if="text && $route.path === '/kubernetes'"> <span v-if="text && $route.path === '/kubernetes'">
@ -488,6 +489,7 @@ import TooltipButton from '@/components/widgets/TooltipButton'
import ResourceIcon from '@/components/view/ResourceIcon' import ResourceIcon from '@/components/view/ResourceIcon'
import ResourceLabel from '@/components/widgets/ResourceLabel' import ResourceLabel from '@/components/widgets/ResourceLabel'
import { createPathBasedOnVmType } from '@/utils/plugins' import { createPathBasedOnVmType } from '@/utils/plugins'
import { validateLinks } from '@/utils/links'
import cronstrue from 'cronstrue/i18n' import cronstrue from 'cronstrue/i18n'
import moment from 'moment-timezone' import moment from 'moment-timezone'
@ -576,6 +578,18 @@ export default {
notification: 'storageallocatedthreshold', notification: 'storageallocatedthreshold',
disable: 'storageallocateddisablethreshold' 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)
})
} }
} }
}, },

36
ui/src/utils/links.js Normal file
View File

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