fix list dnsservers api, ui screens for dns servers, generate events

This commit is contained in:
Manoj Kumar 2026-03-04 19:40:37 +05:30
parent 369bb16d0d
commit 0df50cedf8
No known key found for this signature in database
GPG Key ID: E952B7234D2C6F88
13 changed files with 400 additions and 102 deletions

View File

@ -864,8 +864,10 @@ public class EventTypes {
// DNS Framework Events
public static final String EVENT_DNS_SERVER_ADD = "DNS.SERVER.ADD";
public static final String EVENT_DNS_SERVER_UPDATE = "DNS.SERVER.UPDATE";
public static final String EVENT_DNS_SERVER_DELETE = "DNS.SERVER.DELETE";
public static final String EVENT_DNS_ZONE_CREATE = "DNS.ZONE.CREATE";
public static final String EVENT_DNS_ZONE_UPDATE = "DNS.ZONE.UPDATE";
public static final String EVENT_DNS_ZONE_DELETE = "DNS.ZONE.DELETE";
public static final String EVENT_DNS_RECORD_CREATE = "DNS.RECORD.CREATE";
public static final String EVENT_DNS_RECORD_DELETE = "DNS.RECORD.DELETE";

View File

@ -19,7 +19,6 @@ package com.cloud.user;
import java.util.List;
import java.util.Map;
import com.cloud.utils.Pair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@ -27,6 +26,9 @@ import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.dns.DnsServer;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
@ -35,8 +37,7 @@ import com.cloud.network.vpc.VpcOffering;
import com.cloud.offering.DiskOffering;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.ServiceOffering;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import com.cloud.utils.Pair;
public interface AccountService {
@ -119,6 +120,8 @@ public interface AccountService {
void checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException;
void checkAccess(Account account, DnsServer dnsServer) throws PermissionDeniedException;
void checkAccess(User user, ControlledEntity entity);
void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) throws PermissionDeniedException;

View File

@ -55,8 +55,8 @@ public class AddDnsServerCmd extends BaseCmd {
@Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "API URL of the provider")
private String url;
@Parameter(name = ApiConstants.PROVIDER_TYPE, type = CommandType.STRING, required = true, description = "Provider type (e.g., PowerDNS)")
private String providerType;
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, required = true, description = "Provider type (e.g., PowerDNS)")
private String provider;
@Parameter(name = ApiConstants.DNS_USER_NAME, type = CommandType.STRING,
description = "Username or email associated with the external DNS provider account (used for authentication)")
@ -109,8 +109,8 @@ public class AddDnsServerCmd extends BaseCmd {
return nameServers;
}
public DnsProviderType getProviderType() {
DnsProviderType dnsProviderType = EnumUtils.getEnumIgnoreCase(DnsProviderType.class, providerType, DnsProviderType.PowerDNS);
public DnsProviderType getProvider() {
DnsProviderType dnsProviderType = EnumUtils.getEnumIgnoreCase(DnsProviderType.class, provider, DnsProviderType.PowerDNS);
if (dnsProviderType == null) {
throw new InvalidParameterValueException(String.format("Invalid value passed for provider type, valid values are: %s",
EnumUtils.listValues(DnsProviderType.values())));

View File

@ -38,7 +38,6 @@ import org.apache.cloudstack.api.response.DnsZoneResponse;
import org.apache.cloudstack.api.response.ListResponse;
import com.cloud.network.Network;
import com.cloud.user.Account;
import com.cloud.utils.component.Manager;
import com.cloud.utils.component.PluggableService;
import com.cloud.vm.Nic;
@ -75,7 +74,5 @@ public interface DnsProviderManager extends Manager, PluggableService {
boolean disassociateZoneFromNetwork(DisassociateDnsZoneFromNetworkCmd cmd);
void checkDnsServerPermissions(Account caller, DnsServer server);
String processDnsRecordForInstance(VirtualMachine instance, Network network, Nic nic, boolean isAdd);
}

View File

@ -30,8 +30,6 @@ import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.dns.DnsProviderManager;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
import org.springframework.stereotype.Component;
@ -103,8 +101,6 @@ public class DomainChecker extends AdapterBase implements SecurityChecker {
private ProjectDao projectDao;
@Inject
private AccountService accountService;
@Inject
private DnsProviderManager dnsProviderManager;
protected DomainChecker() {
super();
@ -220,8 +216,6 @@ public class DomainChecker extends AdapterBase implements SecurityChecker {
_networkMgr.checkRouterPermissions(caller, (VirtualRouter)entity);
} else if (entity instanceof AffinityGroup) {
return false;
} else if (entity instanceof DnsServer) {
dnsProviderManager.checkDnsServerPermissions(caller, (DnsServer) entity);
} else {
validateCallerHasAccessToEntityOwner(caller, entity, accessType);
}

View File

@ -179,9 +179,9 @@ import com.cloud.user.dao.UserDataDao;
import com.cloud.utils.ConstantTimeComparator;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.Ternary;
import com.cloud.utils.UuidUtils;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.Manager;
import com.cloud.utils.component.ManagerBase;
@ -737,8 +737,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
if (entity.getAccountId() != -1 && domainId != -1 && !(entity instanceof VirtualMachineTemplate)
&& !(entity instanceof Network && (accessType == AccessType.UseEntry || accessType == AccessType.OperateEntry))
&& !(entity instanceof AffinityGroup) && !(entity instanceof VirtualRouter)
&& !(entity instanceof DnsServer)) {
&& !(entity instanceof AffinityGroup) && !(entity instanceof VirtualRouter)) {
List<ControlledEntity> toBeChecked = domains.get(entity.getDomainId());
// for templates, we don't have to do cross domains check
if (toBeChecked == null) {
@ -3636,6 +3635,20 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + bof);
}
@Override
public void checkAccess(Account caller, DnsServer dnsServer) throws PermissionDeniedException {
if (caller.getId() == dnsServer.getAccountId()) {
return;
}
if (!dnsServer.isPublicServer()) {
throw new PermissionDeniedException(caller + "is not allowed to access the DNS server " + dnsServer.getName());
}
Account owner = getAccount(dnsServer.getAccountId());
if (!_domainDao.isChildDomain(owner.getDomainId(), caller.getDomainId())) {
throw new PermissionDeniedException(caller + "is not allowed to access the DNS server " + dnsServer.getName());
}
}
@Override
public void checkAccess(User user, ControlledEntity entity) throws PermissionDeniedException {
for (SecurityChecker checker : _securityCheckers) {

View File

@ -60,22 +60,20 @@ import org.apache.cloudstack.dns.vo.DnsZoneVO;
import org.springframework.stereotype.Component;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.network.Network;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.projects.Project;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.Ternary;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.component.PluggableService;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.Nic;
import com.cloud.vm.VirtualMachine;
@ -119,6 +117,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_SERVER_ADD, eventDescription = "Adding a DNS Server")
public DnsServer addDnsServer(AddDnsServerCmd cmd) {
Account caller = CallContext.current().getCallingAccount();
DnsServer existing = dnsServerDao.findByUrlAndAccount(cmd.getUrl(), caller.getId());
@ -139,7 +138,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
publicDomainSuffix = DnsProviderUtil.normalizeDomain(publicDomainSuffix);
}
DnsProviderType type = cmd.getProviderType();
DnsProviderType type = cmd.getProvider();
DnsServerVO server = new DnsServerVO(cmd.getName(), cmd.getUrl(), cmd.getPort(), cmd.getExternalServerId(), type,
cmd.getDnsUserName(), cmd.getCredentials(), isDnsPublic, publicDomainSuffix, cmd.getNameServers(),
caller.getAccountId(), caller.getDomainId());
@ -159,12 +158,15 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
@Override
public ListResponse<DnsServerResponse> listDnsServers(ListDnsServersCmd cmd) {
Pair<List<DnsServerVO>, Integer> result = searchForDnsServerInternal(cmd);
ListResponse<DnsServerResponse> response = new ListResponse<>();
if (result == null) {
return response;
}
List<String> serverIds = new ArrayList<>();
for (DnsServer server : result.first()) {
serverIds.add(server.getUuid());
}
List<DnsServerJoinVO> joinResult = dnsServerJoinDao.listByUuids(serverIds);
ListResponse<DnsServerResponse> response = new ListResponse<>();
List<DnsServerResponse> serverResponses = new ArrayList<>();
for (DnsServerJoinVO server : joinResult) {
serverResponses.add(createDnsServerResponse(server));
@ -176,64 +178,20 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
private Pair<List<DnsServerVO>, Integer> searchForDnsServerInternal(ListDnsServersCmd cmd) {
Long dnsServerId = cmd.getId();
Account caller = CallContext.current().getCallingAccount();
Long domainId = cmd.getDomainId();
boolean isRecursive = cmd.isRecursive();
// Step 1: Build ACL search parameters based on caller permissions
List<Long> permittedAccountIds = new ArrayList<>();
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(
domainId, isRecursive, null);
accountMgr.buildACLSearchParameters(caller, dnsServerId, cmd.getAccountName(), null, permittedAccountIds,
domainIdRecursiveListProject, cmd.listAll(), false);
domainId = domainIdRecursiveListProject.first();
isRecursive = domainIdRecursiveListProject.second();
Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(DnsServerVO.class, ApiConstants.ID, true, cmd.getStartIndex(), cmd.getPageSizeVal());
// Step 2: Search for caller's own DNS servers using standard ACL pattern
SearchBuilder<DnsServerVO> sb = dnsServerDao.createSearchBuilder();
accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccountIds, listProjectResourcesCriteria);
sb.and(ApiConstants.STATE, sb.entity().getState(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.PROVIDER_TYPE, sb.entity().getProviderType(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<DnsServerVO> sc = sb.create();
accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccountIds, listProjectResourcesCriteria);
sc.setParameters(ApiConstants.STATE, DnsServer.State.Enabled);
sc.setParameters(ApiConstants.PROVIDER_TYPE, cmd.getProviderType());
Pair<List<DnsServerVO>, Integer> ownServersPair = dnsServerDao.searchAndCount(sc, searchFilter);
List<DnsServerVO> dnsServers = new ArrayList<>(ownServersPair.first());
int count = ownServersPair.second();
if (cmd.getId() == null) {
Set<Long> parentDomainIds = domainDao.getDomainParentIds(caller.getDomainId());
if (!parentDomainIds.isEmpty()) {
SearchBuilder<DnsServerVO> publicSb = dnsServerDao.createSearchBuilder();
publicSb.and(ApiConstants.IS_PUBLIC, publicSb.entity().isPublicServer(), SearchCriteria.Op.EQ);
publicSb.and(ApiConstants.DOMAIN_IDS, publicSb.entity().getDomainId(), SearchCriteria.Op.IN);
publicSb.and(ApiConstants.STATE, publicSb.entity().getState(), SearchCriteria.Op.EQ);
publicSb.and(ApiConstants.PROVIDER_TYPE, publicSb.entity().getProviderType(), SearchCriteria.Op.EQ);
publicSb.done();
SearchCriteria<DnsServerVO> publicSc = publicSb.create();
publicSc.setParameters(ApiConstants.IS_PUBLIC, 1);
publicSc.setParameters(ApiConstants.DOMAIN_IDS, parentDomainIds.toArray());
publicSc.setParameters(ApiConstants.STATE, DnsServer.State.Enabled);
publicSc.setParameters(ApiConstants.PROVIDER_TYPE, cmd.getProviderType());
List<DnsServerVO> publicServers = dnsServerDao.search(publicSc, null);
List<Long> ownServerIds = dnsServers.stream().map(DnsServerVO::getId).collect(Collectors.toList());
for (DnsServerVO publicServer : publicServers) {
if (!ownServerIds.contains(publicServer.getId())) {
dnsServers.add(publicServer);
count++;
}
}
if (dnsServerId != null) {
DnsServerVO dnsServerVO = dnsServerDao.findById(dnsServerId);
if (dnsServerVO == null) {
return null;
}
return new Pair<>(Collections.singletonList(dnsServerVO), 1);
}
return new Pair<>(dnsServers, count);
Set<Long> parentDomainIds = domainDao.getDomainParentIds(caller.getDomainId());
Filter searchFilter = new Filter(DnsServerVO.class, ApiConstants.ID, true, cmd.getStartIndex(), cmd.getPageSizeVal());
return dnsServerDao.searchDnsServer(dnsServerId, caller.getAccountId(), parentDomainIds, cmd.getProviderType(), cmd.getKeyword(), searchFilter);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_SERVER_UPDATE, eventDescription = "Updating DNS Server")
public DnsServer updateDnsServer(UpdateDnsServerCmd cmd) {
Long dnsServerId = cmd.getId();
DnsServerVO dnsServer = dnsServerDao.findById(dnsServerId);
@ -242,7 +200,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, true, dnsServer);
accountMgr.checkAccess(caller, dnsServer);
boolean validationRequired = false;
String originalUrl = dnsServer.getUrl();
@ -305,6 +263,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_SERVER_DELETE, eventDescription = "Deleting DNS Server")
public boolean deleteDnsServer(DeleteDnsServerCmd cmd) {
Long dnsServerId = cmd.getId();
DnsServerVO dnsServer = dnsServerDao.findById(dnsServerId);
@ -312,11 +271,12 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
throw new InvalidParameterValueException(String.format("DNS server with ID: %s not found.", dnsServerId));
}
Account caller = CallContext.current().getCallingAccount();
accountMgr.checkAccess(caller, null, true, dnsServer);
accountMgr.checkAccess(caller, dnsServer);
return dnsServerDao.remove(dnsServerId);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_ZONE_DELETE, eventDescription = "Deleting DNS Zone")
public boolean deleteDnsZone(Long zoneId) {
DnsZoneVO zone = dnsZoneDao.findById(zoneId);
if (zone == null) {
@ -340,6 +300,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_ZONE_UPDATE, eventDescription = "Updating DNS Zone")
public DnsZone updateDnsZone(UpdateDnsZoneCmd cmd) {
DnsZoneVO dnsZone = dnsZoneDao.findById(cmd.getId());
if (dnsZone == null) {
@ -396,7 +357,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
Account caller = CallContext.current().getCallingAccount();
if (cmd.getDnsServerId() != null) {
DnsServer dnsServer = dnsServerDao.findById(cmd.getDnsServerId());
accountMgr.checkAccess(caller, null, false, dnsServer);
accountMgr.checkAccess(caller, dnsServer);
}
List<Long> ownDnsServerIds = dnsServerDao.listDnsServerIdsByAccountId(caller.getAccountId());
String keyword = cmd.getKeyword();
@ -408,6 +369,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_RECORD_CREATE, eventDescription = "Creating DNS Record")
public DnsRecordResponse createDnsRecord(CreateDnsRecordCmd cmd) {
String recordName = StringUtils.trimToEmpty(cmd.getName()).toLowerCase();
if (StringUtils.isBlank(recordName)) {
@ -436,6 +398,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DNS_RECORD_DELETE, eventDescription = "Deleting DNS Record")
public boolean deleteDnsRecord(DeleteDnsRecordCmd cmd) {
DnsZoneVO zone = dnsZoneDao.findById(cmd.getDnsZoneId());
if (zone == null) {
@ -651,21 +614,6 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa
return dnsZoneNetworkMapDao.remove(mapping.getId());
}
@Override
public void checkDnsServerPermissions(Account caller, DnsServer server) {
if (caller.getId() == server.getAccountId()) {
return;
}
if (!server.isPublicServer()) {
throw new PermissionDeniedException(caller + "is not allowed to access the DNS server " + server.getName());
}
Account owner = accountMgr.getAccount(server.getAccountId());
if (!domainDao.isChildDomain(caller.getDomainId(), owner.getDomainId())) {
throw new PermissionDeniedException(caller + "is not allowed to access the DNS server " + server.getName());
}
}
@Override
public String processDnsRecordForInstance(VirtualMachine instance, Network network, Nic nic, boolean isAdd) {
long networkId = network.getId();

View File

@ -18,7 +18,9 @@
package org.apache.cloudstack.dns.dao;
import java.util.List;
import java.util.Set;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.vo.DnsServerVO;
@ -32,4 +34,6 @@ public interface DnsServerDao extends GenericDao<DnsServerVO, Long> {
List<Long> listDnsServerIdsByAccountId(Long accountId);
Pair<List<DnsServerVO>, Integer> searchDnsServers(Long id, String keyword, String provider, Long accountId, Filter filter);
Pair<List<DnsServerVO>, Integer> searchDnsServer(Long dnsServerId, Long accountId, Set<Long> domainIds, DnsProviderType providerType, String keyword, Filter filter);
}

View File

@ -18,10 +18,13 @@
package org.apache.cloudstack.dns.dao;
import java.util.List;
import java.util.Set;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.dns.DnsProviderType;
import org.apache.cloudstack.dns.DnsServer;
import org.apache.cloudstack.dns.vo.DnsServerVO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import com.cloud.utils.Pair;
@ -96,4 +99,44 @@ public class DnsServerDaoImpl extends GenericDaoBase<DnsServerVO, Long> implemen
}
return searchAndCount(sc, filter);
}
@Override
public Pair<List<DnsServerVO>, Integer> searchDnsServer(Long dnsServerId, Long accountId, Set<Long> domainIds, DnsProviderType providerType,
String keyword, Filter filter) {
SearchBuilder<DnsServerVO> sb = createSearchBuilder();
sb.and(ApiConstants.ID, sb.entity().getId(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.NAME, sb.entity().getName(), SearchCriteria.Op.LIKE);
sb.and().op(ApiConstants.ACCOUNT_ID, sb.entity().getAccountId(), SearchCriteria.Op.EQ);
if (!CollectionUtils.isEmpty(domainIds)) {
sb.or().op(ApiConstants.IS_PUBLIC, sb.entity().isPublicServer(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.DOMAIN_IDS, sb.entity().getDomainId(), SearchCriteria.Op.IN);
sb.cp();
}
sb.cp();
sb.and(ApiConstants.PROVIDER_TYPE, sb.entity().getProviderType(), SearchCriteria.Op.EQ);
sb.and(ApiConstants.STATE, sb.entity().getState(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<DnsServerVO> sc = sb.create();
if (dnsServerId != null) {
sc.setParameters(ApiConstants.ID, dnsServerId);
}
if (accountId != null) {
sc.setParameters(ApiConstants.ACCOUNT_ID, accountId);
}
if (!CollectionUtils.isEmpty(domainIds)) {
sc.setParameters(ApiConstants.IS_PUBLIC, true);
sc.setParameters(ApiConstants.DOMAIN_IDS, domainIds.toArray());
}
if (providerType != null) {
sc.setParameters(ApiConstants.PROVIDER_TYPE, providerType);
}
if (keyword != null) {
sc.setParameters(ApiConstants.NAME, "%" + keyword + "%");
}
sc.setParameters(ApiConstants.STATE, DnsServer.State.Enabled);
return searchAndCount(sc, filter);
}
}

View File

@ -91,6 +91,7 @@
"label.action.delete.account": "Delete Account",
"label.action.delete.backup.offering": "Delete backup offering",
"label.action.delete.cluster": "Delete Cluster",
"label.action.delete.dns.server": "Delete DNS Server",
"label.action.delete.domain": "Delete Domain",
"label.action.delete.egress.firewall": "Delete Egress Firewall Rule",
"label.action.delete.firewall": "Delete Firewall Rule",
@ -292,6 +293,10 @@
"label.add.ip.range": "Add IP Range",
"label.add.ipv4.subnet": "Add IPv4 Subnet for Routed Networks",
"label.dns.server": "DNS Server",
"label.dns.add.server": "Add DNS Server",
"label.dns.update.server": "Update DNS Server",
"label.dns.delete.server": "Delete DNS Server",
"label.dnsrecords": "DNS Records",
"label.add.ip.v6.prefix": "Add IPv6 prefix",
"label.add.isolated.network": "Add Isolated Network",
"label.add.kubernetes.cluster": "Add Kubernetes Cluster",
@ -713,6 +718,8 @@
"label.created": "Created",
"label.creating": "Creating",
"label.creating.iprange": "Creating IP ranges",
"label.dns.credentials": "DNS API key",
"label.nameservers": "DNS Nameservers",
"label.credit": "Credit",
"label.cron": "Cron expression",
"label.cron.mode": "Cron mode",
@ -2947,6 +2954,7 @@
"message.action.delete.backup.schedule": "Please confirm that you want to delete this backup schedule?",
"message.action.delete.cluster": "Please confirm that you want to delete this Cluster.",
"message.action.delete.custom.action": "Please confirm that you want to delete this custom action.",
"message.action.delete.dns.server": "Please confirm you want to delete this DNS server.",
"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.",

View File

@ -207,6 +207,13 @@
</div>
</div>
</a-list-item>
<a-list-item v-else-if="item === 'provider' && $route.path.includes('/dnsserver')">
<div>
<strong>{{ $t('label.provider') }}</strong>
<br/>
<div>{{ dataResource[item] }}</div>
</div>
</a-list-item>
<external-configuration-details
v-else-if="item === 'externaldetails' && (['host', 'computeoffering'].includes($route.meta.name) || (['cluster'].includes($route.meta.name) && dataResource.extensionid))"
:resource="dataResource" />

View File

@ -1510,24 +1510,60 @@ export default {
permission: ['listDnsZones'],
columns: ['name', 'state', 'dnsservername', 'dnsserveraccount'],
details: ['name', 'state', 'dnsservername', 'dnsserveraccount'],
related: [{
tabs: [{
name: 'details',
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'dnsrecords',
title: 'label.dns.records',
param: 'dnszoneid'
component: shallowRef(defineAsyncComponent(() => import('@/views/network/InternalLBAssignedVmTab.vue'))),
show: () => true
}]
},
{
name: 'dnsservers',
name: 'dnsserver',
title: 'label.dns.server',
icon: 'global-outlined',
permission: ['listDnsServers'],
columns: ['name', 'url', 'provider'],
details: ['name', 'url', 'ispublic', 'port', 'nameservers', 'domain', 'account'],
details: ['name', 'url', 'abc', 'provider', 'ispublic', 'port', 'nameservers', 'domain', 'account'],
related: [{
name: 'dnszones',
title: 'label.dns.zone',
param: 'dnsserverid'
}]
}],
actions: [
{
api: 'addDnsServer',
icon: 'plus-outlined',
label: 'label.dns.add.server',
listView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/AddDnsServer.vue'))),
show: () => {
return true
}
},
{
api: 'updateDnsServer',
icon: 'edit-outlined',
label: 'label.dns.update.server',
dataView: true,
popup: true,
component: shallowRef(defineAsyncComponent(() => import('@/views/network/dns/AddDnsServer.vue'))),
show: (record) => { return true }
},
{
api: 'deleteDnsServer',
icon: 'delete-outlined',
label: 'label.dns.delete.server',
message: 'message.action.delete.dns.server',
dataView: true,
groupAction: true,
show: (record) => { return true },
groupMap: (selection) => { return selection.map(x => { return { id: x } }) }
}
]
}
]
}

View File

@ -0,0 +1,243 @@
// 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="name" ref="name">
<template #label>
<tooltip-label
:title="$t('label.name')"
:tooltip="apiParams.name?.description" />
</template>
<a-input
v-model:value="form.name"
:placeholder="apiParams.name?.description"
v-focus="true" />
</a-form-item>
<a-form-item name="url" ref="url">
<template #label>
<tooltip-label
:title="$t('label.url')"
:tooltip="apiParams.url?.description" />
</template>
<a-input
v-model:value="form.url"
:placeholder="apiParams.url?.description" />
</a-form-item>
<a-form-item name="provider" ref="provider">
<template #label>
<tooltip-label
:title="$t('label.provider')"
:tooltip="apiParams.provider?.description" />
</template>
<a-select
v-model:value="form.provider"
:placeholder="apiParams.provider?.description || 'Select Provider'"
:loading="fetchingProviders"
showSearch>
<a-select-option
v-for="provider in providers"
:key="provider.name || provider"
:value="provider.name || provider">
{{ provider.description || provider.name || provider }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="port" ref="port">
<template #label>
<tooltip-label
:title="$t('label.port')"
:tooltip="apiParams.port?.description" />
</template>
<a-input-number
v-model:value="form.port"
:min="1"
:max="65535"
style="width: 100%" />
</a-form-item>
<a-form-item name="credentials" ref="credentials">
<template #label>
<tooltip-label
:title="$t('label.dns.credentials')"
:tooltip="apiParams.credentials?.description" />
</template>
<a-input-password
v-model:value="form.credentials"
:placeholder="apiParams.credentials?.description || 'Enter API Key'" />
</a-form-item>
<a-form-item name="nameservers" ref="nameservers">
<template #label>
<tooltip-label
:title="$t('label.nameservers')"
:tooltip="apiParams.nameservers?.description" />
</template>
<a-input
v-model:value="form.nameservers"
:placeholder="apiParams.nameservers?.description" />
</a-form-item>
<a-form-item name="ispublic" ref="ispublic">
<a-checkbox v-model:checked="form.ispublic">
{{ "Public server" }}
</a-checkbox>
</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: 'AddDnsServer',
components: {
TooltipLabel
},
data () {
return {
loading: false,
apiParams: {},
form: {
name: '',
url: '',
provider: '',
port: 8081,
nameservers: '',
ispublic: false
},
rules: {},
fetchingProviders: false,
providers: []
}
},
created () {
this.apiParams = this.$getApiParams('addDnsServer') || {}
this.rules = {
name: [{ required: true, message: this.$t('message.error.required.input') }],
url: [{ required: true, message: this.$t('message.error.required.input') }],
provider: [{ required: true, message: this.$t('message.error.required.input') }],
credentials: [{ required: true, message: this.$t('message.error.required.input') }]
}
this.fetchProviders()
},
methods: {
async handleSubmit () {
if (this.loading) return
// 1. Validate the form natively using Vue 3 $refs
try {
await this.$refs.formRef.validate()
} catch (error) {
// Scroll to the first field with an error
if (error.errorFields && error.errorFields.length > 0) {
this.$refs.formRef.scrollToField(error.errorFields[0].name)
}
return // Abort submission
}
this.loading = true
// 2. Execute the API
try {
console.log('form data ', this.form)
// Pass the form object directly
await postAPI('addDnsServer', this.form)
this.$notification.success({
message: this.$t('label.add.dns.server'),
description: this.$t('message.success.add.dns.server')
})
// Refresh the parent table and close modal
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 fetchProviders () {
this.fetchingProviders = true
try {
const response = await getAPI('listDnsProviders')
const listResponse = response?.listdnsprovidersresponse || {}
this.providers = listResponse.dnsprovider || listResponse.provider || []
if (this.providers.length > 0) {
const defaultProvider = this.providers[0]
this.form.provider = defaultProvider.name || defaultProvider
}
} catch (error) {
console.error('Failed to fetch DNS providers', error)
this.$message.warning('Could not load DNS providers.')
} finally {
this.fetchingProviders = 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>