mirror of https://github.com/apache/cloudstack.git
add associate and disassociate dns zone to network ui screens, network response changes
This commit is contained in:
parent
a1eca74de1
commit
981bb64b86
|
|
@ -1352,6 +1352,8 @@ public class ApiConstants {
|
|||
public static final String DNS_USER_NAME = "dnsusername";
|
||||
public static final String CREDENTIALS = "credentials";
|
||||
public static final String DNS_ZONE_ID = "dnszoneid";
|
||||
public static final String DNS_ZONE = "dnszone";
|
||||
public static final String DNS_SUB_DOMAIN = "dnssubdomain";
|
||||
public static final String DNS_SERVER_ID = "dnsserverid";
|
||||
public static final String CONTENT = "content";
|
||||
public static final String CONTENTS = "contents";
|
||||
|
|
|
|||
|
|
@ -331,6 +331,14 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
|
|||
@Param(description = "The BGP peers for the network", since = "4.20.0")
|
||||
private Set<BgpPeerResponse> bgpPeers;
|
||||
|
||||
@SerializedName(ApiConstants.DNS_ZONE)
|
||||
@Param(description = "DNS zone associated to the network", since = "4.23.0")
|
||||
private String dnsZone;
|
||||
|
||||
@SerializedName(ApiConstants.DNS_SUB_DOMAIN)
|
||||
@Param(description = "DNS subdomain associated to the network", since = "4.23.0")
|
||||
private String dnsSubdomain;
|
||||
|
||||
public NetworkResponse() {}
|
||||
|
||||
public Boolean getDisplayNetwork() {
|
||||
|
|
@ -702,4 +710,12 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement
|
|||
public void setIpv6Dns2(String ipv6Dns2) {
|
||||
this.ipv6Dns2 = ipv6Dns2;
|
||||
}
|
||||
|
||||
public void setDnsZone(String dnsZone) {
|
||||
this.dnsZone = dnsZone;
|
||||
}
|
||||
|
||||
public void setDnsSubdomain(String dnsSubdomain) {
|
||||
this.dnsSubdomain = dnsSubdomain;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupRespon
|
|||
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
|
||||
import org.apache.cloudstack.backup.BackupOffering;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.dns.DnsServer;
|
||||
|
||||
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
|
|
@ -511,11 +510,6 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
|
|||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkAccess(Account account, DnsServer dnsServer) throws PermissionDeniedException {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd){
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@ import org.apache.cloudstack.backup.dao.BackupOfferingDao;
|
|||
import org.apache.cloudstack.backup.dao.BackupRepositoryDao;
|
||||
import org.apache.cloudstack.backup.dao.BackupScheduleDao;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.dns.dao.DnsZoneDao;
|
||||
import org.apache.cloudstack.dns.dao.DnsZoneNetworkMapDao;
|
||||
import org.apache.cloudstack.dns.vo.DnsZoneNetworkMapVO;
|
||||
import org.apache.cloudstack.dns.vo.DnsZoneVO;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
|
|
@ -365,6 +369,7 @@ import org.apache.cloudstack.acl.dao.ApiKeyPairDao;
|
|||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
|
||||
public class ApiDBUtils {
|
||||
private static final Logger log = LogManager.getLogger(ApiDBUtils.class);
|
||||
|
|
@ -506,6 +511,8 @@ public class ApiDBUtils {
|
|||
static SharedFSJoinDao s_sharedFSJoinDao;
|
||||
|
||||
static BucketDao s_bucketDao;
|
||||
static DnsZoneDao s_dnsZoneDao;
|
||||
static DnsZoneNetworkMapDao s_dnsZoneNetworkMapDao;
|
||||
static VirtualMachineManager s_virtualMachineManager;
|
||||
|
||||
@Inject
|
||||
|
|
@ -778,6 +785,10 @@ public class ApiDBUtils {
|
|||
private VirtualMachineManager virtualMachineManager;
|
||||
@Inject
|
||||
private SharedFSJoinDao sharedFSJoinDao;
|
||||
@Inject
|
||||
private DnsZoneDao dnsZoneDao;
|
||||
@Inject
|
||||
private DnsZoneNetworkMapDao dnsZoneNetworkMapDao;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
|
|
@ -916,6 +927,8 @@ public class ApiDBUtils {
|
|||
s_bucketDao = bucketDao;
|
||||
s_virtualMachineManager = virtualMachineManager;
|
||||
s_sharedFSJoinDao = sharedFSJoinDao;
|
||||
s_dnsZoneDao = dnsZoneDao;
|
||||
s_dnsZoneNetworkMapDao = dnsZoneNetworkMapDao;
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////////////////////////
|
||||
|
|
@ -2294,6 +2307,17 @@ public class ApiDBUtils {
|
|||
return details.isEmpty() ? null : details;
|
||||
}
|
||||
|
||||
public static Pair<String, String> findDnsZoneByNetworkId(long networkId) {
|
||||
DnsZoneNetworkMapVO dnsNetworkMapVO = s_dnsZoneNetworkMapDao.findByNetworkId(networkId);
|
||||
if (dnsNetworkMapVO != null) {
|
||||
DnsZoneVO dnsZoneVO = s_dnsZoneDao.findById(dnsNetworkMapVO.getDnsZoneId());
|
||||
if (Strings.isNotBlank(dnsZoneVO.getName())) {
|
||||
return new Pair<> (dnsZoneVO.getName(), dnsNetworkMapVO.getSubDomain());
|
||||
}
|
||||
}
|
||||
return new Pair<>(null, null);
|
||||
}
|
||||
|
||||
public static boolean isAdmin(Account account) {
|
||||
return s_accountService.isAdmin(account.getId());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -543,6 +543,7 @@ public class ApiResponseHelper implements ResponseGenerator {
|
|||
@Inject
|
||||
ResourceIconManager resourceIconManager;
|
||||
|
||||
|
||||
public static String getPrettyDomainPath(String path) {
|
||||
if (path == null) {
|
||||
return null;
|
||||
|
|
@ -2635,6 +2636,14 @@ public class ApiResponseHelper implements ResponseGenerator {
|
|||
response.setDetails(details);
|
||||
}
|
||||
|
||||
Pair<String, String> dnsZoneAndSubDomain = ApiDBUtils.findDnsZoneByNetworkId(network.getId());
|
||||
if (StringUtils.isNotBlank(dnsZoneAndSubDomain.first())) {
|
||||
response.setDnsZone(dnsZoneAndSubDomain.first());
|
||||
}
|
||||
if (StringUtils.isNotBlank(dnsZoneAndSubDomain.second())) {
|
||||
response.setDnsSubdomain(dnsZoneAndSubDomain.second());
|
||||
}
|
||||
|
||||
DataCenter zone = ApiDBUtils.findZoneById(network.getDataCenterId());
|
||||
if (zone != null) {
|
||||
response.setZoneId(zone.getUuid());
|
||||
|
|
|
|||
|
|
@ -661,10 +661,10 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
|
|||
return null;
|
||||
}
|
||||
DnsServerVO server = dnsServerDao.findById(dnsZone.getDnsServerId());
|
||||
// Construct FQDN Prefix (e.g., "instance-id" or "instance-id.subdomain")
|
||||
// Construct FQDN Prefix (e.g., "instance-id.dnsZoneName" or "instance-id.subdomain.dnsZoneName")
|
||||
String recordName = String.valueOf(instance.getInstanceName());
|
||||
if (StringUtils.isNotBlank(dnsZoneNetworkMap.getSubDomain())) {
|
||||
recordName = recordName + "." + dnsZoneNetworkMap.getSubDomain();
|
||||
recordName = String.join(".", recordName, dnsZoneNetworkMap.getSubDomain(), dnsZone.getName());
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -967,12 +967,15 @@
|
|||
"label.dns.server": "DNS Server",
|
||||
"label.dnsserverid": "DNS Server ID",
|
||||
"label.dnsservername": "DNS Server name",
|
||||
"label.dnssubdomain": "DNS Subdomain",
|
||||
"label.dns.servers": "DNS Servers",
|
||||
"label.dns.zone": "DNS Zone",
|
||||
"label.dns.zones": "DNS Zones",
|
||||
"label.dns.update.server": "Update DNS Server",
|
||||
"label.dns.update.zone": "Update DNS Zone",
|
||||
"label.dns.zone.select": "Select a DNS zone",
|
||||
"label.dnsrecords": "DNS Records",
|
||||
"label.dnszone": "DNS Zone",
|
||||
"label.domain": "Domain",
|
||||
"label.domain.id": "Domain ID",
|
||||
"label.domain.name": "Domain name",
|
||||
|
|
@ -2498,6 +2501,7 @@
|
|||
"label.storagetype": "Storage type",
|
||||
"label.storageip": "Storage IP address",
|
||||
"label.strict": "Strict",
|
||||
"label.subdomain": "Subdomain",
|
||||
"label.subdomainaccess": "Subdomain access",
|
||||
"label.submit": "Submit",
|
||||
"label.subnet": "Subnet",
|
||||
|
|
@ -2990,6 +2994,9 @@
|
|||
"message.confirm.delete.dns.record": "Are you sure you want to delete this DNS record?",
|
||||
"message.action.delete.dns.server": "Please confirm you want to delete this DNS server.",
|
||||
"message.action.delete.dns.zone": "Please confirm you want to delete this DNS zone.",
|
||||
"label.action.associate.dns.zone": "Associate DNS Zone",
|
||||
"label.action.disassociate.dns.zone": "Disassociate DNS Zone",
|
||||
"message.action.disassociate.dns.zone": "Please confirm you want to disassociate the DNS zone from this network.",
|
||||
"message.action.delete.domain": "Please confirm that you want to delete this domain.",
|
||||
"message.action.delete.extension": "Please confirm that you want to delete the extension",
|
||||
"message.action.delete.external.firewall": "Please confirm that you would like to remove this external firewall. Warning: If you are planning to add back the same external firewall, you must reset usage data on the device.",
|
||||
|
|
@ -3911,6 +3918,8 @@
|
|||
"message.success.update.dns.server": "Successfully updated DNS server",
|
||||
"message.success.update.dns.zone": "Successfully updated DNS zone",
|
||||
"message.success.delete.dns.record": "Successfully deleted DNS record",
|
||||
"message.success.associate.dns.zone": "Successfully associated DNS zone with network",
|
||||
"message.error.fetch.dns.zones": "Could not load DNS zones.",
|
||||
"message.success.add.guest.network": "Successfully created guest Network",
|
||||
"message.success.add.gpu.device": "Successfully added GPU device",
|
||||
"message.success.add.interface.static.route": "Successfully added interface Static Route",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
import { shallowRef, defineAsyncComponent } from 'vue'
|
||||
import store from '@/store'
|
||||
import tungsten from '@/assets/icons/tungsten.svg?inline'
|
||||
import { isAdmin } from '@/role'
|
||||
import { isAdmin, isAdminOrDomainAdmin } from '@/role'
|
||||
import { isZoneCreated } from '@/utils/zone'
|
||||
import { vueProps } from '@/vue-app'
|
||||
|
||||
|
|
@ -49,7 +49,10 @@ export default {
|
|||
return fields
|
||||
},
|
||||
details: () => {
|
||||
var fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'asnumber', 'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account', 'domainpath', 'associatednetwork', 'associatednetworkid', 'ip4routing', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu', 'privatemtu']
|
||||
var fields = ['name', 'id', 'description', 'type', 'traffictype', 'vpcid', 'vlan', 'broadcasturi', 'cidr', 'ip6cidr', 'netmask', 'gateway', 'asnumber',
|
||||
'aclname', 'ispersistent', 'restartrequired', 'reservediprange', 'redundantrouter', 'networkdomain', 'egressdefaultpolicy', 'zonename', 'account',
|
||||
'domainpath', 'associatednetwork', 'associatednetworkid', 'ip4routing', 'ip6firewall', 'ip6routing', 'ip6routes', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2',
|
||||
'publicmtu', 'privatemtu', 'dnszone', 'dnssubdomain']
|
||||
if (!isAdmin()) {
|
||||
fields = fields.filter(function (e) { return e !== 'broadcasturi' })
|
||||
}
|
||||
|
|
@ -202,6 +205,36 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'associateDnsZoneToNetwork',
|
||||
icon: 'link-outlined',
|
||||
label: 'label.action.associate.dns.zone',
|
||||
dataView: true,
|
||||
show: (record, store) => {
|
||||
return (record.type === 'Shared' && record.dnszone === undefined &&
|
||||
(record.account === store.userInfo.account || isAdminOrDomainAdmin(store.userInfo.roletype)))
|
||||
},
|
||||
popup: true,
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/AssociateDnsZone.vue')))
|
||||
},
|
||||
{
|
||||
api: 'disassociateDnsZoneFromNetwork',
|
||||
icon: 'disconnect-outlined',
|
||||
label: 'label.action.disassociate.dns.zone',
|
||||
message: 'message.action.disassociate.dns.zone',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
args: ['networkid'],
|
||||
show: (record, store) => {
|
||||
return record.dnszone !== undefined && record.type === 'Shared' &&
|
||||
(record.account === store.userInfo.account || isAdminOrDomainAdmin(store.userInfo.roletype))
|
||||
},
|
||||
mapping: {
|
||||
networkid: {
|
||||
value: (record) => { return record.id }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
api: 'deleteNetwork',
|
||||
icon: 'delete-outlined',
|
||||
|
|
|
|||
|
|
@ -57,11 +57,10 @@
|
|||
<a-descriptions-item :label="$t('label.isolationuri')" v-if="record.isolationuri">
|
||||
{{ record.isolationuri }}
|
||||
</a-descriptions-item>
|
||||
|
||||
<a-descriptions-item :label="$t('label.dns.record.url')" v-if="record.dnsrecordurl">
|
||||
{{ record.dnsrecordurl }}
|
||||
</a-descriptions-item>
|
||||
</template>
|
||||
<a-descriptions-item :label="$t('label.dns.record.url')" v-if="record.dnsrecordurl">
|
||||
{{ record.dnsrecordurl }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<template #bodyCell="{ column, text, record }">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,193 @@
|
|||
// 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.
|
||||
|
||||
<template>
|
||||
<div class="form-layout" v-ctrl-enter="handleSubmit">
|
||||
<a-spin :spinning="loading">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
layout="vertical">
|
||||
|
||||
<a-form-item name="dnszoneid" ref="dnszoneid">
|
||||
<template #label>
|
||||
<tooltip-label
|
||||
:title="$t('label.dns.zone')"
|
||||
:tooltip="apiParams.dnszoneid?.description" />
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="form.dnszoneid"
|
||||
:placeholder="$t('label.dns.zone.select')"
|
||||
:loading="fetchingZones"
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
v-focus="true">
|
||||
<a-select-option
|
||||
v-for="zone in dnsZones"
|
||||
:key="zone.id"
|
||||
:value="zone.id"
|
||||
:label="zone.name">
|
||||
{{ zone.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="subdomain" ref="subdomain">
|
||||
<template #label>
|
||||
<tooltip-label
|
||||
:title="$t('label.subdomain')"
|
||||
:tooltip="apiParams.subdomain?.description" />
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.subdomain"
|
||||
:placeholder="apiParams.subdomain?.description" />
|
||||
</a-form-item>
|
||||
|
||||
<div class="action-button">
|
||||
<a-button @click="closeAction">
|
||||
{{ $t('label.cancel') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="handleSubmit">
|
||||
{{ $t('label.ok') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAPI, postAPI } from '@/api'
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
|
||||
export default {
|
||||
name: 'AssociateDnsZone',
|
||||
components: {
|
||||
TooltipLabel
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
apiParams: {},
|
||||
form: {
|
||||
dnszoneid: undefined,
|
||||
subdomain: ''
|
||||
},
|
||||
rules: {},
|
||||
fetchingZones: false,
|
||||
dnsZones: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.apiParams = this.$getApiParams('associateDnsZoneToNetwork') || {}
|
||||
this.rules = {
|
||||
dnszoneid: [{ required: true, message: this.$t('message.error.required.input') }]
|
||||
}
|
||||
this.fetchDnsZones()
|
||||
},
|
||||
methods: {
|
||||
async handleSubmit () {
|
||||
if (this.loading) return
|
||||
|
||||
try {
|
||||
await this.$refs.formRef.validate()
|
||||
} catch (error) {
|
||||
const field = error?.errorFields?.[0]?.name
|
||||
if (field) {
|
||||
this.$refs.formRef.scrollToField(field)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
const params = {
|
||||
dnszoneid: this.form.dnszoneid,
|
||||
networkid: this.resource.id
|
||||
}
|
||||
|
||||
if (this.form.subdomain && this.form.subdomain.trim()) {
|
||||
params.subdomain = this.form.subdomain.trim()
|
||||
}
|
||||
|
||||
await postAPI('associateDnsZoneToNetwork', params)
|
||||
this.$notification.success({
|
||||
message: this.$t('label.action.associate.dns.zone'),
|
||||
description: this.$t('message.success.associate.dns.zone')
|
||||
})
|
||||
this.$emit('refresh-data')
|
||||
this.closeAction()
|
||||
} catch (error) {
|
||||
this.$notification.error({
|
||||
message: this.$t('message.request.failed'),
|
||||
description: error?.response?.headers['x-description'] || error.message,
|
||||
duration: 0
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
closeAction () {
|
||||
this.$emit('close-action')
|
||||
},
|
||||
async fetchDnsZones () {
|
||||
this.fetchingZones = true
|
||||
try {
|
||||
const response = await getAPI('listDnsZones')
|
||||
const listResponse = response?.listdnszonesresponse || {}
|
||||
this.dnsZones = listResponse.dnszone || []
|
||||
if (this.dnsZones.length > 0) {
|
||||
this.form.dnszoneid = this.dnsZones[0].id
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch DNS zones', error)
|
||||
this.$message.warning(this.$t('message.error.fetch.dns.zones'))
|
||||
} finally {
|
||||
this.fetchingZones = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form-layout {
|
||||
width: 80vw;
|
||||
@media (min-width: 600px) {
|
||||
width: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-button {
|
||||
text-align: right;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.action-button button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue