From a1eca74de1d3fed93d41e100f89a79bd2fb5666b Mon Sep 17 00:00:00 2001 From: Manoj Kumar Date: Wed, 18 Mar 2026 18:45:03 +0530 Subject: [PATCH] work on updateDnsServer ui screen, fixed acl issue for dns server and zone and cleanup methods for dns server and zone --- .../java/com/cloud/user/AccountService.java | 3 - .../command/user/dns/DeleteDnsServerCmd.java | 2 +- .../api/response/DnsServerResponse.java | 8 + .../cloudstack/dns/DnsProviderManager.java | 5 + .../java/com/cloud/acl/DomainChecker.java | 15 +- .../com/cloud/event/ActionEventUtils.java | 4 + .../com/cloud/user/AccountManagerImpl.java | 18 +-- .../dns/DnsProviderManagerImpl.java | 95 +++++++++--- .../dns/DnsVmLifecycleListener.java | 1 + .../apache/cloudstack/dns/dao/DnsZoneDao.java | 2 + .../cloudstack/dns/dao/DnsZoneDaoImpl.java | 12 ++ .../dns/dao/DnsZoneNetworkMapDao.java | 2 +- .../dns/dao/DnsZoneNetworkMapDaoImpl.java | 7 +- ui/public/locales/en.json | 1 + ui/src/components/view/ListView.vue | 3 + ui/src/config/section/network.js | 4 +- ui/src/views/network/dns/AddDnsServer.vue | 14 +- ui/src/views/network/dns/CreateDnsRecord.vue | 11 +- ui/src/views/network/dns/UpdateDnsServer.vue | 139 +++++++++++++++++- 19 files changed, 282 insertions(+), 64 deletions(-) diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index e78afd9618f..30919e5b782 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -46,7 +46,6 @@ import org.apache.cloudstack.api.response.ApiKeyPairResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.auth.UserTwoFactorAuthenticator; import org.apache.cloudstack.backup.BackupOffering; -import org.apache.cloudstack.dns.DnsServer; public interface AccountService { @@ -129,8 +128,6 @@ 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; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java index f5674c42427..5444e769da5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java @@ -54,7 +54,7 @@ public class DeleteDnsServerCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.CLEANUP, type = CommandType.BOOLEAN, entityType = DnsZoneResponse.class, description = "True if all associated DNS zones have to be cleaned up with this server") - private Boolean cleanup; + private Boolean cleanup = true; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DnsServerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DnsServerResponse.java index d67be31504d..fecf49f0f98 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DnsServerResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DnsServerResponse.java @@ -74,6 +74,10 @@ public class DnsServerResponse extends BaseResponse { @Param(description = "the name of the domain associated with the DNS server") private String domainName; + @SerializedName(ApiConstants.STATE) + @Param(description = "The state of the account") + private String state; + public DnsServerResponse() { super(); @@ -122,4 +126,8 @@ public class DnsServerResponse extends BaseResponse { public void setDomainName(String domainName) { this.domainName = domainName; } + + public void setState(String state) { + this.state = state; + } } diff --git a/api/src/main/java/org/apache/cloudstack/dns/DnsProviderManager.java b/api/src/main/java/org/apache/cloudstack/dns/DnsProviderManager.java index b4dede12ce1..e6ce3a49022 100644 --- a/api/src/main/java/org/apache/cloudstack/dns/DnsProviderManager.java +++ b/api/src/main/java/org/apache/cloudstack/dns/DnsProviderManager.java @@ -38,6 +38,7 @@ 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,4 +76,8 @@ public interface DnsProviderManager extends Manager, PluggableService { boolean disassociateZoneFromNetwork(DisassociateDnsZoneFromNetworkCmd cmd); String processDnsRecordForInstance(VirtualMachine instance, Network network, Nic nic, boolean isAdd); + + void checkDnsServerPermission(Account caller, DnsServer dnsServer); + + void checkDnsZonePermission(Account caller, DnsZone dnsZone); } diff --git a/server/src/main/java/com/cloud/acl/DomainChecker.java b/server/src/main/java/com/cloud/acl/DomainChecker.java index 9c8314dc252..116df262352 100644 --- a/server/src/main/java/com/cloud/acl/DomainChecker.java +++ b/server/src/main/java/com/cloud/acl/DomainChecker.java @@ -27,13 +27,16 @@ import org.apache.cloudstack.acl.ProjectRoleService; import org.apache.cloudstack.acl.RolePermissionEntity; import org.apache.cloudstack.acl.SecurityChecker; 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.dns.DnsZone; import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.springframework.stereotype.Component; +import org.apache.cloudstack.backup.dao.BackupOfferingDetailsDao; +import org.apache.cloudstack.backup.BackupOffering; import com.cloud.dc.DataCenter; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.dao.DedicatedResourceDao; @@ -101,6 +104,8 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { private ProjectDao projectDao; @Inject private AccountService accountService; + @Inject + private DnsProviderManager dnsProviderManager; protected DomainChecker() { super(); @@ -216,7 +221,11 @@ public class DomainChecker extends AdapterBase implements SecurityChecker { _networkMgr.checkRouterPermissions(caller, (VirtualRouter)entity); } else if (entity instanceof AffinityGroup) { return false; - } else { + } else if (entity instanceof DnsServer) { + dnsProviderManager.checkDnsServerPermission(caller, (DnsServer) entity); + } else if (entity instanceof DnsZone) { + dnsProviderManager.checkDnsZonePermission(caller, (DnsZone) entity); + } else { validateCallerHasAccessToEntityOwner(caller, entity, accessType); } return true; diff --git a/server/src/main/java/com/cloud/event/ActionEventUtils.java b/server/src/main/java/com/cloud/event/ActionEventUtils.java index ae77446a856..de1ffd72ad5 100644 --- a/server/src/main/java/com/cloud/event/ActionEventUtils.java +++ b/server/src/main/java/com/cloud/event/ActionEventUtils.java @@ -34,6 +34,7 @@ import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.events.EventDistributor; +import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -85,6 +86,8 @@ public class ActionEventUtils { EntityManager entityMgr; @Inject ConfigurationDao configDao; + @Inject + MessageBus messageBus; public ActionEventUtils() { } @@ -97,6 +100,7 @@ public class ActionEventUtils { s_projectDao = projectDao; s_entityMgr = entityMgr; s_configDao = configDao; + messageBus = messageBus; } public static Long onActionEvent(Long userId, Long accountId, Long domainId, String type, String description, Long resourceId, String resourceType) { diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 7df7c6ffb5d..cc2650ff276 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -95,6 +95,7 @@ import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.config.ApiServiceConfiguration; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.dns.DnsServer; +import org.apache.cloudstack.dns.DnsZone; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -774,7 +775,8 @@ 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 AffinityGroup) && !(entity instanceof VirtualRouter) + && !(entity instanceof DnsServer) && !(entity instanceof DnsZone)) { List toBeChecked = domains.get(entity.getDomainId()); // for templates, we don't have to do cross domains check if (toBeChecked == null) { @@ -3972,20 +3974,6 @@ 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.getPublicServer()) { - 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) { diff --git a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java index 626f71ba2e7..8022d2fe1da 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/dns/DnsProviderManagerImpl.java @@ -71,11 +71,14 @@ import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.dao.AccountDao; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.PluggableService; import com.cloud.utils.db.Filter; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.Nic; import com.cloud.vm.VirtualMachine; @@ -105,6 +108,8 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa DnsZoneJoinDao dnsZoneJoinDao; @Inject DnsServerJoinDao dnsServerJoinDao; + @Inject + AccountDao accountDao; private DnsProvider getProviderByType(DnsProviderType type) { if (type == null) { @@ -194,7 +199,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa } Account caller = CallContext.current().getCallingAccount(); - accountMgr.checkAccess(caller, dnsServer); + accountMgr.checkAccess(caller, null, true, dnsServer); boolean validationRequired = false; String originalUrl = dnsServer.getUrl(); @@ -265,35 +270,55 @@ 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, dnsServer); - if (cmd.getCleanup()) { - // ToDo cleanup associated dnsZones - } - return dnsServerDao.remove(dnsServerId); + accountMgr.checkAccess(caller, null, true, dnsServer); + return Transaction.execute((TransactionCallback) status -> { + if (cmd.getCleanup()) { + List dnsZones = dnsZoneDao.findDnsZonesByServerId(dnsServerId); + for (DnsZoneVO dnsZone : dnsZones) { + long dnsZoneId = dnsZone.getId(); + dnsZoneNetworkMapDao.removeNetworkMappingByZoneId(dnsZoneId); + // ToDo: delete nic_record_urls from vm_details if present before removing dnsZone + dnsZoneDao.remove(dnsZoneId); + } + } + 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) { + DnsZoneVO dnsZone = dnsZoneDao.findById(zoneId); + if (dnsZone == null) { throw new InvalidParameterValueException("DNS zone not found for the given ID."); } - + String dnsZoneName = dnsZone.getName(); Account caller = CallContext.current().getCallingAccount(); - accountMgr.checkAccess(caller, null, true, zone); - DnsServerVO server = dnsServerDao.findById(zone.getDnsServerId()); - if (server != null && zone.getState() == DnsZone.State.Active) { - try { - DnsProvider provider = getProviderByType(server.getProviderType()); - provider.deleteZone(server, zone); - logger.debug("Deleted DNS zone: {}", zone.getName()); - } catch (Exception ex) { - logger.error("Failed to delete DNS zone from provider", ex); - throw new CloudRuntimeException("Failed to delete DNS zone."); - } + accountMgr.checkAccess(caller, null, true, dnsZone); + DnsServerVO server = dnsServerDao.findById(dnsZone.getDnsServerId()); + if (server == null) { + throw new CloudRuntimeException(String.format("The DNS server not found for DNS zone: %s", dnsZoneName)); } - return dnsZoneDao.remove(zoneId); + try { + DnsProvider provider = getProviderByType(server.getProviderType()); + provider.deleteZone(server, dnsZone); + logger.debug("Deleted DNS zone: {} from provider", dnsZoneName); + } catch (DnsNotFoundException ex) { + logger.warn("DNS zone: {} is not present in the provider, proceeding with cleanup", dnsZoneName); + } catch (Exception ex) { + logger.error("Failed to delete DNS zone from provider", ex); + throw new CloudRuntimeException(String.format("Failed to delete DNS zone: %s.", dnsZoneName)); + } + + boolean dbResult = Transaction.execute((TransactionCallback) status -> { + dnsZoneNetworkMapDao.removeNetworkMappingByZoneId(zoneId); + // ToDo: delete nic_record_urls from vm_details if present before removing dnsZone + return dnsZoneDao.remove(zoneId); + }); + if (!dbResult) { + logger.error("Failed to remove DNS zone {} from DB after provider deletion", dnsZoneName); + } + return dbResult; } @Override @@ -355,7 +380,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, dnsServer); + accountMgr.checkAccess(caller, null, true, dnsServer); } List ownDnsServerIds = dnsServerDao.listDnsServerIdsByAccountId(caller.getAccountId()); String keyword = cmd.getKeyword(); @@ -538,6 +563,7 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa response.setAccountName(server.getAccountName()); response.setDomainId(server.getDomainUuid()); // Note: APIs always return UUIDs, not internal DB IDs! response.setDomainName(server.getDomainName()); + response.setState(server.getState().name()); response.setObjectName("dnsserver"); return response; } @@ -677,6 +703,31 @@ public class DnsProviderManagerImpl extends ManagerBase implements DnsProviderMa return null; } + @Override + public void checkDnsServerPermission(Account caller, DnsServer dnsServer) throws PermissionDeniedException { + if (caller.getId() == dnsServer.getAccountId()) { + return; + } + if (!dnsServer.getPublicServer()) { + 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 checkDnsZonePermission(Account caller, DnsZone zone) { + if (caller.getId() != zone.getAccountId()) { + throw new PermissionDeniedException(caller + "is not allowed to access the DNS Zone " + zone.getName()); + } + } + + public Account getAccount(long accountId) { + return accountDao.findByIdIncludingRemoved(accountId); + } + @Override public boolean start() { if (dnsProviders == null || dnsProviders.isEmpty()) { diff --git a/server/src/main/java/org/apache/cloudstack/dns/DnsVmLifecycleListener.java b/server/src/main/java/org/apache/cloudstack/dns/DnsVmLifecycleListener.java index 53f6fb62105..12044f497cf 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/DnsVmLifecycleListener.java +++ b/server/src/main/java/org/apache/cloudstack/dns/DnsVmLifecycleListener.java @@ -77,6 +77,7 @@ public class DnsVmLifecycleListener extends ManagerBase implements EventSubscrib eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_VM_DESTROY, null, null, null), this); eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_NIC_CREATE, null, null, null), this); eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_NIC_DELETE, null, null, null), this); + eventBus.subscribe(new EventTopic(null, EventTypes.EVENT_DNS_RECORD_DELETE, null, null, null), this); } catch (EventBusException ex) { logger.error("Failed to subscribe DnsVmLifecycleListener to EventBus", ex); } diff --git a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDao.java b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDao.java index 05fb799a663..bdb2d5de19c 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDao.java +++ b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDao.java @@ -32,4 +32,6 @@ public interface DnsZoneDao extends GenericDao { Pair, Integer> searchZones(Long id, Long accountId, List ownDnsServerIds, Long targetDnsServerId, String keyword, Filter filter); + + List findDnsZonesByServerId(long dnsServerId); } diff --git a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDaoImpl.java b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDaoImpl.java index 1aad6fa0f0b..8245fc01f30 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDaoImpl.java +++ b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneDaoImpl.java @@ -33,12 +33,18 @@ import com.cloud.utils.db.SearchCriteria; @Component public class DnsZoneDaoImpl extends GenericDaoBase implements DnsZoneDao { + SearchBuilder DnsServerSearch; SearchBuilder AccountSearch; SearchBuilder NameServerTypeSearch; public DnsZoneDaoImpl() { super(); + DnsServerSearch = createSearchBuilder(); + DnsServerSearch.selectFields(DnsServerSearch.entity().getDnsServerId()); + DnsServerSearch.and(ApiConstants.DNS_SERVER_ID, DnsServerSearch.entity().getDnsServerId(), SearchCriteria.Op.EQ); + DnsServerSearch.done(); + AccountSearch = createSearchBuilder(); AccountSearch.and(ApiConstants.ACCOUNT_ID, AccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ); AccountSearch.and(ApiConstants.STATE, AccountSearch.entity().getState(), SearchCriteria.Op.EQ); @@ -107,4 +113,10 @@ public class DnsZoneDaoImpl extends GenericDaoBase implements D sc.setParameters(ApiConstants.STATE, DnsZone.State.Active); return searchAndCount(sc, filter); } + + public List findDnsZonesByServerId(long dnsServerId) { + SearchCriteria sc = DnsServerSearch.create(); + sc.setParameters(ApiConstants.DNS_SERVER_ID, dnsServerId); + return listBy(sc); + } } diff --git a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDao.java b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDao.java index 29e9190d542..1936ba3fe66 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDao.java +++ b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDao.java @@ -22,6 +22,6 @@ import org.apache.cloudstack.dns.vo.DnsZoneNetworkMapVO; import com.cloud.utils.db.GenericDao; public interface DnsZoneNetworkMapDao extends GenericDao { - DnsZoneNetworkMapVO findByZoneAndNetwork(long dnsZoneId, long networkId); + void removeNetworkMappingByZoneId(long dnsZoneId); DnsZoneNetworkMapVO findByNetworkId(long networkId); } diff --git a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDaoImpl.java b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDaoImpl.java index 01a8718a895..ce61e26f799 100644 --- a/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDaoImpl.java +++ b/server/src/main/java/org/apache/cloudstack/dns/dao/DnsZoneNetworkMapDaoImpl.java @@ -34,7 +34,6 @@ public class DnsZoneNetworkMapDaoImpl extends GenericDaoBase sc = ZoneNetworkSearch.create(); sc.setParameters(ApiConstants.DNS_ZONE_ID, dnsZoneId); - sc.setParameters(ApiConstants.NETWORK_ID, networkId); - - return findOneBy(sc); + remove(sc); } @Override diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 4bbcb4b3acf..c22b585fb04 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -726,6 +726,7 @@ "label.creating.dns.zone": "Creating DNS zone", "label.creating.iprange": "Creating IP ranges", "label.nameservers": "DNS Nameservers", +"label.publicdomainsuffix": "Public domain suffix", "label.credit": "Credit", "label.cron": "Cron expression", "label.cron.mode": "Cron mode", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 8dcc09391b9..3763bc91f32 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -207,6 +207,9 @@ +