mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2024-11-09 23:20:27 +00:00
Merge branch 'master' into record_adjustment
This commit is contained in:
commit
f5b88ebc73
@ -3,14 +3,11 @@ import json
|
|||||||
import requests
|
import requests
|
||||||
import hashlib
|
import hashlib
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import os
|
|
||||||
|
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from .certutil import KEY_FILE, CERT_FILE
|
|
||||||
|
|
||||||
|
|
||||||
def auth_from_url(url):
|
def auth_from_url(url):
|
||||||
auth = None
|
auth = None
|
||||||
@ -184,101 +181,6 @@ def email_to_gravatar_url(email="", size=100):
|
|||||||
return "https://s.gravatar.com/avatar/{0}?s={1}".format(hash_string, size)
|
return "https://s.gravatar.com/avatar/{0}?s={1}".format(hash_string, size)
|
||||||
|
|
||||||
|
|
||||||
def prepare_flask_request(request):
|
|
||||||
# If server is behind proxys or balancers use the HTTP_X_FORWARDED fields
|
|
||||||
url_data = urlparse(request.url)
|
|
||||||
return {
|
|
||||||
'https': 'on' if request.scheme == 'https' else 'off',
|
|
||||||
'http_host': request.host,
|
|
||||||
'server_port': url_data.port,
|
|
||||||
'script_name': request.path,
|
|
||||||
'get_data': request.args.copy(),
|
|
||||||
'post_data': request.form.copy(),
|
|
||||||
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
|
|
||||||
'lowercase_urlencoding': True,
|
|
||||||
'query_string': request.query_string
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def init_saml_auth(req):
|
|
||||||
own_url = ''
|
|
||||||
if req['https'] == 'on':
|
|
||||||
own_url = 'https://'
|
|
||||||
else:
|
|
||||||
own_url = 'http://'
|
|
||||||
own_url += req['http_host']
|
|
||||||
metadata = get_idp_data()
|
|
||||||
settings = {}
|
|
||||||
settings['sp'] = {}
|
|
||||||
if 'SAML_NAMEID_FORMAT' in app.config:
|
|
||||||
settings['sp']['NameIDFormat'] = app.config['SAML_NAMEID_FORMAT']
|
|
||||||
else:
|
|
||||||
settings['sp']['NameIDFormat'] = idp_data.get('sp', {}).get(
|
|
||||||
'NameIDFormat',
|
|
||||||
'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified')
|
|
||||||
settings['sp']['entityId'] = app.config['SAML_SP_ENTITY_ID']
|
|
||||||
if os.path.isfile(CERT_FILE):
|
|
||||||
cert = open(CERT_FILE, "r").readlines()
|
|
||||||
settings['sp']['x509cert'] = "".join(cert)
|
|
||||||
if os.path.isfile(KEY_FILE):
|
|
||||||
key = open(KEY_FILE, "r").readlines()
|
|
||||||
settings['sp']['privateKey'] = "".join(key)
|
|
||||||
settings['sp']['assertionConsumerService'] = {}
|
|
||||||
settings['sp']['assertionConsumerService'][
|
|
||||||
'binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
|
||||||
settings['sp']['assertionConsumerService'][
|
|
||||||
'url'] = own_url + '/saml/authorized'
|
|
||||||
settings['sp']['attributeConsumingService'] = {}
|
|
||||||
settings['sp']['singleLogoutService'] = {}
|
|
||||||
settings['sp']['singleLogoutService'][
|
|
||||||
'binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
|
||||||
settings['sp']['singleLogoutService']['url'] = own_url + '/saml/sls'
|
|
||||||
settings['idp'] = metadata['idp']
|
|
||||||
settings['strict'] = True
|
|
||||||
settings['debug'] = app.config['SAML_DEBUG']
|
|
||||||
settings['security'] = {}
|
|
||||||
settings['security'][
|
|
||||||
'digestAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
|
||||||
settings['security']['metadataCacheDuration'] = None
|
|
||||||
settings['security']['metadataValidUntil'] = None
|
|
||||||
settings['security']['requestedAuthnContext'] = True
|
|
||||||
settings['security'][
|
|
||||||
'signatureAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
|
||||||
settings['security']['wantAssertionsEncrypted'] = False
|
|
||||||
settings['security']['wantAttributeStatement'] = True
|
|
||||||
settings['security']['wantNameId'] = True
|
|
||||||
settings['security']['authnRequestsSigned'] = app.config[
|
|
||||||
'SAML_SIGN_REQUEST']
|
|
||||||
settings['security']['logoutRequestSigned'] = app.config[
|
|
||||||
'SAML_SIGN_REQUEST']
|
|
||||||
settings['security']['logoutResponseSigned'] = app.config[
|
|
||||||
'SAML_SIGN_REQUEST']
|
|
||||||
settings['security']['nameIdEncrypted'] = False
|
|
||||||
settings['security']['signMetadata'] = True
|
|
||||||
settings['security']['wantAssertionsSigned'] = True
|
|
||||||
settings['security']['wantMessagesSigned'] = app.config.get(
|
|
||||||
'SAML_WANT_MESSAGE_SIGNED', True)
|
|
||||||
settings['security']['wantNameIdEncrypted'] = False
|
|
||||||
settings['contactPerson'] = {}
|
|
||||||
settings['contactPerson']['support'] = {}
|
|
||||||
settings['contactPerson']['support']['emailAddress'] = app.config[
|
|
||||||
'SAML_SP_CONTACT_NAME']
|
|
||||||
settings['contactPerson']['support']['givenName'] = app.config[
|
|
||||||
'SAML_SP_CONTACT_MAIL']
|
|
||||||
settings['contactPerson']['technical'] = {}
|
|
||||||
settings['contactPerson']['technical']['emailAddress'] = app.config[
|
|
||||||
'SAML_SP_CONTACT_NAME']
|
|
||||||
settings['contactPerson']['technical']['givenName'] = app.config[
|
|
||||||
'SAML_SP_CONTACT_MAIL']
|
|
||||||
settings['organization'] = {}
|
|
||||||
settings['organization']['en-US'] = {}
|
|
||||||
settings['organization']['en-US']['displayname'] = 'PowerDNS-Admin'
|
|
||||||
settings['organization']['en-US']['name'] = 'PowerDNS-Admin'
|
|
||||||
settings['organization']['en-US']['url'] = own_url
|
|
||||||
auth = OneLogin_Saml2_Auth(req, settings)
|
|
||||||
return auth
|
|
||||||
|
|
||||||
|
|
||||||
def display_setting_state(value):
|
def display_setting_state(value):
|
||||||
if value == 1:
|
if value == 1:
|
||||||
return "ON"
|
return "ON"
|
||||||
|
@ -28,11 +28,13 @@ from ..services.google import google_oauth
|
|||||||
from ..services.github import github_oauth
|
from ..services.github import github_oauth
|
||||||
from ..services.azure import azure_oauth
|
from ..services.azure import azure_oauth
|
||||||
from ..services.oidc import oidc_oauth
|
from ..services.oidc import oidc_oauth
|
||||||
|
from ..services.saml import SAML
|
||||||
|
|
||||||
google = None
|
google = None
|
||||||
github = None
|
github = None
|
||||||
azure = None
|
azure = None
|
||||||
oidc = None
|
oidc = None
|
||||||
|
saml = None
|
||||||
|
|
||||||
index_bp = Blueprint('index',
|
index_bp = Blueprint('index',
|
||||||
__name__,
|
__name__,
|
||||||
@ -46,10 +48,12 @@ def register_modules():
|
|||||||
global github
|
global github
|
||||||
global azure
|
global azure
|
||||||
global oidc
|
global oidc
|
||||||
|
global saml
|
||||||
google = google_oauth()
|
google = google_oauth()
|
||||||
github = github_oauth()
|
github = github_oauth()
|
||||||
azure = azure_oauth()
|
azure = azure_oauth()
|
||||||
oidc = oidc_oauth()
|
oidc = oidc_oauth()
|
||||||
|
saml = SAML()
|
||||||
|
|
||||||
|
|
||||||
@index_bp.before_request
|
@index_bp.before_request
|
||||||
@ -351,8 +355,8 @@ def logout():
|
|||||||
'SAML_ENABLED'
|
'SAML_ENABLED'
|
||||||
) and 'samlSessionIndex' in session and current_app.config.get(
|
) and 'samlSessionIndex' in session and current_app.config.get(
|
||||||
'SAML_LOGOUT'):
|
'SAML_LOGOUT'):
|
||||||
req = utils.prepare_flask_request(request)
|
req = saml.prepare_flask_request(request)
|
||||||
auth = utils.init_saml_auth(req)
|
auth = saml.init_saml_auth(req)
|
||||||
if current_app.config.get('SAML_LOGOUT_URL'):
|
if current_app.config.get('SAML_LOGOUT_URL'):
|
||||||
return redirect(
|
return redirect(
|
||||||
auth.logout(
|
auth.logout(
|
||||||
@ -579,10 +583,10 @@ def dyndns_update():
|
|||||||
def saml_login():
|
def saml_login():
|
||||||
if not current_app.config.get('SAML_ENABLED'):
|
if not current_app.config.get('SAML_ENABLED'):
|
||||||
abort(400)
|
abort(400)
|
||||||
req = utils.prepare_flask_request(request)
|
req = saml.prepare_flask_request(request)
|
||||||
auth = utils.init_saml_auth(req)
|
auth = saml.init_saml_auth(req)
|
||||||
redirect_url = OneLogin_Saml2_Utils.get_self_url(req) + url_for(
|
redirect_url = OneLogin_Saml2_Utils.get_self_url(req) + url_for(
|
||||||
'saml_authorized')
|
'index.saml_authorized')
|
||||||
return redirect(auth.login(return_to=redirect_url))
|
return redirect(auth.login(return_to=redirect_url))
|
||||||
|
|
||||||
|
|
||||||
@ -592,8 +596,8 @@ def saml_metadata():
|
|||||||
current_app.logger.error("SAML authentication is disabled.")
|
current_app.logger.error("SAML authentication is disabled.")
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
req = utils.prepare_flask_request(request)
|
req = saml.prepare_flask_request(request)
|
||||||
auth = utils.init_saml_auth(req)
|
auth = saml.init_saml_auth(req)
|
||||||
settings = auth.get_settings()
|
settings = auth.get_settings()
|
||||||
metadata = settings.get_sp_metadata()
|
metadata = settings.get_sp_metadata()
|
||||||
errors = settings.validate_metadata(metadata)
|
errors = settings.validate_metadata(metadata)
|
||||||
@ -612,8 +616,8 @@ def saml_authorized():
|
|||||||
if not current_app.config.get('SAML_ENABLED'):
|
if not current_app.config.get('SAML_ENABLED'):
|
||||||
current_app.logger.error("SAML authentication is disabled.")
|
current_app.logger.error("SAML authentication is disabled.")
|
||||||
abort(400)
|
abort(400)
|
||||||
req = utils.prepare_flask_request(request)
|
req = saml.prepare_flask_request(request)
|
||||||
auth = utils.init_saml_auth(req)
|
auth = saml.init_saml_auth(req)
|
||||||
auth.process_response()
|
auth.process_response()
|
||||||
errors = auth.get_errors()
|
errors = auth.get_errors()
|
||||||
if len(errors) == 0:
|
if len(errors) == 0:
|
||||||
@ -719,7 +723,7 @@ def saml_authorized():
|
|||||||
session['authentication_type'] = 'SAML'
|
session['authentication_type'] = 'SAML'
|
||||||
login_user(user, remember=False)
|
login_user(user, remember=False)
|
||||||
signin_history(user.username, 'SAML', True)
|
signin_history(user.username, 'SAML', True)
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index.login'))
|
||||||
else:
|
else:
|
||||||
return render_template('errors/SAML.html', errors=errors)
|
return render_template('errors/SAML.html', errors=errors)
|
||||||
|
|
||||||
@ -765,8 +769,8 @@ def uplift_to_admin(user):
|
|||||||
|
|
||||||
@index_bp.route('/saml/sls')
|
@index_bp.route('/saml/sls')
|
||||||
def saml_logout():
|
def saml_logout():
|
||||||
req = utils.prepare_flask_request(request)
|
req = saml.prepare_flask_request(request)
|
||||||
auth = utils.init_saml_auth(req)
|
auth = saml.init_saml_auth(req)
|
||||||
url = auth.process_slo()
|
url = auth.process_slo()
|
||||||
errors = auth.get_errors()
|
errors = auth.get_errors()
|
||||||
if len(errors) == 0:
|
if len(errors) == 0:
|
||||||
|
163
powerdnsadmin/services/saml.py
Normal file
163
powerdnsadmin/services/saml.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from threading import Thread
|
||||||
|
from flask import current_app
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ..lib.certutil import KEY_FILE, CERT_FILE
|
||||||
|
from ..lib.utils import urlparse
|
||||||
|
|
||||||
|
|
||||||
|
class SAML(object):
|
||||||
|
def __init__(self):
|
||||||
|
if current_app.config['SAML_ENABLED']:
|
||||||
|
from onelogin.saml2.auth import OneLogin_Saml2_Auth
|
||||||
|
from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
|
||||||
|
|
||||||
|
self.idp_timestamp = datetime.now()
|
||||||
|
self.OneLogin_Saml2_Auth = OneLogin_Saml2_Auth
|
||||||
|
self.OneLogin_Saml2_IdPMetadataParser = OneLogin_Saml2_IdPMetadataParser
|
||||||
|
self.idp_data = None
|
||||||
|
|
||||||
|
if 'SAML_IDP_ENTITY_ID' in current_app.config:
|
||||||
|
self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(
|
||||||
|
current_app.config['SAML_METADATA_URL'],
|
||||||
|
entity_id=current_app.config.get('SAML_IDP_ENTITY_ID',
|
||||||
|
None),
|
||||||
|
required_sso_binding=current_app.
|
||||||
|
config['SAML_IDP_SSO_BINDING'])
|
||||||
|
else:
|
||||||
|
self.idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(
|
||||||
|
current_app.config['SAML_METADATA_URL'],
|
||||||
|
entity_id=current_app.config.get('SAML_IDP_ENTITY_ID',
|
||||||
|
None))
|
||||||
|
if self.idp_data is None:
|
||||||
|
current_app.logger.info(
|
||||||
|
'SAML: IDP Metadata initial load failed')
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
def get_idp_data(self):
|
||||||
|
|
||||||
|
lifetime = timedelta(
|
||||||
|
minutes=current_app.config['SAML_METADATA_CACHE_LIFETIME'])
|
||||||
|
|
||||||
|
if self.idp_timestamp + lifetime < datetime.now():
|
||||||
|
background_thread = Thread(target=self.retrieve_idp_data())
|
||||||
|
background_thread.start()
|
||||||
|
|
||||||
|
return self.idp_data
|
||||||
|
|
||||||
|
def retrieve_idp_data(self):
|
||||||
|
|
||||||
|
if 'SAML_IDP_SSO_BINDING' in current_app.config:
|
||||||
|
new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote(
|
||||||
|
current_app.config['SAML_METADATA_URL'],
|
||||||
|
entity_id=current_app.config.get('SAML_IDP_ENTITY_ID', None),
|
||||||
|
required_sso_binding=current_app.config['SAML_IDP_SSO_BINDING']
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
new_idp_data = self.OneLogin_Saml2_IdPMetadataParser.parse_remote(
|
||||||
|
current_app.config['SAML_METADATA_URL'],
|
||||||
|
entity_id=current_app.config.get('SAML_IDP_ENTITY_ID', None))
|
||||||
|
if new_idp_data is not None:
|
||||||
|
self.idp_data = new_idp_data
|
||||||
|
self.idp_timestamp = datetime.now()
|
||||||
|
current_app.logger.info(
|
||||||
|
"SAML: IDP Metadata successfully retrieved from: " +
|
||||||
|
current_app.config['SAML_METADATA_URL'])
|
||||||
|
else:
|
||||||
|
current_app.logger.info(
|
||||||
|
"SAML: IDP Metadata could not be retrieved")
|
||||||
|
|
||||||
|
def prepare_flask_request(self, request):
|
||||||
|
# If server is behind proxys or balancers use the HTTP_X_FORWARDED fields
|
||||||
|
url_data = urlparse(request.url)
|
||||||
|
return {
|
||||||
|
'https': 'on' if request.scheme == 'https' else 'off',
|
||||||
|
'http_host': request.host,
|
||||||
|
'server_port': url_data.port,
|
||||||
|
'script_name': request.path,
|
||||||
|
'get_data': request.args.copy(),
|
||||||
|
'post_data': request.form.copy(),
|
||||||
|
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
|
||||||
|
'lowercase_urlencoding': True,
|
||||||
|
'query_string': request.query_string
|
||||||
|
}
|
||||||
|
|
||||||
|
def init_saml_auth(self, req):
|
||||||
|
own_url = ''
|
||||||
|
if req['https'] == 'on':
|
||||||
|
own_url = 'https://'
|
||||||
|
else:
|
||||||
|
own_url = 'http://'
|
||||||
|
own_url += req['http_host']
|
||||||
|
metadata = self.get_idp_data()
|
||||||
|
settings = {}
|
||||||
|
settings['sp'] = {}
|
||||||
|
if 'SAML_NAMEID_FORMAT' in current_app.config:
|
||||||
|
settings['sp']['NameIDFormat'] = current_app.config[
|
||||||
|
'SAML_NAMEID_FORMAT']
|
||||||
|
else:
|
||||||
|
settings['sp']['NameIDFormat'] = self.idp_data.get('sp', {}).get(
|
||||||
|
'NameIDFormat',
|
||||||
|
'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified')
|
||||||
|
settings['sp']['entityId'] = current_app.config['SAML_SP_ENTITY_ID']
|
||||||
|
if os.path.isfile(CERT_FILE):
|
||||||
|
cert = open(CERT_FILE, "r").readlines()
|
||||||
|
settings['sp']['x509cert'] = "".join(cert)
|
||||||
|
if os.path.isfile(KEY_FILE):
|
||||||
|
key = open(KEY_FILE, "r").readlines()
|
||||||
|
settings['sp']['privateKey'] = "".join(key)
|
||||||
|
settings['sp']['assertionConsumerService'] = {}
|
||||||
|
settings['sp']['assertionConsumerService'][
|
||||||
|
'binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||||
|
settings['sp']['assertionConsumerService'][
|
||||||
|
'url'] = own_url + '/saml/authorized'
|
||||||
|
settings['sp']['attributeConsumingService'] = {}
|
||||||
|
settings['sp']['singleLogoutService'] = {}
|
||||||
|
settings['sp']['singleLogoutService'][
|
||||||
|
'binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
|
||||||
|
settings['sp']['singleLogoutService']['url'] = own_url + '/saml/sls'
|
||||||
|
settings['idp'] = metadata['idp']
|
||||||
|
settings['strict'] = True
|
||||||
|
settings['debug'] = current_app.config['SAML_DEBUG']
|
||||||
|
settings['security'] = {}
|
||||||
|
settings['security'][
|
||||||
|
'digestAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
||||||
|
settings['security']['metadataCacheDuration'] = None
|
||||||
|
settings['security']['metadataValidUntil'] = None
|
||||||
|
settings['security']['requestedAuthnContext'] = True
|
||||||
|
settings['security'][
|
||||||
|
'signatureAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
|
||||||
|
settings['security']['wantAssertionsEncrypted'] = True
|
||||||
|
settings['security']['wantAttributeStatement'] = True
|
||||||
|
settings['security']['wantNameId'] = True
|
||||||
|
settings['security']['authnRequestsSigned'] = current_app.config[
|
||||||
|
'SAML_SIGN_REQUEST']
|
||||||
|
settings['security']['logoutRequestSigned'] = current_app.config[
|
||||||
|
'SAML_SIGN_REQUEST']
|
||||||
|
settings['security']['logoutResponseSigned'] = current_app.config[
|
||||||
|
'SAML_SIGN_REQUEST']
|
||||||
|
settings['security']['nameIdEncrypted'] = False
|
||||||
|
settings['security']['signMetadata'] = True
|
||||||
|
settings['security']['wantAssertionsSigned'] = True
|
||||||
|
settings['security']['wantMessagesSigned'] = current_app.config.get(
|
||||||
|
'SAML_WANT_MESSAGE_SIGNED', True)
|
||||||
|
settings['security']['wantNameIdEncrypted'] = False
|
||||||
|
settings['contactPerson'] = {}
|
||||||
|
settings['contactPerson']['support'] = {}
|
||||||
|
settings['contactPerson']['support'][
|
||||||
|
'emailAddress'] = current_app.config['SAML_SP_CONTACT_NAME']
|
||||||
|
settings['contactPerson']['support']['givenName'] = current_app.config[
|
||||||
|
'SAML_SP_CONTACT_MAIL']
|
||||||
|
settings['contactPerson']['technical'] = {}
|
||||||
|
settings['contactPerson']['technical'][
|
||||||
|
'emailAddress'] = current_app.config['SAML_SP_CONTACT_MAIL']
|
||||||
|
settings['contactPerson']['technical'][
|
||||||
|
'givenName'] = current_app.config['SAML_SP_CONTACT_NAME']
|
||||||
|
settings['organization'] = {}
|
||||||
|
settings['organization']['en-US'] = {}
|
||||||
|
settings['organization']['en-US']['displayname'] = 'PowerDNS-Admin'
|
||||||
|
settings['organization']['en-US']['name'] = 'PowerDNS-Admin'
|
||||||
|
settings['organization']['en-US']['url'] = own_url
|
||||||
|
auth = self.OneLogin_Saml2_Auth(req, settings)
|
||||||
|
return auth
|
@ -1,5 +1,6 @@
|
|||||||
.length-break {
|
.length-break {
|
||||||
word-break: break-all !important;
|
word-break: break-all !important;
|
||||||
|
width: 70% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
table td {
|
table td {
|
||||||
|
@ -138,7 +138,7 @@
|
|||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
type: 'natural',
|
type: 'natural',
|
||||||
targets: [0, 5]
|
targets: [0, 4]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// hidden column so that we can add new records on top
|
// hidden column so that we can add new records on top
|
||||||
@ -148,7 +148,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
className: "length-break",
|
className: "length-break",
|
||||||
targets: [ 5 ]
|
targets: [ 4, 5 ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"orderFixed": [[8, 'asc']]
|
"orderFixed": [[8, 'asc']]
|
||||||
|
@ -62,10 +62,10 @@
|
|||||||
<td>
|
<td>
|
||||||
{{ record.ttl }}
|
{{ record.ttl }}
|
||||||
</td>
|
</td>
|
||||||
<td class="length-break">
|
<td>
|
||||||
{{ record.data }}
|
{{ record.data }}
|
||||||
</td>
|
</td>
|
||||||
<td class="length-break">
|
<td>
|
||||||
{{ record.comment }}
|
{{ record.comment }}
|
||||||
</td>
|
</td>
|
||||||
<td width="6%">
|
<td width="6%">
|
||||||
@ -123,7 +123,7 @@
|
|||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
type: 'natural',
|
type: 'natural',
|
||||||
targets: [0, 5]
|
targets: [0, 4]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// hidden column so that we can add new records on top
|
// hidden column so that we can add new records on top
|
||||||
@ -133,7 +133,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
className: "length-break",
|
className: "length-break",
|
||||||
targets: [ 5 ]
|
targets: [ 4, 5 ]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"orderFixed": [[8, 'asc']]
|
"orderFixed": [[8, 'asc']]
|
||||||
|
Loading…
Reference in New Issue
Block a user