Commit d68765d9 authored by Elias Nahum's avatar Elias Nahum Committed by Juorder Antonio

finish trello tasks, add multi form to create domain, add zone dns tab into domain details.

parent fe727adf
......@@ -101,7 +101,6 @@ http {
}
}
# HTTPS server
#
#server {
......
......@@ -70,9 +70,8 @@ export default class AddAdminModal extends React.Component {
e.preventDefault();
this.props.domain.addAdmin(
user.name,
(error, success) => {
console.log(error, success);
user.id,
(error) => {
if (error) {
return this.setState({
error: {
......
......@@ -7,6 +7,10 @@ import * as Client from '../../utils/client.jsx';
import * as Utils from '../../utils/utils.jsx';
import Promise from 'bluebird';
import DomainStore from '../../stores/domain_store.jsx';
import EventStore from '../../stores/event_store.jsx';
import Constants from '../../utils/constants.jsx';
const MessageType = Constants.MessageType;
export default class AntiSpam extends React.Component {
constructor(props) {
......@@ -15,13 +19,18 @@ export default class AntiSpam extends React.Component {
this.handleDelete = this.handleDelete.bind(this);
this.handleSave = this.handleSave.bind(this);
this.alterDomain = this.alterDomain.bind(this);
this.domain = DomainStore.getCurrent();
console.log(this.domain);
this.blackList = null;
this.whiteList = null;
if (this.domain.attrs.amavisBlacklistSender) {
this.blackList = Array.isArray(this.domain.attrs.amavisBlacklistSender) ? this.domain.attrs.amavisBlacklistSender : this.domain.attrs.amavisBlacklistSender.trim().split(' ');
}
if (this.domain.attrs.amavisWhitelistSender) {
this.whiteList = Array.isArray(this.domain.attrs.amavisWhitelistSender) ? this.domain.attrs.amavisWhitelistSender : this.domain.attrs.amavisWhitelistSender.trim().split(' ');
}
}
handleDelete(e, item, action) {
e.preventDefault();
......@@ -56,20 +65,27 @@ export default class AntiSpam extends React.Component {
const attrs = {};
const isEmail = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const isDomain = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/;
const target = action === 'black' ? 'lista negra' : 'lista blanca';
e.preventDefault();
const input = this.refs[`${action}-item`];
const value = input.value.trim();
if (!value || value === '') {
console.log('no hay valores');
EventStore.emitMessage({
message: `No puede agregar una ${target} vacia.`,
typeError: MessageType.ERROR
});
return false;
}
const isValid = isEmail.test(value) || isDomain.test(value);
if (!isValid) {
console.log('es invalido');
EventStore.emitMessage({
message: `Solo es posible agregar dominios o email en ${target}`,
typeError: MessageType.ERROR
});
return false;
}
......@@ -84,7 +100,7 @@ export default class AntiSpam extends React.Component {
break;
}
this.alterDomain(attrs, e.target, input);
return this.alterDomain(attrs, e.target, input);
}
alterDomain(attrs, button, input) {
......@@ -99,7 +115,6 @@ export default class AntiSpam extends React.Component {
id = `#${button.getAttribute('id')}`;
}
new Promise((resolve, reject) => {
if (hasButton) {
Utils.toggleStatusButtons(id, true);
......@@ -117,7 +132,10 @@ export default class AntiSpam extends React.Component {
update: true
});
}).catch((err) => {
console.log(err);
EventStore.emitMessage({
message: err.message || err.reason,
typeError: MessageType.ERROR
});
}).finally(() => {
if (hasButton) {
Utils.toggleStatusButtons(id, false);
......@@ -134,7 +152,7 @@ export default class AntiSpam extends React.Component {
let whiteList = null;
let blackList = null;
if (this.blackList.length > 0) {
if (this.blackList && this.blackList.length > 0) {
blackList = this.blackList.map((black, i) => {
return (
<tr
......@@ -169,7 +187,7 @@ export default class AntiSpam extends React.Component {
);
}
if (this.whiteList.length > 0) {
if (this.whiteList && this.whiteList.length > 0) {
whiteList = this.whiteList.map((white, i) => {
return (
<tr
......
This diff is collapsed.
This diff is collapsed.
......@@ -8,7 +8,6 @@ import MessageBar from '../message_bar.jsx';
import PageInfo from '../page_info.jsx';
import PanelTab from '../panel_tab.jsx';
import Button from '../button.jsx';
import DomainGeneralInfo from './domain_general_info.jsx';
import DomainMailboxPlans from './domain_mailbox_plans.jsx';
import DomainAdmins from './domain_admin_list.jsx';
......@@ -16,6 +15,8 @@ import DomainDistributionList from './domain_distribution_list.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import MultipleTaskModal from './multiple_task_modal.jsx';
import AntiSpamComponent from './antispam.jsx';
import ZonaDNS from './domain_admin_dns.jsx';
import EventStore from '../../stores/event_store.jsx';
import DomainStore from '../../stores/domain_store.jsx';
......@@ -27,27 +28,63 @@ export default class DomainDetails extends React.Component {
super(props);
this.getDomain = this.getDomain.bind(this);
this.showMessage = this.showMessage.bind(this);
this.state = {};
}
showMessage(attrs) {
this.setState({
error: {
message: attrs.message,
type: attrs.typeError
}
});
}
getDomain() {
const domain = DomainStore.getCurrent();
if (domain && domain.id === this.props.params.id) {
GlobalActions.emitEndLoading();
Client.getZone(domain.name, (zone) => {
DomainStore.setZoneDNS(zone);
this.setState({
domain
});
GlobalActions.emitEndLoading();
}, () => {
DomainStore.setZoneDNS(null);
this.setState({
domain
});
GlobalActions.emitEndLoading();
});
} else {
Client.getDomain(
this.props.params.id,
(data) => {
DomainStore.setCurrent(data);
Client.getZone(data.name, (zone) => {
DomainStore.setZoneDNS(zone);
this.setState({
domain: data
});
GlobalActions.emitEndLoading();
}, () => {
this.setState({
domain: data
});
GlobalActions.emitEndLoading();
});
},
(error) => {
this.setState({
......@@ -60,12 +97,14 @@ export default class DomainDetails extends React.Component {
}
componentDidMount() {
EventStore.addMessageListener(this.showMessage);
$('#sidebar-domains').addClass('active');
this.getDomain();
}
componentWillUnmount() {
$('#sidebar-domains').removeClass('active');
EventStore.removeMessageListener(this.showMessage);
}
render() {
......@@ -125,14 +164,21 @@ export default class DomainDetails extends React.Component {
</div>
);
const zonaDNS = (
<ZonaDNS
domain={domain}
/>
);
const panelTabs = (
<PanelTab
tabNames={['Administradores', 'AntiSpam', 'Listas De Distribución', 'Tareas Masivas']}
tabNames={['Administradores', 'AntiSpam', 'Listas De Distribución', 'Tareas Masivas', 'Zona DNS']}
tabs={{
administradores: tabAdmin,
antispam: tabAntiSpam,
listas_de_distribución: tabDistribution,
tareas_masivas: tabTareasMasivas
tareas_masivas: tabTareasMasivas,
zona_dns: zonaDNS
}}
location={this.props.location}
/>
......
......@@ -56,13 +56,11 @@ export default class DomainGeneralInfo extends React.Component {
company: company.name
});
} else {
Client.getCompany(id).
then((data) => {
Client.getCompany(id, (data) => {
this.setState({
company: data.name
});
}).
catch((error) => {
}, (error) => {
this.setState({
error: {
message: error.message,
......
This diff is collapsed.
This diff is collapsed.
import React from 'react';
import * as Utils from '../../../utils/utils.jsx';
import {browserHistory} from 'react-router';
import DomainStore from '../../../stores/domain_store.jsx';
export default class MailCleanerForm extends React.Component {
constructor(props) {
super(props);
this.addMailCleaner = this.addMailCleaner.bind(this);
this.nextStep = this.nextStep.bind(this);
this.handleChangeOption = this.handleChangeOption.bind(this);
this.state = {};
}
nextStep() {
if (this.props.state.step === this.props.state.total) {
browserHistory.push(`/domains/${this.props.state.domain.id}`);
} else {
DomainStore.emitNextStep({
step: ++this.props.state.step
});
}
}
handleChangeOption() {
const isEnabledMailCleaner = this.refs.enableMailcleaner.checked;
Utils.toggleStatusButtons('.saveMC', !isEnabledMailCleaner);
}
addMailCleaner() {
const isEnabledMailCleaner = this.refs.enableMailcleaner.checked;
return isEnabledMailCleaner;
}
componentDidMount() {
Utils.toggleStatusButtons('.saveMC', true);
}
render() {
const textButton = this.props.state.step === this.props.state.total ? 'Saltar y Finalizar' : 'Saltar este paso';
return (
<div>
<blockquote>
<p>{'¿ Desea agregar su dominio '}<strong>{this.props.state.domain.name}</strong>{' a MailCleaner ?'}</p>
</blockquote>
<form>
<div className='col-xs-12'>
<label
className='radio radio-info radio-inline pretty-input'
>
<div className='pretty-checkbox'>
<input
type='checkbox'
className='pretty'
name='mailbox'
ref='enableMailcleaner'
onChange={this.handleChangeOption}
/>
<span></span>
</div>
{'Desea usar MailCleaner en su dominio: '} <strong>{this.props.state.domain.name}</strong>
</label>
</div>
<br/>
<br/>
<div className='col-xs-12 text-right'>
<button
type='button'
className='btn btn-info saveMC'
onClick={this.addMailCleaner}
>
{'Guardar'}
</button>
<button
type='button'
className='btn btn-info'
onClick={this.nextStep}
>
{textButton}
</button>
</div>
</form>
</div>
);
}
}
MailCleanerForm.propTypes = {
state: React.PropTypes.object
};
......@@ -69,8 +69,7 @@ export default class MultipleTaskModal extends React.Component {
if (this.props.show) {
this.props.onHide();
}
}, (err) => {
console.log('err',err);
}, () => {
if (this.props.show) {
this.props.onHide();
}
......@@ -93,8 +92,7 @@ export default class MultipleTaskModal extends React.Component {
accounts: res
});
}).catch((error) => {
console.log('err', error);
}).finally(() => {
return error;
});
}
......
......@@ -11,7 +11,6 @@ import * as Utils from '../utils/utils.jsx';
import * as Client from '../utils/client.jsx';
import MailboxStore from '../stores/mailbox_store.jsx';
export default class ImportMassiveModal extends React.Component {
constructor(props) {
super(props);
......@@ -35,7 +34,6 @@ export default class ImportMassiveModal extends React.Component {
this.uploaded = null;
this.disabled = {};
this.plans = Utils.getEnabledPlansByCos(ZimbraStore.getAllCos());
this.state = {
......@@ -90,14 +88,17 @@ export default class ImportMassiveModal extends React.Component {
} else {
return this.setState({
alert: true,
alertMessage: 'Su archvio esta vacio, verifiquelo, por favor',
alertMessage: 'Su archvio esta vacio, verifiquelo, por favor'
});
}
return null;
};
fileReader.readAsText(file);
}
return null;
}
onChangeColumn(e, option, key) {
......@@ -149,6 +150,8 @@ export default class ImportMassiveModal extends React.Component {
if (pos === '') {
return true;
}
return null;
});
if (isEmpty) {
......@@ -204,7 +207,7 @@ export default class ImportMassiveModal extends React.Component {
if (!ul[c]) {
if (col.indexOf('@') > -1) {
if (isEmail.test(col)) {
ul[c] = {}
ul[c] = {};
ul[c][flagDefault] = [];
this.disabled[flagDefault] = {
col: c
......@@ -246,8 +249,8 @@ export default class ImportMassiveModal extends React.Component {
counter++;
}
const length = (Array.isArray(this.uploaded[current])) ? this.uploaded[current].length : this.uploaded[current]['email'].length;
const data = (Array.isArray(this.uploaded[current])) ? this.uploaded[current] : this.uploaded[current]['email'];
const length = (Array.isArray(this.uploaded[current])) ? this.uploaded[current].length : this.uploaded[current].email.length;
const data = (Array.isArray(this.uploaded[current])) ? this.uploaded[current] : this.uploaded[current].email;
for (var i = 0; i < length; i++) {
if (!padre[i]) {
......@@ -267,10 +270,11 @@ export default class ImportMassiveModal extends React.Component {
return this.createMassiveAccounts(padre);
}
return this.setState({
this.setState({
alert: true,
alertMessage: 'Faltan columnas que son obligatorias, verifique por favor.'
});
return null;
}
createMassiveAccounts(accounts) {
......@@ -324,9 +328,15 @@ export default class ImportMassiveModal extends React.Component {
//Aqui va error batchrequest
GlobalActions.emitEndTask({
id: 'casillamasiva'
id: 'casillamasiva',
toast: {
message: 'Se han importado todas las casillas.',
title: 'Mailbox - Carga Masiva'
}
});
});
return null;
}
render() {
......@@ -339,7 +349,6 @@ export default class ImportMassiveModal extends React.Component {
);
}
if (this.state.cols) {
const columns = this.state.cols;
ul = [];
......
......@@ -70,6 +70,8 @@ export default class ConfirmDeleteModal extends React.Component {
});
});
}
return null;
}
render() {
......
......@@ -182,6 +182,8 @@ export default class CreateMailBox extends React.Component {
if (currentDomainId === domain.id) {
return domain;
}
return null;
});
}
......@@ -481,5 +483,6 @@ export default class CreateMailBox extends React.Component {
}
CreateMailBox.propTypes = {
location: React.PropTypes.object
location: React.PropTypes.object,
params: React.PropTypes.object
};
......@@ -92,7 +92,7 @@ export default class EditMailBox extends React.Component {
return false;
}
this.handleRenameAccount(mail);
return this.handleRenameAccount(mail);
}
handleRenameAccount(email) {
......@@ -139,7 +139,7 @@ export default class EditMailBox extends React.Component {
});
}
GlobalActions.emitMessage({
return GlobalActions.emitMessage({
message: 'Error, no existe instancia de la casilla.',
typeError: messageType.ERROR
});
......
......@@ -132,6 +132,8 @@ export default class Mailboxes extends React.Component {
componentWillReceiveProps(newProps) {
const condition = this.props.location.query.page !== newProps.location.query.page;
let domainId = null;
if (condition) {
const page = parseInt(newProps.location.query.page, 10) || 1;
......@@ -142,13 +144,16 @@ export default class Mailboxes extends React.Component {
offset: ((page - 1) * QueryOptions.DEFAULT_LIMIT)
};
this.getAllMailboxes();
domainId = this.props.params.domain_id;
this.getAllMailboxes(domainId);
} else {
GlobalActions.emitStartLoading();
let domainId;
if (newProps.params.domain_id !== this.props.params.domain_id) {
domainId = newProps.params.domain_id;
}
this.getAllMailboxes(domainId);
}
}
......@@ -221,8 +226,8 @@ export default class Mailboxes extends React.Component {
notMatches: true,
domain: domainName
});
}).catch(() => {
console.log('error',error);
}).catch((error) => {
return error;
}).finally(() => {
GlobalActions.emitEndLoading();
});
......@@ -493,7 +498,7 @@ export default class Mailboxes extends React.Component {
className='form-control plans'
onChange={this.handleChangeFilter}
>
<option value=''>Todoas los planes</option>
<option value=''>Todos los planes</option>
<option value='basic'>Básico</option>
<option value='professional'>Profesional</option>
<option value='premium'>Premium</option>
......@@ -572,7 +577,6 @@ export default class Mailboxes extends React.Component {
onClick={this.handleTabChanged}
/>
);
}
return (
......
......@@ -97,7 +97,7 @@ export default class MailboxDetails extends React.Component {
});
}
this.setState({
return this.setState({
data: account,
alias: items,
webmail: false
......@@ -132,7 +132,7 @@ export default class MailboxDetails extends React.Component {
});
}
this.setState({
return this.setState({
data: result,
alias: items,
webmail: false
......
......@@ -39,12 +39,12 @@ export default class Pagination extends React.Component {
pageUrl = this.getPageQueryString(page);
}
browserHistory.push(`/${this.props.url}${pageUrl}`);
browserHistory.push(`${this.props.url}${pageUrl}`);
}
handleNext(e) {
e.preventDefault();
const page = this.getPageQueryString(this.props.currentPage + 1);
browserHistory.push(`/${this.props.url}${page}`);
browserHistory.push(`${this.props.url}${page}`);
}
handleLast(e) {
e.preventDefault();
......
......@@ -2,7 +2,6 @@
// See LICENSE.txt for license information.
import React from 'react';
import EventStore from '../stores/event_store.jsx';
import * as GlobalActions from '../action_creators/global_actions.jsx';
export default class ProgressTask extends React.Component {
constructor(props) {
......
{
"debug": false,
"debug": true,
"zimbraUrl": "http://zimbra.zboxapp.dev:8000/service/admin/soap",
"zimbraProxy": "https://zimbra.zboxapp.dev:7071",
"dnsApiUrl": "http://zimbra.zboxapp.dev:3000",
"webMailUrl": "https://zimbra.zboxapp.dev",
"dns": {
"url": "http://192.168.1.8:8081",
"token": "otto"
},
"plans": {
"basic": true,
"premium": true,
"professional": true,
"default": false
},
"multiFormDomain": {
"hasMailCleaner": false,
"hasDNSZone": true
},
"webmailLifetime": 3600,
"companiesEndPoints": {
"list": "http://zimbra.zboxapp.dev:8001/list",
......
......@@ -25,9 +25,9 @@
}
.taskboard {
height: 100%;
max-height: 0;
overflow: hidden;
height: 100%;
transition: max-height 1s;
.alert {
......@@ -48,3 +48,9 @@
width: 100%;
}
}
.flash {
+ .progress {
margin-top: 10px;
}
}
......@@ -102,3 +102,32 @@
}
}
}
.progress-text {
color: $white;
font-weight: bold;
text-align: center;
text-shadow: 0 1px 2px $black;
}
.progress-bar.step {
background-color: $bg-progresss-bar;
border-right: 0;
text-align: center;
&.first-step {
width: 33.33%;
}
&.second-step {
width: 66.66%;
}
&.third-step {
width: 100%;
}
}
.set-margin-up {
margin-top: 10px;
}
......@@ -93,3 +93,7 @@ $color-new-fields: #e4e5e7;
$bg-import-ok: rgba(78, 165, 224, .2);
$border-color-ok: #4ea5e0;
$bg-import-error: #ffe8e8;
// color ProgressBar
$bg-progresss-bar: #5bc0de;
......@@ -12,6 +12,7 @@ class DomainStoreClass extends EventEmitter {
this.current = null;
this.distributionListOwners = null;
this.distributionListMembers = null;
this.zoneDNS = null;
}
getCurrent() {
......@@ -241,6 +242,19 @@ class DomainStoreClass extends EventEmitter {
return false;
}
setZoneDNS(zone) {
this.zoneDNS = zone;
return this.emitZoneDNSChange(zone);
}
getZoneDNS() {
if (this.zoneDNS) {
return this.zoneDNS;
}
return null;
}
removeDistributionList(listId) {
if (this.current.lists) {
Reflect.deleteProperty(this.current.lists, listId);
......@@ -248,6 +262,18 @@ class DomainStoreClass extends EventEmitter {
this.emitDistributionListsChange();
}
emitZoneDNSChange(zone) {
this.emit(eventTypes.ZONE_DNS_CHANGE_EVENT, zone);
}
addZoneDNSChangeListener(zone) {
this.on(eventTypes.ZONE_DNS_CHANGE_EVENT, zone);
}
removeZoneDNSChangeListener(zone) {
this.removeListener(eventTypes.ZONE_DNS_CHANGE_EVENT, zone);
}
emitDistributionListsChange() {
this.emit(eventTypes.DOMAIN_DLS_CHANGE_EVENT);
}
......@@ -259,6 +285,18 @@ class DomainStoreClass extends EventEmitter {
removeDistributionListsChangeListener(callback) {
this.removeListener(eventTypes.DOMAIN_DLS_CHANGE_EVENT, callback);
}
emitNextStep(attrs) {
this.emit(eventTypes.NEXT_STEP_EVENT, attrs);
}
addNextStepListener(callback) {
this.on(eventTypes.NEXT_STEP_EVENT, callback);
}
removeNextStepListener(callback) {
this.removeListener(eventTypes.NEXT_STEP_EVENT, callback);
}
}
const DomainStore = new DomainStoreClass();
......
......@@ -5,6 +5,7 @@ import $ from 'jquery';
import Promise from 'bluebird';
import ZimbraAdminApi from 'zimbra-admin-api-js';
import Powerdns from 'js-powerdns';
import ZimbraStore from '../stores/zimbra_store.jsx';
import * as GlobalActions from '../action_creators/global_actions.jsx';
......@@ -160,21 +161,19 @@ export function getAllCompanies() {
});
}
export function getCompany(id) {
export function getCompany(id, success, error) {
const url = global.window.manager_config.companiesEndPoints.detail.replace('{id}', id);
return new Promise((resolve, reject) => {
return $.ajax({
url,
dataType: 'json',
success: function onSuccess(data) {
resolve(data);
success(data);
},
error: function onError(xhr, status, err) {
reject(err);
error: function onError(xhr) {
error(xhr.responseJSON);
}
});
});
}
export function getInvoices(id, success, error) {
......@@ -665,3 +664,93 @@ export function renameAccount(account, success, error) {
}
);
}
export function initPowerDNS() {
return new Promise((resolve, reject) => {
const powerAttrs = window.manager_config.dns;
const api = new Powerdns({url: powerAttrs.url, token: powerAttrs.token});
if (api) {
return resolve(api);
}
return reject({
type: Constants.MessageType.ERROR,
message: 'PowerDNS no instanciado'
});
});
}
export function createZoneWithRecords(zoneData, records, success, error) {
initPowerDNS().then(
(api) => {
api.createZoneWithRecords(zoneData, records, (er, data) => {
if (er) {
return error(er);
}
return success(data);
});
},
(err) => {
const e = handleError('createZoneWithRecords', err);
return error(e);
}
);
}
export function getZone(domain, success, error) {
initPowerDNS().then(
(api) => {
api.getZone(domain, (er, data) => {
if (er) {
return error(er);
}
return success(data);
});
},
(err) => {
const e = handleError('getZone', err);
return error(e);
}
);
}
export function modifyOrCreateRecords(record, success, error) {
initPowerDNS().then(
(api) => {
api.modifyOrCreateRecords(record, (err, data) => {
if (err) {
const e = handleError('modifyOrCreateRecords', err);
return error(e);
}
return success(data);
});
},
(err) => {
const e = handleError('modifyOrCreateRecords', err);
return error(e);
}
);
}
export function deleteRecords(zoneUrl, record, success, error) {
initPowerDNS().then(
(api) => {
api.deleteRecords(zoneUrl, record, (err, data) => {
if (err) {
const e = handleError('deleteRecords', err);
return error(e);
}
return success(data);
});
},
(err) => {
const e = handleError('deleteRecords', err);
return error(e);
}
);
}
......@@ -31,7 +31,9 @@ export default {
USER_CHANGE_EVENT: null,
NEW_MESSAGE_EVENT: null,
NEW_TOAST_EVENT: null,
MAILBOX_ADD_MASSIVE_EVENT: null
MAILBOX_ADD_MASSIVE_EVENT: null,
NEXT_STEP_EVENT: null,
ZONE_DNS_CHANGE_EVENT: null
}),
MessageType: keyMirror({
......@@ -54,5 +56,50 @@ export default {
MONTHS: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Juio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
QueryOptions: {
DEFAULT_LIMIT: 10
}
},
Labels: keyMirror({
name: null,
type: null,
content: null,
priority: null,
ttl: null
}),
typesOfDNS: [
'AAAA',
'AFSDB',
'CERT',
'CNAME',
'A',
'DLV',
'DNSKEY',
'DS',
'EUI48',
'EUI64',
'HINFO',
'IPSECKEY',
'KEY',
'KX',
'LOC',
'MINFO',
'MR',
'MX',
'NAPTR',
'NS',
'NSEC',
'NSEC3',
'NSEC3PARAM',
'OPT',
'PTR',
'RKEY',
'RP',
'RRSIG',
'SOA',
'SPF',
'SRV',
'SSHFP',
'TLSA',
'TSIG',
'TXT',
'WKS'
]
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment