Adjustment in domain template feature to work with python3

This commit is contained in:
Khanh Ngo 2018-03-31 08:21:02 +07:00
commit 29d1cf4117
11 changed files with 1034 additions and 33 deletions

View File

@ -18,6 +18,8 @@ from flask_login import AnonymousUserMixin
from app import app, db
from app.lib import utils
from app.lib.log import logger
# LOG CONFIGS
logging = logger('MODEL', app.config['LOG_LEVEL'], app.config['LOG_FILE']).config()
if 'LDAP_TYPE' in app.config.keys():
@ -894,7 +896,7 @@ class Record(object):
list_deleted_records = [x for x in list_current_records if x not in list_new_records]
# convert back to list of hash
deleted_records = [x for x in current_records if [x['name'],x['type']] in list_deleted_records and x['type'] in app.config['RECORDS_ALLOW_EDIT']]
deleted_records = [x for x in current_records if [x['name'],x['type']] in list_deleted_records and (x['type'] in app.config['RECORDS_ALLOW_EDIT'] and x['type'] != 'SOA')]
# return a tuple
return deleted_records, new_records
@ -1042,12 +1044,14 @@ class Record(object):
})
postdata_for_new = {"rrsets": final_records}
logging.info(postdata_for_new)
logging.info(postdata_for_delete)
logging.info(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)))
try:
headers = {}
headers['X-API-Key'] = PDNS_API_KEY
jdata1 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=postdata_for_delete)
jdata2 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/%s' % domain), headers=headers, method='PATCH', data=postdata_for_new)
jdata1 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=postdata_for_delete)
jdata2 = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=postdata_for_new)
if 'error' in jdata2.keys():
logging.error('Cannot apply record changes.')
@ -1058,7 +1062,7 @@ class Record(object):
logging.info('Record was applied successfully.')
return {'status': 'ok', 'msg': 'Record was applied successfully'}
except Exception as e:
logging.error("Cannot apply record changes to domain %s. DETAIL: %s" % (e, domain))
logging.error("Cannot apply record changes to domain {0}. DETAIL: {1}".format(e, domain))
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
def auto_ptr(self, domain, new_records, deleted_records):
@ -1127,12 +1131,18 @@ class Record(object):
logging.error("Cannot remove record %s/%s/%s from domain %s" % (self.name, self.type, self.data, domain))
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
def is_allowed(self):
def is_allowed_edit(self):
"""
Check if record is allowed to edit/removed
Check if record is allowed to edit
"""
return self.type in app.config['RECORDS_ALLOW_EDIT']
def is_allowed_delete(self):
"""
Check if record is allowed to removed
"""
return (self.type in app.config['RECORDS_ALLOW_EDIT'] and self.type != 'SOA')
def exists(self, domain):
"""
Check if record is present within domain records, and if it's present set self to found record
@ -1361,3 +1371,83 @@ class Setting(db.Model):
logging.debug(traceback.format_exec())
db.session.rollback()
return False
class DomainTemplate(db.Model):
__tablename__ = "domain_template"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), index=True, unique=True)
description = db.Column(db.String(255))
records = db.relationship('DomainTemplateRecord', back_populates='template', cascade="all, delete-orphan")
def __repr__(self):
return '<DomainTemplate %s>' % self.name
def __init__(self, name=None, description=None):
self.id = None
self.name = name
self.description = description
def replace_records(self, records):
try:
self.records = []
for record in records:
self.records.append(record)
db.session.commit()
return {'status': 'ok', 'msg': 'Template records have been modified'}
except Exception as e:
logging.error('Cannot create template records Error: {0}'.format(e))
db.session.rollback()
return {'status': 'error', 'msg': 'Can not create template records'}
def create(self):
try:
db.session.add(self)
db.session.commit()
return {'status': 'ok', 'msg': 'Template has been created'}
except Exception as e:
logging.error('Can not update domain template table. Error: {0}'.format(e))
db.session.rollback()
return {'status': 'error', 'msg': 'Can not update domain template table'}
def delete_template(self):
try:
self.records = []
db.session.delete(self)
db.session.commit()
return {'status': 'ok', 'msg': 'Template has been deleted'}
except Exception as e:
logging.error('Can not delete domain template. Error: {0}'.format(e))
db.session.rollback()
return {'status': 'error', 'msg': 'Can not delete domain template'}
class DomainTemplateRecord(db.Model):
__tablename__ = "domain_template_record"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
type = db.Column(db.String(64))
ttl = db.Column(db.Integer)
data = db.Column(db.String(255))
status = db.Column(db.Boolean)
template_id = db.Column(db.Integer, db.ForeignKey('domain_template.id'))
template = db.relationship('DomainTemplate', back_populates='records')
def __repr__(self):
return '<DomainTemplateRecord %i>' % self.id
def __init__(self, id=None, name=None, type=None, ttl=None, data=None, status=None):
self.id = id
self.name = name
self.type = type
self.ttl = ttl
self.data = data
self.status = status
def apply(self):
try:
db.session.commit()
except Exception as e:
logging.error('Can not update domain template table. Error: {0}'.format(e))
db.session.rollback()
return {'status': 'error', 'msg': 'Can not update domain template table'}

View File

@ -127,6 +127,7 @@
<li><a href="{{ url_for('domain_add') }}"><i class="fa fa-plus"></i> <span>New Domain</span></a></li>
<li class="header">ADMINISTRATION</li>
<li><a href="{{ url_for('admin') }}"><i class="fa fa-wrench"></i> <span>Admin Console</span></a></li>
<li><a href="{{ url_for('templates') }}"><i class="fa fa-clone"></i> <span>Domain Templates</span></a></li>
<li><a href="{{ url_for('admin_manageuser') }}"><i class="fa fa-users"></i> <span>Users</span></a></li>
<li><a href="{{ url_for('admin_history') }}"><i class="fa fa-calendar"></i> <span>History</span></a></li>
<li><a href="{{ url_for('admin_settings') }}"><i class="fa fa-cog"></i> <span>Settings</span></a></li>

View File

@ -203,6 +203,31 @@
var domain = $(this).prop('id');
getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec');
});
$(document.body).on("click", ".button_template", function (e) {
var modal = $("#modal_template");
var domain = $(this).prop('id');
var form = " <label for=\"template_name\">Template name</label> \
<input type=\"text\" class=\"form-control\" name=\"template_name\" id=\"template_name\" placeholder=\"Enter a valid template name (required)\"> \
<label for=\"template_description\">Template description</label> \
<input type=\"text\" class=\"form-control\" name=\"template_description\" id=\"template_description\" placeholder=\"Enter a template description (optional)\"> \
<input id=\"domain\" name=\"domain\" type=\"hidden\" value=\""+domain+"\"> \
";
modal.find('.modal-body p').html(form);
modal.find('#button_save').click(function() {
var data = {};
data['name'] = modal.find('#template_name').val();
data['description'] = modal.find('#template_description').val();
data['domain'] = modal.find('#domain').val();
applyChanges(data, $SCRIPT_ROOT + "{{ url_for('create_template_from_zone') }}", true);
modal.modal('hide');
})
modal.find('#button_close').click(function() {
modal.modal('hide');
})
modal.modal('show');
});
</script>
{% endblock %}
{% block modals %}
@ -252,4 +277,27 @@
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div class="modal fade modal-primary" id="modal_template">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Clone to template</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-default pull-left"
id="button_close" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-flat btn-primary" id="button_save">Save</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endblock %}

View File

@ -27,14 +27,11 @@
{% endmacro %}
{% macro actions(domain) %}
{% if current_user.role.name !='Administrator' %}
<td width="6%">
<button type="button" class="btn btn-flat btn-success" onclick="window.location.href='{{ url_for('domain', domain_name=domain.name) }}'">
Manage&nbsp;<i class="fa fa-cog"></i>
{% if current_user.role.name =='Administrator' %}
<td width="25%">
<button type="button" class="btn btn-flat btn-success button_template" id="{{ domain.name }}">
Template&nbsp;<i class="fa fa-clone"></i>
</button>
</td>
{% else %}
<td width="20%">
<button type="button" class="btn btn-flat btn-success" onclick="window.location.href='{{ url_for('domain', domain_name=domain.name) }}'">
Manage&nbsp;<i class="fa fa-cog"></i>
</button>
@ -42,5 +39,11 @@
Admin&nbsp;<i class="fa fa-cog"></i>
</button>
</td>
{% else %}
<td width="6%">
<button type="button" class="btn btn-flat btn-success" onclick="window.location.href='{{ url_for('domain', domain_name=domain.name) }}'">
Manage&nbsp;<i class="fa fa-cog"></i>
</button>
</td>
{% endif %}
{% endmacro %}

View File

@ -70,24 +70,22 @@
</td>
{% if domain.type != 'Slave' %}
<td width="6%">
{% if record.is_allowed() %}
{% if record.is_allowed_edit() %}
<button type="button" class="btn btn-flat btn-warning button_edit" id="{{ (record.name,domain.name)|display_record_name }}">Edit&nbsp;<i class="fa fa-edit"></i></button>
{% else %}
<button type="button" class="btn btn-flat btn-warning"">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
{% endif %}
</td>
<td width="6%">
{% if record.is_allowed() %}
{% if record.is_allowed_delete() %}
<button type="button" class="btn btn-flat btn-danger button_delete" id="{{ (record.name,domain.name)|display_record_name }}">Delete&nbsp;<i class="fa fa-trash"></i></button>
{% else %}
<button type="button" class="btn btn-flat btn-warning"">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
{% endif %}
{% else %}
<td width="6%">
<button type="button" class="btn btn-flat btn-warning"">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
<button type="button" class="btn btn-flat btn-warning">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
</td>
<td width="6%">
<button type="button" class="btn btn-flat btn-warning"">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
<button type="button" class="btn btn-flat btn-warning">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
</td>
{% endif %}
</td>

View File

@ -47,6 +47,15 @@
</label>
</div>
</div>
<div class="form-group">
<label>Select a template</label>
<select class="form-control" id="domain_template" name="domain_template">
<option value="0">No template</option>
{% for template in templates %}
<option value="{{ template.id }}">{{ template.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group" style="display: none;" id="domain_master_address_div">
<input type="text" class="form-control" name="domain_master_address" id="domain_master_address" placeholder="Enter valid master ip addresses (separated by commas)">
</div>

116
app/templates/template.html Normal file
View File

@ -0,0 +1,116 @@
{% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Templates</title>{% endblock %}
{% block dashboard_stat %}
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Templates
<small>List</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('templates') }}"><i class="fa fa-dashboard"></i> Templates</a></li>
<li class="active">List</li>
</ol>
</section>
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
{% with errors = get_flashed_messages(category_filter=["error"]) %} {% if errors %}
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert"
aria-hidden="true">&times;</button>
<h4>
<i class="icon fa fa-ban"></i> Error!
</h4>
<div class="alert-message block-message error">
<a class="close" href="#">x</a>
<ul>
{%- for msg in errors %}
<li>{{ msg }}</li> {% endfor -%}
</ul>
</div>
</div>
</div>
</div>
{% endif %} {% endwith %}
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">Templates</h3>
</div>
<div class="box-body">
<a href="{{ url_for('create_template') }}">
<button type="button" class="btn btn-flat btn-primary pull-left">
Create Template&nbsp;<i class="fa fa-plus"></i>
</button>
</a>
</div>
<div class="box-body">
<table id="tbl_template_list" class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Number of Records</th>
<th width="20%">Action</th>
</tr>
</thead>
<tbody>
{% for template in templates %}
<tr>
<td>
<a href="{{ url_for('edit_template', template=template.name) }}"><strong>{{ template.name }}</strong></a>
</td>
<td>
{{ template.description }}
</td>
<td>
{{ template.records|count }}
</td>
<td>
<a href="{{ url_for('edit_template', template=template.name) }}">
<button type="button" class="btn btn-flat btn-warning button_edit" id="">
Edit&nbsp;<i class="fa fa-edit"></i>
</button>
</a>
<a href="{{ url_for('delete_template', template=template.name) }}">
<button type="button" class="btn btn-flat btn-danger button_delete" id="">
Delete&nbsp;<i class="fa fa-trash"></i>
</button>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</section>
<!-- /.content -->
{% endblock %}
{% block extrascripts %}
<script>
// set up history data table
$("#tbl_template_list").DataTable({
"paging" : true,
"lengthChange" : true,
"searching" : true,
"ordering" : true,
"info" : false,
"autoWidth" : false
});
</script>
{% endblock %}
{% block modals %}
{% endblock %}

View File

@ -0,0 +1,104 @@
{% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Create Template</title>{% endblock %}
{% block dashboard_stat %}
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Template
<small>Create</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('templates') }}"><i class="fa fa-dashboard"></i> Templates</a></li>
<li class="active">Create</li>
</ol>
</section>
{% endblock %}
{% block content %}
<section class="content">
{% with errors = get_flashed_messages(category_filter=["error"]) %} {%
if errors %}
<div class="row">
<div class="col-md-12">
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert"
aria-hidden="true">&times;</button>
<h4>
<i class="icon fa fa-ban"></i> Error!
</h4>
<div class="alert-message block-message error">
<a class="close" href="#">x</a>
<ul>
{%- for msg in errors %}
<li>{{ msg }}</li> {% endfor -%}
</ul>
</div>
</div>
</div>
</div>
{% endif %} {% endwith %}
<div class="row">
<div class="col-md-4">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Create new template</h3>
</div>
<!-- /.box-header -->
<!-- form start -->
<form role="form" method="post"
action="{{ url_for('create_template') }}">
<div class="box-body">
<div class="form-group">
<input type="text" class="form-control" name="name" id="name"
placeholder="Enter a valid template name (required)">
</div>
<div class="form-group">
<input type="text" class="form-control" name="description"
id="description"
placeholder="Enter a template description (optional)">
</div>
</div>
<div class="box-footer">
<button type="submit" class="btn btn-flat btn-primary">Submit</button>
<button type="button" class="btn btn-flat btn-default"
onclick="window.location.href='{{ url_for('templates') }}'">Cancel</button>
</div>
</form>
</div>
<!-- /.box -->
</div>
<div class="col-md-8">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Help with creating a new template</h3>
</div>
<div class="box-body">
<dl class="dl-horizontal">
<dt>Template name</dt>
<dd>Enter your template name, this is the name of the template that
will be shown to users. The name should not have any spaces but
can have symbols.</dd>
<dt>Template description</dt>
<dd>Enter your template description, this is to help better
identify the template.</dd>
</dl>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extrascripts %}
<script>
$("input[name=radio_type]").change(function() {
var type = $(this).val();
if (type == "slave") {
$("#domain_master_address_div").show();
} else {
$("#domain_master_address_div").hide();
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,415 @@
{% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Edit Template</title>{% endblock %}
{% block dashboard_stat %}
<section class="content-header">
<h1>
Edit template <small>{{ template }}</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('dashboard') }}"><i
class="fa fa-dashboard"></i> Home</a></li>
<li>Templates</li>
<li class="active">{{ template }}</li>
</ol>
</section>
{% endblock %}
{% block content %}
<section class="content">
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">Manage Template Records for {{ template }}</h3>
</div>
<div class="box-body">
<button type="button" class="btn btn-flat btn-primary pull-left button_add_record" id="{{ template }}">
Add Record&nbsp;<i class="fa fa-plus"></i>
</button>
<button type="button" class="btn btn-flat btn-primary pull-right button_apply_changes" id="{{ template }}">
Apply Changes&nbsp;<i class="fa fa-floppy-o"></i>
</button>
</div>
<div class="box-body">
<table id="tbl_records" class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Status</th>
<th>TTL</th>
<th>Data</th>
<th>Edit</th>
<th>Delete</th>
<th>ID</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr class="odd row_record" id="{{ record.name }}">
<td>
{{ record.name }}
</td>
<td>
{{ record.type }}
</td>
<td>
{{ record.status }}
</td>
<td>
{{ record.ttl }}
</td>
<td class="length-break">
{{ record.data }}
</td>
<td width="6%">
<button type="button" class="btn btn-flat btn-warning button_edit" id="{{ record.name }}">
Edit&nbsp;<i class="fa fa-edit"></i>
</button>
</td>
<td width="6%">
<button type="button" class="btn btn-flat btn-danger button_delete" id="{{ record.name }}">
Delete&nbsp;<i class="fa fa-trash"></i>
</button>
</td>
<td>
{{ record.id }}
</td>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extrascripts %}
<script>
// superglobals
window.records_allow_edit = {{ editable_records|tojson }};
window.nEditing = null;
window.nNew = false;
// set up user data table
$("#tbl_records").DataTable({
"paging" : true,
"lengthChange" : true,
"searching" : true,
"ordering" : true,
"info" : true,
"autoWidth" : false,
{% if default_record_table_size_setting in ['5','15','20'] %}
"lengthMenu": [ [5, 15, 20, -1],
[5, 15, 20, "All"]],
{% else %}
"lengthMenu": [ [5, 15, 20, {{ default_record_table_size_setting }}, -1],
[5, 15, 20, {{ default_record_table_size_setting }}, "All"]],
{% endif %}
"pageLength": {{ default_record_table_size_setting }},
"language": {
"lengthMenu": " _MENU_ records"
},
"retrieve" : true,
"columnDefs": [{
"targets": [ 7 ],
"visible": false,
"searchable": false
}]
});
// handle delete button
$(document.body).on("click", ".button_delete", function(e) {
e.stopPropagation();
var modal = $("#modal_delete");
var table = $("#tbl_records").DataTable();
var record = $(this).prop('id');
var nRow = $(this).parents('tr')[0];
var info = "Are you sure you want to delete " + record + "?";
modal.find('.modal-body p').text(info);
modal.find('#button_delete_confirm').click(function() {
table.row(nRow).remove().draw();
modal.modal('hide');
})
modal.modal('show');
});
// handle edit button
$(document.body).on("click", ".button_edit, .row_record", function(e) {
e.stopPropagation();
if ($(this).is('tr')) {
var nRow = $(this)[0];
} else {
var nRow = $(this).parents('tr')[0];
}
var table = $("#tbl_records").DataTable();
if (nEditing == nRow) {
/* click on row already being edited, do nothing */
} else if (nEditing !== null && nEditing != nRow && nNew == false) {
/* Currently editing - but not this row - restore the old before continuing to edit mode */
restoreRow(table, nEditing);
editRow(table, nRow);
nEditing = nRow;
} else if (nNew == true) {
/* adding a new row, delete it and start editing */
table.row(nEditing).remove().draw();
nNew = false;
editRow(table, nRow);
nEditing = nRow;
} else {
/* No edit in progress - let's start one */
editRow(table, nRow);
nEditing = nRow;
}
});
// handle apply changes button
$(document.body).on("click",".button_apply_changes", function() {
var modal = $("#modal_apply_changes");
var table = $("#tbl_records").DataTable();
var template = $(this).prop('id');
var info = "Are you sure you want to apply your changes?";
modal.find('.modal-body p').text(info);
modal.find('#button_apply_confirm').click(function() {
var data = getTableData(table);
applyChanges(data, '/template/' + template + '/apply', true);
modal.modal('hide');
})
modal.modal('show');
});
// handle add record button
$(document.body).on("click", ".button_add_record", function (e) {
if (nNew || nEditing) {
// TODO: replace this alert with modal
alert("Previous record not saved. Please save it before adding more record.")
return;
}
var table = $("#tbl_records").DataTable();
var aiNew = table.row.add(['', 'A', 'Active', 3600, '', '', '', '']).draw();
var nRow = aiNew.index();
editRow(table, nRow);
nEditing = nRow;
nNew = true;
});
//handle cancel button
$(document.body).on("click", ".button_cancel", function (e) {
e.stopPropagation();
var oTable = $("#tbl_records").DataTable();
if (nNew) {
oTable.row(nEditing).remove().draw();
nEditing = null;
nNew = false;
} else {
restoreRow(oTable, nEditing);
nEditing = null;
}
});
//handle save button
$(document.body).on("click", ".button_save", function (e) {
e.stopPropagation();
var table = $("#tbl_records").DataTable();
saveRow(table, nEditing);
nEditing = null;
nNew = false;
});
{% if record_helper_setting %}
//handle wacky record types
$(document.body).on("focus", "#current_edit_record_data", function (e) {
var record_type = $(this).parents("tr").find('#record_type').val();
var record_data = $(this);
if (record_type == "MX") {
var modal = $("#modal_custom_record");
if (record_data.val() == "") {
var form = " <label for=\"mx_priority\">MX Priority</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_priority\" id=\"mx_priority\" placeholder=\"10\"> \
<label for=\"mx_server\">MX Server</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_server\" id=\"mx_server\" placeholder=\"postfix.example.com\"> \
";
} else {
var parts = record_data.val().split(" ");
var form = " <label for=\"mx_priority\">MX Priority</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_priority\" id=\"mx_priority\" placeholder=\"10\" value=\"" + parts[0] + "\"> \
<label for=\"mx_server\">MX Server</label> \
<input type=\"text\" class=\"form-control\" name=\"mx_server\" id=\"mx_server\" placeholder=\"postfix.example.com\" value=\"" + parts[1] + "\"> \
";
}
modal.find('.modal-body p').html(form);
modal.find('#button_save').click(function() {
mx_server = modal.find('#mx_server').val();
mx_priority = modal.find('#mx_priority').val();
data = mx_priority + " " + mx_server;
record_data.val(data);
modal.modal('hide');
})
modal.modal('show');
} else if (record_type == "SRV") {
var modal = $("#modal_custom_record");
if (record_data.val() == "") {
var form = " <label for=\"srv_priority\">SRV Priority</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_priority\" id=\"srv_priority\" placeholder=\"0\"> \
<label for=\"srv_weight\">SRV Weight</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_weight\" id=\"srv_weight\" placeholder=\"10\"> \
<label for=\"srv_port\">SRV Port</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_port\" id=\"srv_port\" placeholder=\"5060\"> \
<label for=\"srv_target\">SRV Target</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_target\" id=\"srv_target\" placeholder=\"sip.example.com\"> \
";
} else {
var parts = record_data.val().split(" ");
var form = " <label for=\"srv_priority\">SRV Priority</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_priority\" id=\"srv_priority\" placeholder=\"0\" value=\"" + parts[0] + "\"> \
<label for=\"srv_weight\">SRV Weight</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_weight\" id=\"srv_weight\" placeholder=\"10\" value=\"" + parts[1] + "\"> \
<label for=\"srv_port\">SRV Port</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_port\" id=\"srv_port\" placeholder=\"5060\" value=\"" + parts[2] + "\"> \
<label for=\"srv_target\">SRV Target</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_target\" id=\"srv_target\" placeholder=\"sip.example.com\" value=\"" + parts[3] + "\"> \
";
}
modal.find('.modal-body p').html(form);
modal.find('#button_save').click(function() {
srv_priority = modal.find('#srv_priority').val();
srv_weight = modal.find('#srv_weight').val();
srv_port = modal.find('#srv_port').val();
srv_target = modal.find('#srv_target').val();
data = srv_priority + " " + srv_weight + " " + srv_port + " " + srv_target;
record_data.val(data);
modal.modal('hide');
})
modal.modal('show');
} else if (record_type == "SOA") {
var modal = $("#modal_custom_record");
if (record_data.val() == "") {
var form = " <label for=\"soa_primaryns\">Primary Name Server</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_primaryns\" id=\"soa_primaryns\" placeholder=\"ns1.example.com\"> \
<label for=\"soa_adminemail\">Primary Contact</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_adminemail\" id=\"soa_adminemail\" placeholder=\"admin.example.com\"> \
<label for=\"soa_serial\">Serial</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_serial\" id=\"soa_serial\" placeholder=\"2016010101\"> \
<label for=\"soa_zonerefresh\">Zone refresh timer</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_zonerefresh\" id=\"soa_zonerefresh\" placeholder=\"86400\"> \
<label for=\"soa_failedzonerefresh\">Failed refresh retry timer</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_failedzonerefresh\" id=\"soa_failedzonerefresh\" placeholder=\"7200\"> \
<label for=\"soa_zoneexpiry\">Zone expiry timer</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_zoneexpiry\" id=\"soa_zoneexpiry\" placeholder=\"604800\"> \
<label for=\"soa_minimumttl\">Minimum TTL</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_minimumttl\" id=\"soa_minimumttl\" placeholder=\"300\"> \
";
} else {
var parts = record_data.val().split(" ");
var form = " <label for=\"soa_primaryns\">Primary Name Server</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_primaryns\" id=\"soa_primaryns\" value=\"" + parts[0] + "\"> \
<label for=\"soa_adminemail\">Primary Contact</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_adminemail\" id=\"soa_adminemail\" value=\"" + parts[1] + "\"> \
<label for=\"soa_serial\">Serial</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_serial\" id=\"soa_serial\" value=\"" + parts[2] + "\"> \
<label for=\"soa_zonerefresh\">Zone refresh timer</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_zonerefresh\" id=\"soa_zonerefresh\" value=\"" + parts[3] + "\"> \
<label for=\"soa_failedzonerefresh\">Failed refresh retry timer</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_failedzonerefresh\" id=\"soa_failedzonerefresh\" value=\"" + parts[4] + "\"> \
<label for=\"soa_zoneexpiry\">Zone expiry timer</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_zoneexpiry\" id=\"soa_zoneexpiry\" value=\"" + parts[5] + "\"> \
<label for=\"soa_minimumttl\">Minimum TTL</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_minimumttl\" id=\"soa_minimumttl\" value=\"" + parts[6] + "\"> \
";
}
modal.find('.modal-body p').html(form);
modal.find('#button_save').click(function() {
soa_primaryns = modal.find('#soa_primaryns').val();
soa_adminemail = modal.find('#soa_adminemail').val();
soa_serial = modal.find('#soa_serial').val();
soa_zonerefresh = modal.find('#soa_zonerefresh').val();
soa_failedzonerefresh = modal.find('#soa_failedzonerefresh').val();
soa_zoneexpiry = modal.find('#soa_zoneexpiry').val();
soa_minimumttl = modal.find('#soa_minimumttl').val();
data = soa_primaryns + " " + soa_adminemail + " " + soa_serial + " " + soa_zonerefresh + " " + soa_failedzonerefresh + " " + soa_zoneexpiry + " " + soa_minimumttl;
record_data.val(data);
modal.modal('hide');
})
modal.modal('show');
}
});
{% endif %}
</script>
{% endblock %}
{% block modals %}
<div class="modal fade modal-warning" id="modal_delete">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Confirmation</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-default pull-left"
data-dismiss="modal">Close</button>
<button type="button" class="btn btn-flat btn-danger" id="button_delete_confirm">Delete</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<div class="modal fade modal-primary" id="modal_apply_changes">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Confirmation</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-default pull-left"
data-dismiss="modal">Close</button>
<button type="button" class="btn btn-flat btn-primary" id="button_apply_confirm">Apply</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<div class="modal fade modal-primary" id="modal_custom_record">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Custom Record</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-primary" id="button_save">Save</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endblock %}

View File

@ -11,17 +11,21 @@ from io import BytesIO
import jinja2
import qrcode as qrc
import qrcode.image.svg as qrc_svg
from flask import g, request, make_response, jsonify, render_template, session, redirect, url_for, send_from_directory, abort
from flask import g, request, make_response, jsonify, render_template, session, redirect, url_for, send_from_directory, abort, flash
from flask_login import login_user, logout_user, current_user, login_required
from werkzeug import secure_filename
from werkzeug.security import gen_salt
from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting
from .models import User, Domain, Record, Server, History, Anonymous, Setting, DomainSetting, DomainTemplate, DomainTemplateRecord
from app import app, login_manager, github, google
from app.lib import utils
from app.lib.log import logger
from app.decorators import admin_role_required, can_access_domain
# LOG CONFIG
logging = logger('MODEL', app.config['LOG_LEVEL'], app.config['LOG_FILE']).config()
# FILTERS
jinja2.filters.FILTERS['display_record_name'] = utils.display_record_name
jinja2.filters.FILTERS['display_master_name'] = utils.display_master_name
jinja2.filters.FILTERS['display_second_to_time'] = utils.display_time
@ -436,10 +440,13 @@ def domain(domain_name):
@login_required
@admin_role_required
def domain_add():
templates = DomainTemplate.query.all()
if request.method == 'POST':
try:
domain_name = request.form.getlist('domain_name')[0]
domain_type = request.form.getlist('radio_type')[0]
domain_template = request.form.getlist('domain_template')[0]
logging.info("Selected template ==== {0}".format(domain_template))
soa_edit_api = request.form.getlist('radio_type_soa_edit_api')[0]
if ' ' in domain_name or not domain_name or not domain_type:
@ -457,12 +464,28 @@ def domain_add():
if result['status'] == 'ok':
history = History(msg='Add domain %s' % domain_name, detail=str({'domain_type': domain_type, 'domain_master_ips': domain_master_ips}), created_by=current_user.username)
history.add()
if domain_template != '0':
template = DomainTemplate.query.filter(DomainTemplate.id == domain_template).first()
template_records = DomainTemplateRecord.query.filter(DomainTemplateRecord.template_id == domain_template).all()
record_data = []
for template_record in template_records:
record_row = {'record_data': template_record.data, 'record_name': template_record.name, 'record_status': template_record.status, 'record_ttl': template_record.ttl, 'record_type': template_record.type}
record_data.append(record_row)
r = Record()
result = r.apply(domain_name, record_data)
if result['status'] == 'ok':
history = History(msg='Applying template %s to %s, created records successfully.' % (template.name, domain_name), detail=str(result), created_by=current_user.username)
history.add()
else:
history = History(msg='Applying template %s to %s, FAILED to created records.' % (template.name, domain_name), detail=str(result), created_by=current_user.username)
history.add()
return redirect(url_for('dashboard'))
else:
return render_template('errors/400.html', msg=result['msg']), 400
except:
logging.error(traceback.print_exc())
return redirect(url_for('error', code=500))
return render_template('domain_add.html')
return render_template('domain_add.html', templates=templates)
@app.route('/admin/domain/<path:domain_name>/delete', methods=['GET'])
@ -534,7 +557,7 @@ def record_apply(domain_name):
else:
return make_response(jsonify( result ), 400)
except:
traceback.print_exc()
logging.error(traceback.print_exc())
return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500)
@ -557,7 +580,7 @@ def record_update(domain_name):
else:
return make_response(jsonify( {'status': 'error', 'msg': result['msg']} ), 500)
except:
traceback.print_exc()
logging.error(traceback.print_exc())
return make_response(jsonify( {'status': 'error', 'msg': 'Error when applying new changes'} ), 500)
@ -571,7 +594,7 @@ def record_delete(domain_name, record_name, record_type):
if result['status'] == 'error':
print(result['msg'])
except:
traceback.print_exc()
logging.error(traceback.print_exc())
return redirect(url_for('error', code=500)), 500
return redirect(url_for('domain', domain_name=domain_name))
@ -621,10 +644,182 @@ def admin_setdomainsetting(domain_name):
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except:
traceback.print_exc()
logging.error(traceback.print_exc())
return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400)
@app.route('/templates', methods=['GET', 'POST'])
@app.route('/templates/list', methods=['GET', 'POST'])
@login_required
@admin_role_required
def templates():
templates = DomainTemplate.query.all()
return render_template('template.html', templates=templates)
@app.route('/template/create', methods=['GET', 'POST'])
@login_required
@admin_role_required
def create_template():
if request.method == 'GET':
return render_template('template_add.html')
if request.method == 'POST':
try:
name = request.form.getlist('name')[0]
description = request.form.getlist('description')[0]
if ' ' in name or not name or not type:
flash("Please correct your input", 'error')
return redirect(url_for('create_template'))
if DomainTemplate.query.filter(DomainTemplate.name == name).first():
flash("A template with the name %s already exists!" % name, 'error')
return redirect(url_for('create_template'))
t = DomainTemplate(name=name, description=description)
result = t.create()
if result['status'] == 'ok':
history = History(msg='Add domain template %s' % name, detail=str({'name': name, 'description': description}), created_by=current_user.username)
history.add()
return redirect(url_for('templates'))
else:
flash(result['msg'], 'error')
return redirect(url_for('create_template'))
except:
logging.error(traceback.print_exc())
return redirect(url_for('error', code=500))
return redirect(url_for('templates'))
@app.route('/template/createfromzone', methods=['POST'])
@login_required
@admin_role_required
def create_template_from_zone():
try:
jdata = request.json
name = jdata['name']
description = jdata['description']
domain_name = jdata['domain']
if ' ' in name or not name or not type:
return make_response(jsonify({'status': 'error', 'msg': 'Please correct template name'}), 500)
if DomainTemplate.query.filter(DomainTemplate.name == name).first():
return make_response(jsonify({'status': 'error', 'msg': 'A template with the name %s already exists!' % name}), 500)
t = DomainTemplate(name=name, description=description)
result = t.create()
if result['status'] == 'ok':
history = History(msg='Add domain template %s' % name, detail=str({'name': name, 'description': description}), created_by=current_user.username)
history.add()
records = []
r = Record()
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
# query domain info from PowerDNS API
zone_info = r.get_record_data(domain.name)
if zone_info:
jrecords = zone_info['records']
if NEW_SCHEMA:
for jr in jrecords:
name = '@' if jr['name'] == domain_name else jr['name']
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']:
for subrecord in jr['records']:
record = DomainTemplateRecord(name=name, type=jr['type'], status=True if subrecord['disabled'] else False, ttl=jr['ttl'], data=subrecord['content'])
records.append(record)
else:
for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']:
record = DomainTemplateRecord(name=name, type=jr['type'], status=True if jr['disabled'] else False, ttl=jr['ttl'], data=jr['content'])
records.append(record)
result_records = t.replace_records(records)
if result_records['status'] == 'ok':
return make_response(jsonify({'status': 'ok', 'msg': result['msg']}), 200)
else:
result = t.delete_template()
return make_response(jsonify({'status': 'error', 'msg': result_records['msg']}), 500)
else:
return make_response(jsonify({'status': 'error', 'msg': result['msg']}), 500)
except:
logging.error(traceback.print_exc())
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<string:template>/edit', methods=['GET'])
@login_required
@admin_role_required
def edit_template(template):
try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
if t is not None:
records = []
for jr in t.records:
if jr.type in app.config['RECORDS_ALLOW_EDIT']:
record = DomainTemplateRecord(name=jr.name, type=jr.type, status='Disabled' if jr.status else 'Active', ttl=jr.ttl, data=jr.data)
records.append(record)
return render_template('template_edit.html', template=t.name, records=records, editable_records=app.config['RECORDS_ALLOW_EDIT'])
except:
logging.error(traceback.print_exc())
return redirect(url_for('error', code=500))
return redirect(url_for('templates'))
@app.route('/template/<string:template>/apply', methods=['POST'], strict_slashes=False)
@login_required
def apply_records(template):
try:
jdata = request.json
records = []
for j in jdata:
name = '@' if j['record_name'] in ['@', ''] else j['record_name']
type = j['record_type']
data = j['record_data']
disabled = True if j['record_status'] == 'Disabled' else False
ttl = int(j['record_ttl']) if j['record_ttl'] else 3600
dtr = DomainTemplateRecord(name=name, type=type, data=data, status=disabled, ttl=ttl)
records.append(dtr)
t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
result = t.replace_records(records)
if result['status'] == 'ok':
history = History(msg='Apply domain template record changes to domain template %s' % template, detail=str(jdata), created_by=current_user.username)
history.add()
return make_response(jsonify(result), 200)
else:
return make_response(jsonify(result), 400)
except:
logging.error(traceback.print_exc())
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<string:template>/delete', methods=['GET'])
@login_required
@admin_role_required
def delete_template(template):
try:
t = DomainTemplate.query.filter(DomainTemplate.name == template).first()
if t is not None:
result = t.delete_template()
if result['status'] == 'ok':
history = History(msg='Deleted domain template %s' % template, detail=str({'name': template}), created_by=current_user.username)
history.add()
return redirect(url_for('templates'))
else:
flash(result['msg'], 'error')
return redirect(url_for('templates'))
except:
logging.error(traceback.print_exc())
return redirect(url_for('error', code=500))
return redirect(url_for('templates'))
@app.route('/admin', methods=['GET', 'POST'])
@login_required
@admin_role_required
@ -718,7 +913,7 @@ def admin_manageuser():
else:
return make_response(jsonify( { 'status': 'error', 'msg': 'Action not supported.' } ), 400)
except:
traceback.print_exc()
logging.error(traceback.print_exc())
return make_response(jsonify( { 'status': 'error', 'msg': 'There is something wrong, please contact Administrator.' } ), 400)
@ -883,7 +1078,7 @@ def dyndns_update():
r = Record()
r.name = hostname
# check if the user requested record exists within this domain
if r.exists(domain.name) and r.is_allowed:
if r.exists(domain.name) and r.is_allowed_edit():
if r.data == myip:
# record content did not change, return 'nochg'
history = History(msg="DynDNS update: attempted update of %s but record did not change" % hostname, created_by=current_user.username)
@ -898,7 +1093,7 @@ def dyndns_update():
return render_template('dyndns.html', response='good'), 200
else:
return render_template('dyndns.html', response='911'), 200
elif r.is_allowed:
elif r.is_allowed_edit():
ondemand_creation = DomainSetting.query.filter(DomainSetting.domain == domain).filter(DomainSetting.setting == 'create_via_dyndns').first()
if (ondemand_creation != None) and (strtobool(ondemand_creation.value) == True):
record = Record(name=hostname,type='A',data=myip,status=False,ttl=3600)

View File

@ -9,7 +9,7 @@ from migrate.versioning import api
from config import SQLALCHEMY_DATABASE_URI
from config import SQLALCHEMY_MIGRATE_REPO
from app import db
from app.models import Role, Setting
from app.models import Role, Setting, DomainTemplate
def start():
@ -69,6 +69,23 @@ def init_settings(db, setting_names):
for setting in settings:
db.session.add(setting)
def init_domain_templates(db, domain_template_names):
# Get key name of data
name_of_domain_templates = map(lambda r: r.name, domain_template_names)
# Query to get current data
rows = db.session.query(DomainTemplate).filter(DomainTemplate.name.in_(name_of_domain_templates)).all()
# Check which data that need to insert
name_of_rows = map(lambda r: r.name, rows)
domain_templates = filter(lambda r: r.name not in name_of_rows, domain_template_names)
# Insert data
for domain_template in domain_templates:
db.session.add(domain_template)
def init_records():
# Create initial user roles and turn off maintenance mode
init_roles(db, [
@ -84,7 +101,12 @@ def init_records():
Setting('default_domain_table_size', '10'),
Setting('auto_ptr','False')
])
# TODO: add sample records to sample templates
init_domain_templates(db, [
DomainTemplate('basic_template_1', 'Basic Template #1'),
DomainTemplate('basic_template_2', 'Basic Template #2'),
DomainTemplate('basic_template_3', 'Basic Template #3')
])
db_commit = db.session.commit()
commit_version_control(db_commit)