This commit is contained in:
Gabriel Pordeus Santos 2026-07-04 14:36:10 +00:00 committed by GitHub
commit 632808ec48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 488 additions and 81 deletions

View File

@ -1017,6 +1017,7 @@ public class ApiConstants {
public static final String VPC_OFF_NAME = "vpcofferingname";
public static final String VPC_OFFERING_CONSERVE_MODE = "vpcofferingconservemode";
public static final String NETWORK = "network";
public static final String VPC_ACCESS = "vpcaccess";
public static final String VPC_ID = "vpcid";
public static final String VPC_NAME = "vpcname";
public static final String VPC_GATEWAY_ID = "vpcgatewayid";

View File

@ -240,7 +240,7 @@ public class ListNetworksCmd extends BaseListRetrieveOnlyResourceCountCmd implem
private void updateNetworkResponse(List<NetworkResponse> response) {
for (NetworkResponse networkResponse : response) {
ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Network, networkResponse.getId());
if (resourceIcon == null && networkResponse.getVpcId() != null) {
if (resourceIcon == null && networkResponse.getVpcId() != null && networkResponse.getVpcAccess()) {
resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Vpc, networkResponse.getVpcId());
}
if (resourceIcon == null) {

View File

@ -143,6 +143,10 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co
@Param(description = "Purpose of the IP address. In Acton this value is not null for IPs with isSystem=true, and can have either StaticNat or LB value")
private String purpose;
@SerializedName(ApiConstants.VPC_ACCESS)
@Param(description = "Whether the calling account has access to this network's VPC", since = "4.21.0")
private boolean vpcAccess;
@SerializedName(ApiConstants.VPC_ID)
@Param(description = "VPC ID the IP belongs to")
private String vpcId;
@ -301,6 +305,10 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co
this.purpose = purpose;
}
public void setVpcAccess(boolean vpcAccess) {
this.vpcAccess = vpcAccess;
}
public void setVpcId(String vpcId) {
this.vpcId = vpcId;
}

View File

@ -203,6 +203,10 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
@Param(description = "True if Network supports specifying IP ranges, false otherwise")
private Boolean specifyIpRanges;
@SerializedName(ApiConstants.VPC_ACCESS)
@Param(description = "Whether the calling account has access to this network's VPC", since = "4.21.0")
private Boolean vpcAccess;
@SerializedName(ApiConstants.VPC_ID)
@Param(description = "VPC the Network belongs to")
private String vpcId;
@ -523,6 +527,14 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
this.specifyIpRanges = specifyIpRanges;
}
public void setVpcAccess(boolean vpcAccess) {
this.vpcAccess = vpcAccess;
}
public Boolean getVpcAccess() {
return vpcAccess;
}
public void setVpcId(String vpcId) {
this.vpcId = vpcId;
}

View File

@ -1167,7 +1167,7 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
}
setVpcIdInResponse(ipAddr.getVpcId(), ipResponse::setVpcId, ipResponse::setVpcName);
setVpcIdInResponse(ipAddr.getVpcId(), ipResponse::setVpcId, ipResponse::setVpcName, ipResponse::setVpcAccess);
// Network id the ip is associated with (if associated networkId is
@ -1242,20 +1242,43 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
return ipResponse;
}
private void setVpcIdInResponse(Long vpcId, Consumer<String> vpcUuidSetter, Consumer<String> vpcNameSetter) {
if (vpcId != null) {
Vpc vpc = ApiDBUtils.findVpcById(vpcId);
if (vpc != null) {
try {
_accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, false, vpc);
vpcUuidSetter.accept(vpc.getUuid());
} catch (PermissionDeniedException e) {
logger.debug("Not setting the vpcId to the response because the caller does not have access to the VPC");
}
vpcNameSetter.accept(vpc.getName());
}
protected void setVpcIdInResponse(Long vpcId, Consumer<String> vpcUuidSetter, Consumer<String> vpcNameSetter, Consumer<Boolean> vpcAccessSetter) {
if (vpcId == null) {
return;
}
Vpc vpc = ApiDBUtils.findVpcById(vpcId);
if (vpc == null) {
return;
}
try {
_accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, false, vpc);
vpcAccessSetter.accept(true);
} catch (PermissionDeniedException e) {
vpcAccessSetter.accept(false);
logger.debug("Setting [{}] as false because the caller does not have access to the VPC [{}].", ApiConstants.VPC_ACCESS, vpc);
}
vpcNameSetter.accept(vpc.getName());
vpcUuidSetter.accept(vpc.getUuid());
}
protected void setAclIdInResponse(Network network, NetworkResponse response) {
if (network.getNetworkACLId() == null) {
return;
}
NetworkACL acl = ApiDBUtils.findByNetworkACLId(network.getNetworkACLId());
if (acl == null) {
return;
}
if (Boolean.FALSE.equals(response.getVpcAccess()) && acl.getVpcId() != 0) {
logger.debug("[{}] not set in response, since caller does not have access to it.", acl);
return;
}
response.setAclId(acl.getUuid());
response.setAclName(acl.getName());
}
private void showVmInfoForSharedNetworks(boolean forVirtualNetworks, IpAddress ipAddr, IPAddressResponse ipResponse) {
@ -2799,7 +2822,8 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
response.setSpecifyIpRanges(network.getSpecifyIpRanges());
setVpcIdInResponse(network.getVpcId(), response::setVpcId, response::setVpcName);
setVpcIdInResponse(network.getVpcId(), response::setVpcId, response::setVpcName, response::setVpcAccess);
setAclIdInResponse(network, response);
setResponseAssociatedNetworkInformation(response, network.getId());
@ -2816,14 +2840,6 @@ public class ApiResponseHelper implements ResponseGenerator, ResourceIdSupport {
response.setHasAnnotation(annotationDao.hasAnnotations(network.getUuid(), AnnotationService.EntityType.NETWORK.name(),
_accountMgr.isRootAdmin(CallContext.current().getCallingAccount().getId())));
if (network.getNetworkACLId() != null) {
NetworkACL acl = ApiDBUtils.findByNetworkACLId(network.getNetworkACLId());
if (acl != null) {
response.setAclId(acl.getUuid());
response.setAclName(acl.getName());
}
}
response.setStrechedL2Subnet(network.isStrechedL2Network());
if (network.isStrechedL2Network()) {
Set<String> networkSpannedZones = new HashSet<String>();

View File

@ -29,6 +29,47 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import com.cloud.capacity.Capacity;
import com.cloud.configuration.Resource;
import com.cloud.domain.DomainVO;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.network.Network;
import com.cloud.network.PublicIpQuarantine;
import com.cloud.network.as.AutoScaleVmGroup;
import com.cloud.network.as.AutoScaleVmGroupVO;
import com.cloud.network.as.AutoScaleVmProfileVO;
import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.LoadBalancerVO;
import com.cloud.network.dao.NetworkServiceMapDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.vpc.NetworkACL;
import com.cloud.network.vpc.VpcVO;
import com.cloud.storage.VMTemplateVO;
import com.cloud.usage.UsageVO;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserData;
import com.cloud.user.UserDataVO;
import com.cloud.user.UserVO;
import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.net.Ip;
import com.cloud.vm.NicSecondaryIp;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.IpQuarantineResponse;
import org.apache.cloudstack.api.response.NetworkResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.usage.UsageService;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -42,40 +83,16 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse;
import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.GuestOSCategoryResponse;
import org.apache.cloudstack.api.response.IpQuarantineResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.UnmanagedInstanceResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.api.response.TrafficTypeResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.usage.UsageService;
import org.apache.cloudstack.vm.UnmanagedInstanceTO;
import com.cloud.capacity.Capacity;
import com.cloud.configuration.Resource;
import com.cloud.domain.DomainVO;
import com.cloud.host.HostVO;
import com.cloud.network.Networks;
import com.cloud.network.PhysicalNetworkTrafficType;
import com.cloud.network.PublicIpQuarantine;
import com.cloud.network.as.AutoScaleVmGroup;
import com.cloud.network.as.AutoScaleVmGroupVO;
import com.cloud.network.as.AutoScaleVmProfileVO;
import com.cloud.network.as.dao.AutoScaleVmGroupVmMapDao;
import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.LoadBalancerVO;
import com.cloud.network.dao.NetworkServiceMapDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO;
import com.cloud.resource.icon.ResourceIconVO;
@ -83,19 +100,7 @@ import com.cloud.server.ResourceIcon;
import com.cloud.server.ResourceIconManager;
import com.cloud.server.ResourceTag;
import com.cloud.storage.GuestOsCategory;
import com.cloud.storage.VMTemplateVO;
import com.cloud.usage.UsageVO;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountVO;
import com.cloud.user.User;
import com.cloud.user.UserData;
import com.cloud.user.UserDataVO;
import com.cloud.user.UserVO;
import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.net.Ip;
import com.cloud.vm.ConsoleSessionVO;
import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VMInstanceVO;
import static org.junit.Assert.assertEquals;
@ -150,6 +155,12 @@ public class ApiResponseHelperTest {
@Mock
private VMInstanceVO vmInstanceVOMock;
@Mock
private VpcVO vpcVOMock;
@Mock
private NetworkACL networkACLMock;
@Spy
@InjectMocks
ApiResponseHelper apiResponseHelper = new ApiResponseHelper();
@ -168,6 +179,9 @@ public class ApiResponseHelperTest {
static long autoScaleUserId = 7L;
static final String A_NAME = "name";
static final String A_UUID = "021f94d4-73f9-4a9a-b003-1df9dd968a09";
@Before
public void injectMocks() throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException {
@ -800,4 +814,135 @@ public class ApiResponseHelperTest {
Assert.assertEquals(expected.getVmName(), response.getVmName());
}
}
@Test
public void setVpcIdInResponseTestNullVpcIdReturnNull() {
NetworkResponse networkResponse = new NetworkResponse();
apiResponseHelper.setVpcIdInResponse(null, networkResponse::setVpcId, networkResponse::setVpcName, networkResponse::setVpcAccess);
Assert.assertNull(networkResponse.getVpcId());
Assert.assertNull(networkResponse.getVpcName());
Assert.assertNull(networkResponse.getVpcAccess());
}
@Test
public void setVpcIdInResponseTestNullVpcReturnNull() {
NetworkResponse networkResponse = new NetworkResponse();
try (MockedStatic<ApiDBUtils> utils = Mockito.mockStatic(ApiDBUtils.class)) {
utils.when(() -> ApiDBUtils.findVpcById(1L)).thenReturn(null);
apiResponseHelper.setVpcIdInResponse(1L, networkResponse::setVpcId, networkResponse::setVpcName, networkResponse::setVpcAccess);
}
Assert.assertNull(networkResponse.getVpcId());
Assert.assertNull(networkResponse.getVpcName());
Assert.assertNull(networkResponse.getVpcAccess());
}
@Test
public void setVpcIdInResponseCallerHasAccessReturnVpcAccessTrueAndVpcIdAndVpcName() {
NetworkResponse networkResponse = new NetworkResponse();
Mockito.when(vpcVOMock.getName()).thenReturn(A_NAME);
Mockito.when(vpcVOMock.getUuid()).thenReturn(A_UUID);
try (MockedStatic<ApiDBUtils> utils = Mockito.mockStatic(ApiDBUtils.class)) {
utils.when(() -> ApiDBUtils.findVpcById(1L)).thenReturn(vpcVOMock);
apiResponseHelper.setVpcIdInResponse(1L, networkResponse::setVpcId, networkResponse::setVpcName, networkResponse::setVpcAccess);
};
Assert.assertEquals(A_UUID, networkResponse.getVpcId());
Assert.assertEquals(A_NAME, networkResponse.getVpcName());
Assert.assertTrue(networkResponse.getVpcAccess());
}
@Test
public void setVpcIdInResponseCallerDoesNotHaveAccessReturnVpcAccessFalseAndVpcIdAndVpcName() {
NetworkResponse networkResponse = new NetworkResponse();
Mockito.when(vpcVOMock.getName()).thenReturn(A_NAME);
Mockito.when(vpcVOMock.getUuid()).thenReturn(A_UUID);
try (MockedStatic<ApiDBUtils> utils = Mockito.mockStatic(ApiDBUtils.class)) {
utils.when(() -> ApiDBUtils.findVpcById(1L)).thenReturn(vpcVOMock);
Mockito.doThrow(PermissionDeniedException.class).when(accountManagerMock).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any());
apiResponseHelper.setVpcIdInResponse(1L, networkResponse::setVpcId, networkResponse::setVpcName, networkResponse::setVpcAccess);
};
Assert.assertEquals(A_UUID, networkResponse.getVpcId());
Assert.assertEquals(A_NAME, networkResponse.getVpcName());
Assert.assertFalse(networkResponse.getVpcAccess());
}
@Test
public void setAclIdInResponseTestNullNetworkAclIdReturnNull() {
NetworkResponse networkResponse = new NetworkResponse();
Network networkMock = Mockito.mock(Network.class);
Mockito.when(networkMock.getNetworkACLId()).thenReturn(null);
apiResponseHelper.setAclIdInResponse(networkMock, networkResponse);
Assert.assertNull(networkResponse.getAclId());
Assert.assertNull(networkResponse.getAclName());
}
@Test
public void setAclIdInResponseTestNullAclReturnNull() {
NetworkResponse networkResponse = new NetworkResponse();
Network networkMock = Mockito.mock(Network.class);
Mockito.when(networkMock.getNetworkACLId()).thenReturn(1L);
try (MockedStatic<ApiDBUtils> utils = Mockito.mockStatic(ApiDBUtils.class)) {
utils.when(() -> ApiDBUtils.findByNetworkACLId(1L)).thenReturn(null);
apiResponseHelper.setAclIdInResponse(networkMock, networkResponse);
}
Assert.assertNull(networkResponse.getAclId());
Assert.assertNull(networkResponse.getAclName());
}
@Test
public void setAclIdInResponseTestCallerDoesNotHaveAccessReturnNull() {
NetworkResponse networkResponse = new NetworkResponse();
networkResponse.setVpcAccess(false);
Network networkMock = Mockito.mock(Network.class);
Mockito.when(networkMock.getNetworkACLId()).thenReturn(1L);
Mockito.when(networkACLMock.getVpcId()).thenReturn(2L);
try (MockedStatic<ApiDBUtils> utils = Mockito.mockStatic(ApiDBUtils.class)) {
utils.when(() -> ApiDBUtils.findByNetworkACLId(1L)).thenReturn(networkACLMock);
apiResponseHelper.setAclIdInResponse(networkMock, networkResponse);
}
Assert.assertNull(networkResponse.getAclId());
Assert.assertNull(networkResponse.getAclName());
}
@Test
public void setAclIdInResponseTestCallerDoesNotHaveAccessButAclIsGlobalReturnAclIdAndAclName() {
NetworkResponse networkResponse = new NetworkResponse();
networkResponse.setVpcAccess(false);
Network networkMock = Mockito.mock(Network.class);
Mockito.when(networkMock.getNetworkACLId()).thenReturn(1L);
Mockito.when(networkACLMock.getVpcId()).thenReturn(0L);
Mockito.when(networkACLMock.getName()).thenReturn(A_NAME);
Mockito.when(networkACLMock.getUuid()).thenReturn(A_UUID);
try (MockedStatic<ApiDBUtils> utils = Mockito.mockStatic(ApiDBUtils.class)) {
utils.when(() -> ApiDBUtils.findByNetworkACLId(1L)).thenReturn(networkACLMock);
apiResponseHelper.setAclIdInResponse(networkMock, networkResponse);
}
Assert.assertEquals(A_UUID, networkResponse.getAclId());
Assert.assertEquals(A_NAME, networkResponse.getAclName());
}
@Test
public void setAclIdInResponseTestCallerHasAccessReturnAclIdAndAclName() {
NetworkResponse networkResponse = new NetworkResponse();
networkResponse.setVpcAccess(true);
Network networkMock = Mockito.mock(Network.class);
Mockito.when(networkMock.getNetworkACLId()).thenReturn(1L);
Mockito.lenient().when(networkACLMock.getVpcId()).thenReturn(2L);
Mockito.when(networkACLMock.getName()).thenReturn(A_NAME);
Mockito.when(networkACLMock.getUuid()).thenReturn(A_UUID);
try (MockedStatic<ApiDBUtils> utils = Mockito.mockStatic(ApiDBUtils.class)) {
utils.when(() -> ApiDBUtils.findByNetworkACLId(1L)).thenReturn(networkACLMock);
apiResponseHelper.setAclIdInResponse(networkMock, networkResponse);
}
Assert.assertEquals(A_UUID, networkResponse.getAclId());
Assert.assertEquals(A_NAME, networkResponse.getAclName());
}
}

View File

@ -570,7 +570,129 @@
<gold-outlined />
<router-link :to="{ path: '/autoscalevmgroup/' + resource.autoscalevmgroupid }">{{ resource.autoscalevmgroupname || resource.autoscalevmgroupid }}</router-link>
</div>
</div>
</div>
<div class="resource-detail-item" v-if="resource.resourcetype && resource.resourceid && routeFromResourceType">
<div class="resource-detail-item__label">{{ $t('label.resource') }}</div>
<div class="resource-detail-item__details">
<resource-label :resourceType="resource.resourcetype" :resourceId="resource.resourceid" :resourceName="resource.resourcename" />
</div>
</div>
<div class="resource-detail-item" v-if="resource.virtualmachineid">
<div class="resource-detail-item__label">{{ $t('label.vmname') }}</div>
<div class="resource-detail-item__details">
<desktop-outlined />
<router-link :to="{ path: createPathBasedOnVmType(resource.vmtype, resource.virtualmachineid) }">{{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} </router-link>
<status class="status status--end" :text="resource.vmstate" v-if="resource.vmstate"/>
</div>
</div>
<div class="resource-detail-item" v-if="resource.volumeid">
<div class="resource-detail-item__label">{{ $t('label.volume') }}</div>
<div class="resource-detail-item__details">
<hdd-outlined />
<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">
<div class="resource-detail-item__label">{{ $t('label.associatednetwork') }}</div>
<div class="resource-detail-item__details">
<wifi-outlined />
<router-link :to="{ path: '/guestnetwork/' + resource.associatednetworkid }">{{ resource.associatednetworkname || resource.associatednetwork || resource.associatednetworkid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.sourceipaddressnetworkid">
<div class="resource-detail-item__label">{{ $t('label.network') }}</div>
<div class="resource-detail-item__details">
<wifi-outlined />
<router-link :to="{ path: '/guestnetwork/' + resource.sourceipaddressnetworkid }">{{ resource.sourceipaddressnetworkname || resource.sourceipaddressnetworkid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.guestnetworkid">
<div class="resource-detail-item__label">{{ $t('label.guestnetwork') }}</div>
<div class="resource-detail-item__details">
<gateway-outlined />
<router-link :to="{ path: '/guestnetwork/' + resource.guestnetworkid }">{{ resource.guestnetworkname || resource.guestnetworkid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.publicip">
<div class="resource-detail-item__label">{{ $t('label.public.ip') }}</div>
<div class="resource-detail-item__details">
<gateway-outlined />
<router-link v-if="resource.publicipid" :to="{ path: '/publicip/' + resource.publicipid }">{{ resource.publicip }} </router-link>
<copy-label v-if="resource.publicipid" :copyValue="resource.publicip" :showIcon=true />
<copy-label v-else :label="resource.publicip" />
</div>
</div>
<div class="resource-detail-item" v-if="resource.vpcid">
<div class="resource-detail-item__label">{{ $t('label.vpcname') }}</div>
<div class="resource-detail-item__details">
<span v-if="images.vpc">
<resource-icon :image="getImage(images.vpc)" size="1x" style="margin-right: 5px"/>
</span>
<deployment-unit-outlined v-else />
<router-link v-if="resource.vpcaccess" :to="{ path: '/vpc/' + resource.vpcid }">{{ resource.vpcname || resource.vpcid }}</router-link>
<span v-else>{{ resource.vpcname || resource.vpcid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.aclid">
<div class="resource-detail-item__label">{{ $t('label.aclid') }}</div>
<div class="resource-detail-item__details">
<span v-if="images.acl">
<resource-icon :image="getImage(images.acl)" size="1x" style="margin-right: 5px"/>
</span>
<deployment-unit-outlined v-else />
<router-link :to="{ path: '/acllist/' + resource.aclid }">{{ resource.aclname || resource.aclid }}</router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.affinitygroup && resource.affinitygroup.length > 0">
<div class="resource-detail-item__label">{{ $t('label.affinitygroup') }}</div>
<SwapOutlined />
<span
v-for="(group, index) in resource.affinitygroup"
:key="group.id"
>
<router-link :to="{ path: '/affinitygroup/' + group.id }">{{ group.name }}</router-link>
<span v-if="index + 1 < resource.affinitygroup.length">, </span>
</span>
</div>
<div class="resource-detail-item" v-if="resource.templateid">
<div class="resource-detail-item__label">{{ resource.templateformat === 'ISO'? $t('label.iso') : $t('label.templatename') }}</div>
<div class="resource-detail-item__details">
<resource-icon v-if="resource.icon" :image="getImage(resource.icon.base64image)" size="1x" style="margin-right: 5px"/>
<SaveOutlined v-else />
<router-link :to="{ path: (resource.templateformat === 'ISO' ? '/iso/' : '/template/') + resource.templateid }">{{ resource.templatedisplaytext || resource.templatename || resource.templateid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.isoid">
<div class="resource-detail-item__label">{{ $t('label.isoname') }}</div>
<div class="resource-detail-item__details">
<resource-icon v-if="resource.icon" :image="getImage(resource.icon.base64image)" size="1x" style="margin-right: 5px"/>
<UsbOutlined v-else />
<router-link :to="{ path: '/iso/' + resource.isoid }">{{ resource.isodisplaytext || resource.isoname || resource.isoid }} </router-link>
</div>
</div>
<div class="resource-detail-item" v-if="resource.serviceofferingname && resource.serviceofferingid">
<div class="resource-detail-item__label" v-if="$route.meta.name === 'router' || $route.meta.name === 'systemvm'">{{ $t('label.system.offering') }}</div>
<div class="resource-detail-item__label" v-else >{{ $t('label.serviceofferingname') }}</div>
<div class="resource-detail-item__details">
<cloud-outlined />
<router-link v-if="!isStatic && ($route.meta.name === 'router' || $route.meta.name === 'systemvm')" :to="{ path: '/systemoffering/' + resource.serviceofferingid}">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
<router-link v-else-if="$router.resolve('/computeoffering/' + resource.serviceofferingid).matched[0].redirect !== '/exception/404'" :to="{ path: '/computeoffering/' + resource.serviceofferingid }">{{ resource.serviceofferingname || resource.serviceofferingid }} </router-link>
<span v-else>{{ resource.serviceofferingname || resource.serviceofferingid }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.rootdiskofferingid && resource.rootdiskofferingdisplaytext || resource.datadiskofferingid && resource.datadiskofferingdisplaytext">
<div class="resource-detail-item__label">{{ $t('label.diskoffering') }}</div>
<div class="resource-detail-item__details">
<hdd-outlined />
<div v-if="resource.rootdiskofferingid">
<router-link v-if="!isStatic && $router.resolve('/diskoffering/' + resource.rootdiskofferingid).matched[0].redirect !== '/exception/404'" :to="{ path: '/diskoffering/' + resource.rootdiskofferingid }">{{ resource.rootdiskofferingdisplaytext }}</router-link>
<span v-else>{{ resource.rootdiskofferingdisplaytext }}</span>
</div>
</div>
<div class="resource-detail-item" v-if="resource.keypairs && resource.keypairs.length > 0">
<div class="resource-detail-item__label">{{ $t('label.keypairs') }}</div>
<div class="resource-detail-item__details">

View File

@ -585,7 +585,7 @@
<router-link :to="{ path: '/guestnetwork/' + record.associatednetworkid }">{{ text }}</router-link>
</template>
<template v-if="column.key === 'vpcname'">
<a v-if="record.vpcid">
<a v-if="record.vpcid && record.vpcaccess">
<router-link :to="{ path: '/vpc/' + record.vpcid }">{{ text || record.vpcid }}</router-link>
</a>
<span v-else>{{ text }}</span>

View File

@ -145,7 +145,9 @@
centered
width="450px">
<a-spin :spinning="acquireLoading" v-ctrl-enter="acquireIpAddress">
<a-alert :message="$t('message.action.acquire.ip')" type="warning" />
<div v-if="$route.path.startsWith('/vpc') && !isNormalUserOrProject()">
<ownership-selection :override="possibleOwnership" @fetch-owner="fetchOwnerOptions"/>
</div>
<a-form layout="vertical" style="margin-top: 10px">
<a-form-item :label="$t('label.ipaddress')">
<infinite-scroll-select
@ -160,6 +162,7 @@
:autoSelectFirstOption="true"
@change-option-value="(ip) => acquireIp = ip" />
</a-form-item>
<a-alert :message="$t('message.action.acquire.ip')" type="warning" />
<div :span="24" class="action-button">
<a-button @click="onCloseModal">{{ $t('label.cancel') }}</a-button>
<a-button ref="submit" type="primary" @click="acquireIpAddress">{{ $t('label.ok') }}</a-button>
@ -210,6 +213,7 @@ import TooltipButton from '@/components/widgets/TooltipButton'
import BulkActionView from '@/components/view/BulkActionView'
import eventBus from '@/config/eventBus'
import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect'
import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue'
export default {
name: 'IpAddressesTab',
@ -217,7 +221,8 @@ export default {
Status,
TooltipButton,
BulkActionView,
InfiniteScrollSelect
InfiniteScrollSelect,
OwnershipSelection
},
props: {
resource: {
@ -281,7 +286,12 @@ export default {
acquireLoading: false,
acquireIp: null,
changeSourceNat: false,
zoneExtNetProvider: ''
zoneExtNetProvider: '',
possibleOwnership: {
domains: null,
projects: null,
accounts: null
}
}
},
async created () {
@ -321,6 +331,9 @@ export default {
}
},
methods: {
isNormalUserOrProject () {
return ['User'].includes(this.$store.getters.userInfo.roletype) || this.$store.getters.project?.id
},
fetchData () {
const params = {
listall: true,
@ -334,6 +347,7 @@ export default {
if (this.vpcTier) {
params.associatednetworkid = this.vpcTier
}
this.getAvailableOwnersForIP()
} else if (this.resource.type === 'Shared') {
params.networkid = this.resource.id
params.allocatedonly = false
@ -362,13 +376,20 @@ export default {
}).catch(reject)
})
},
fetchListPublicIpAddress (state) {
return new Promise((resolve, reject) => {
getAPI('listPublicIpAddresses', this.listApiParams).then(json => {
const listPublicIps = json.listpublicipaddressesresponse.publicipaddress || []
resolve(listPublicIps)
}).catch(reject)
})
fetchOwnerOptions (OwnerOptions) {
this.owner = {}
if (OwnerOptions.selectedAccountType === this.$t('label.account')) {
if (!OwnerOptions.selectedAccount) {
return
}
this.owner.account = OwnerOptions.selectedAccount
this.owner.domainid = OwnerOptions.selectedDomain
} else if (OwnerOptions.selectedAccountType === this.$t('label.project')) {
if (!OwnerOptions.selectedProject) {
return
}
this.owner.projectid = OwnerOptions.selectedProject
}
},
handleTierSelect (tier) {
this.vpcTier = tier
@ -453,6 +474,11 @@ export default {
const params = {}
if (this.$route.path.startsWith('/vpc')) {
params.vpcid = this.resource.id
if (this.owner) {
params.domainid = this.owner.domainid
params.account = this.owner.projectid ? null : this.owner.account
params.projectid = this.owner.projectid ? this.owner.projectid : null
}
if (this.vpcTier) {
params.networkid = this.vpcTier
}
@ -560,6 +586,20 @@ export default {
default: return '/vm/'
}
},
getAvailableOwnersForIP () {
this.possibleOwnership.domains = new Set([this.resource.domainid])
this.possibleOwnership.projects = this.resource.projectid ? new Set([this.resource.projectid]) : new Set([])
this.possibleOwnership.accounts = this.resource.projectid ? new Set([]) : new Set([this.resource.account])
this.resource.network.forEach(network => {
this.possibleOwnership.domains.add(network.domainid)
if (network.projectid) {
this.possibleOwnership.projects.add(network.projectid)
} else {
this.possibleOwnership.accounts.add(network.account)
}
})
},
async onShowAcquireIp () {
this.showAcquireIp = true
},

View File

@ -34,7 +34,14 @@
{{ $t('label.name') }}
</div>
<div>
<router-link :to="{ path: '/guestnetwork/' + network.id }">{{ network.name }} </router-link>
<router-link
v-if="network.projectid"
:to="{ path: '/guestnetwork/' + network.id, query: {projectid: -1 }}">
{{ network.name }}
</router-link>
<router-link v-else :to="{ path: '/guestnetwork/' + network.id }">
{{ network.name }}
</router-link>
<a-tag v-if="network.broadcasturi">{{ network.broadcasturi }}</a-tag>
</div>
</div>
@ -58,6 +65,32 @@
</router-link>
</div>
</div>
<div
class="list__col"
v-if="(network.projectid && resource.account) || (network.projectid !== resource.projectid)"
>
<div class="list__label">
{{ $t('label.project') }}
</div>
<div>
<router-link :to="{ path: '/project/' + network.projectid }">
{{ network.project }}
</router-link>
</div>
</div>
<div
class="list__col"
v-else-if="(network.account && resource.projectid) || network.account !== resource.account || network.domainid !== resource.domainid"
>
<div class="list__label">
{{ $t('label.account') }}
</div>
<div>
<router-link :to="{ path: '/account', query: { name: network.account, domainid: network.domainid, dataView: true } }">
{{ network.account }}
</router-link>
</div>
</div>
</div>
<a-collapse :bordered="false" style="margin-left: -18px">
<template #expandIcon="props">
@ -166,6 +199,9 @@
:footer="null"
@cancel="showCreateNetworkModal = false">
<a-spin :spinning="modalLoading" v-ctrl-enter="handleAddNetworkSubmit">
<div v-if="!isNormalUserOrProject()">
<ownership-selection @fetch-owner="fetchOwnerOptions"/>
</div>
<a-form
layout="vertical"
:ref="formRef"
@ -370,11 +406,13 @@ import { mixinForm } from '@/utils/mixin'
import Status from '@/components/widgets/Status'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { getNetmaskFromCidr } from '@/utils/network'
import OwnershipSelection from '@/views/compute/wizard/OwnershipSelection.vue'
export default {
name: 'VpcTiersTab',
mixins: [mixinForm],
components: {
OwnershipSelection,
Status,
TooltipLabel
},
@ -506,6 +544,9 @@ export default {
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
isNormalUserOrProject () {
return ['User'].includes(this.$store.getters.userInfo.roletype) || this.$store.getters.project?.id
},
initForm () {
this.formRef = ref()
this.form = reactive({})
@ -531,11 +572,28 @@ export default {
}
for (const network of this.networks) {
this.fetchLoadBalancers(network.id)
this.fetchVMs(network.id)
this.fetchVMs(network)
this.updateDisplayCollapsible(network.networkofferingid, network)
}
this.publicLBNetworkExists()
},
fetchOwnerOptions (OwnerOptions) {
this.owner = {}
if (OwnerOptions.selectedAccountType === this.$t('label.account')) {
if (!OwnerOptions.selectedAccount) {
this.owner = undefined
return
}
this.owner.account = OwnerOptions.selectedAccount
this.owner.domainid = OwnerOptions.selectedDomain
} else if (OwnerOptions.selectedAccountType === this.$t('label.project')) {
if (!OwnerOptions.selectedProject) {
this.owner = undefined
return
}
this.owner.projectid = OwnerOptions.selectedProject
}
},
fetchMtuForZone () {
getAPI('listZones', {
id: this.resource.zoneid
@ -675,17 +733,21 @@ export default {
this.fetchLoading = false
})
},
fetchVMs (id) {
fetchVMs (network) {
this.fetchLoading = true
getAPI('listVirtualMachines', {
var params = {
listAll: true,
vpcid: this.resource.id,
networkid: id,
networkid: network.id,
page: this.page,
pagesize: this.pageSize
}).then(json => {
this.vms[id] = json.listvirtualmachinesresponse.virtualmachine || []
this.itemCounts.vms[id] = json.listvirtualmachinesresponse.count || 0
}
if (network.projectid) {
params.projectid = -1
}
getAPI('listVirtualMachines', params).then(json => {
this.vms[network.id] = json.listvirtualmachinesresponse.virtualmachine || []
this.itemCounts.vms[network.id] = json.listvirtualmachinesresponse.count || 0
}).finally(() => {
this.fetchLoading = false
})
@ -748,8 +810,9 @@ export default {
this.showCreateNetworkModal = false
var params = {
vpcid: this.resource.id,
domainid: this.resource.domainid,
account: this.resource.account,
domainid: this.owner?.domainid || this.resource.domainid,
account: this.owner?.projectid ? null : (this.owner?.account || this.resource.account),
projectid: this.owner?.projectid || null,
networkOfferingId: values.networkOffering,
name: values.name,
displayText: values.name,