Page MenuHomeGRNET

No OneTemporary

File Metadata

Created
Sun, May 17, 10:50 AM
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 805a5e4..ff601b5 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -1,107 +1,129 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery.min
//= require jquery_ujs
//= require bootstrap.min
//= require typeahead.bundle.min
//= require jquery.dataTables.min
//= require dataTables.bootstrap.min
//= require bootstrap-editable.min
//= require_tree .
$(function() {
+ // Setup X-Editable
+ $.fn.editable.defaults.mode = 'inline';
+ $.fn.editable.defaults.ajaxOptions = {
+ type: 'put',
+ dataType: 'json'
+ };
+
+ function editable_record_success(response, _value) {
+ // Visual hint for the changed serial
+ if(response.serial) {
+ $('tr.soa .soa-serial').text(response.serial);
+ $('tr.soa .soa-serial').fadeOut(100).fadeIn(500);
+ }
+
+ // Render the value returned by the server as
+ // there are cases where the value is normalized (e.x. name)
+ return { newValue: response.value };
+ }
+
+ $('table .editable').editable({
+ success: editable_record_success
+ });
// Show priority on MX/SRV record only
$('#record_type').change(function() {
if ($(this).val() == 'MX') { // MX, default priority 10
$('#record_prio.autohide').parents('div.form-group').removeClass('hidden');
$('#record_prio.autodisable').prop('disabled', false);
$('#record_prio').val('10');
} else if ($(this).val() == 'SRV') { // SRV
$('#record_prio').val('');
$('#record_prio.autohide').parents('div.form-group').removeClass('hidden');
$('#record_prio.autodisable').prop('disabled', false);
} else {
$('#record_prio').val('');
$('#record_prio.autohide').parents('div.form-group').addClass('hidden');
$('#record_prio.autodisable').prop('disabled', true);
}
});
// Show master only on SLAVE domains
$('#domain_type').change(function() {
if ($(this).val() == 'SLAVE') {
$('#domain_master').parents('div.form-group').removeClass('hidden');
} else {
$('#domain_master').parents('div.form-group').addClass('hidden');
}
});
// Disable DNSSEC options
$('#domain_dnssec').change(function() {
if ($(this).val()== 'true') {
$("#dnssec_fieldset").prop('disabled', false)
} else {
$("#dnssec_fieldset").prop('disabled', true);
}
});
var searchMembersGroup = $('#js-search-member').data('group');
var searchMembers = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('email'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
identify: function(obj) { return obj.id; },
remote: {
url: '/groups/' + searchMembersGroup + '/search_member.json?q=%QUERY',
wildcard: '%QUERY'
}
});
$('#js-search-member').typeahead({
hint: true,
minLength: 2
}, {
name: 'members',
display: 'email',
source: searchMembers
});
// Highlighter helper
//
// Applies 'highlight' class to the element followed by 'hl-' prefix
function highlighter() {
$('.highlight').removeClass('highlight');
if (!window.location.hash)
return;
if (window.location.hash.indexOf('#hl-') == 0) {
var id = window.location.hash.slice('hl-'.length + 1);
$('#' + id).addClass('highlight');
}
}
$(window).bind('hashchange', highlighter);
highlighter();
$('table#domains').DataTable({
paging: false,
columnDefs: [{
targets: 'no-order-and-search',
orderable: false,
searchable: false
}],
});
});
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index fe4a854..13df1b8 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,70 +1,70 @@
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
attr_writer :breadcrumb
helper_method :admin?
helper_method :dnssec?
def admin?
return false if params.key?('user')
return false if current_user.nil?
@admin_count ||= begin
current_user
.groups
.where(name: WebDNS.settings[:admin_group]).count
end
@admin_count != 0
end
def admin_only!
return if admin?
redirect_to root_path, alert: 'Admin only area!'
end
def dnssec?
WebDNS.settings[:dnssec]
end
private
def group
@group ||= edit_group_scope.find(params[:group_id] || params[:id])
end
def domain
@domain ||= edit_domain_scope.find(params[:domain_id] || params[:id])
end
def record
- @record ||= record_scope.find(params[:record_id] || params[:id])
+ @record ||= record_scope.find(params[:record_id] || params[:id] || params[:pk])
end
def show_group_scope
@show_group_scope ||= current_user.groups
end
def edit_group_scope
@edit_group_scope ||= admin? ? Group.all : show_group_scope
end
def show_domain_scope
@show_domain_scope ||= Domain.where(group: show_group_scope)
end
def edit_domain_scope
@edit_domain_scope ||= admin? ? Domain.all : Domain.where(group: show_group_scope)
end
def record_scope
@record_scope ||= domain.records
end
def notification
Notification.instance if WebDNS.settings[:notifications]
end
end
diff --git a/app/controllers/records_controller.rb b/app/controllers/records_controller.rb
index 4baf816..7bd8565 100644
--- a/app/controllers/records_controller.rb
+++ b/app/controllers/records_controller.rb
@@ -1,79 +1,101 @@
class RecordsController < ApplicationController
before_action :authenticate_user!
before_action :domain, except: [:search]
- before_action :record, only: [:edit, :update, :destroy]
+ before_action :editable_transform_params, only: [:editable]
+ before_action :record, only: [:edit, :update, :editable, :destroy]
# GET /records/new
def new
@record = domain.records.build
end
# GET /records/1/edit
def edit
end
# POST /records
def create
@record = domain.records.new(new_record_params)
if @record.save
notify_record(@record, :create)
redirect_to domain, notice: 'Record was successfully created.'
else
flash[:alert] = 'There were some errors creating the record!'
render :new
end
end
# PATCH/PUT /records/1
def update
if @record.update(edit_record_params)
notify_record(@record, :update)
redirect_to domain, notice: 'Record was successfully updated.'
else
render :edit
end
end
+ def editable
+ if @record.update(edit_record_params)
+ notify_record(@record, :update)
+ response = {
+ value: @record.read_attribute(@attr),
+ serial: @domain.soa(true).serial,
+ record: @record.as_json
+ }
+
+ render json: response
+ else
+ render text: @record.errors[@attr].join(', '), status: 400
+ end
+ end
+
# DELETE /records/1
def destroy
@record.destroy
notify_record(@record, :destroy)
redirect_to domain, notice: 'Record was successfully destroyed.'
end
# GET /search
def search
@records = Record
.where(domain: show_domain_scope)
.includes(:domain)
.search(params[:q]) # scope by domain
@records = Record.smart_order(@records)
end
private
+ # Modify params to use standard Rails patterns
+ def editable_transform_params
+ @attr = params[:name]
+ params[:record] = { params[:name] => params[:value] }
+ end
+
def edit_record_params
if @record.type == 'SOA'
permitted = [:contact, :serial, :refresh, :retry, :expire, :nx]
else
permitted = [:name, :content, :ttl, :prio, :disabled]
end
params.require(:record).permit(*permitted).tap { |r|
r[:drop_privileges] = true if not admin?
}
end
def new_record_params
params.require(:record).permit(:name, :content, :ttl, :type, :prio).tap { |r|
r[:drop_privileges] = true if not admin?
}
end
def notify_record(*args)
notification.notify_record(current_user, *args) if WebDNS.settings[:notifications]
end
end
diff --git a/app/helpers/records_helper.rb b/app/helpers/records_helper.rb
index e0f974c..2fbd5fd 100644
--- a/app/helpers/records_helper.rb
+++ b/app/helpers/records_helper.rb
@@ -1,22 +1,40 @@
module RecordsHelper
# Smart suffix for records
#
# On forward zones returns the zone name.
# On reverse zones returns the zone name but also tries to infer the subnet.
#
# Returns a smart suffix string.
def name_field_append(record)
return ".#{record.domain.name}" if not record.domain.reverse?
".#{record.domain.name} (#{record.domain.subnet})"
end
# List of record types usually used for that domain type
def record_types_for_domain(domain)
return Record.reverse_records if domain.reverse?
return Record.enum_records if domain.enum?
Record.forward_records
end
+
+ def editable_record_attr(rec, attr)
+ return soa_content(rec) if rec.type == 'SOA' && attr == :content
+ return rec.read_attribute(attr) if rec.type == 'SOA' || !can_edit?(rec)
+
+ link_to(
+ rec.read_attribute(attr),
+ "#edit-record-#{rec.id}-#{attr}",
+ class: 'editable',
+ data: { pk: rec.id, name: attr, type: 'text', url: editable_domain_records_path(rec.domain_id) }
+ )
+ end
+
+ def soa_content(rec)
+ SOA::SOA_FIELDS.map { |attr|
+ "<span class='soa-#{attr}'>#{rec.send(attr)}</span>"
+ }.join(' ').html_safe
+ end
end
diff --git a/app/views/domains/show.html.erb b/app/views/domains/show.html.erb
index 64000d7..e27f861 100644
--- a/app/views/domains/show.html.erb
+++ b/app/views/domains/show.html.erb
@@ -1,48 +1,52 @@
<% content_for :more_breadcrumbs do %>
<li>
<%= link_to_edit edit_domain_path(@domain) %>
</li>
<% end if admin? %>
<%= render 'records/inline_form' %>
<table class="table table-striped table-hover">
<thead>
<tr>
<th colspan="6">Records</th>
<th colspan="3"><%= 'Controls' if !@domain.slave? %></th>
</tr>
</thead>
<tbody>
<% Record.smart_order(@domain.records).each do |record| %>
- <tr id="record-<%= record.id %>" class="<%= record.disabled? ? 'warning' : '' %>">
- <td><%= record.name %></td>
- <td><%= record.ttl %></td>
- <td>IN</td>
- <td><%= record.type %></td>
- <td><%= record.supports_prio? ? record.prio : '' %></td>
- <td><%= record.content %></td>
- <% if record.classless_delegation? %>
- <td/>
- <td/>
- <td><%= link_to_destroy [@domain, record], method: :delete, data: { confirm: 'Are you sure?' } %></td>
- <% elsif can_edit?(record) %>
- <td>
- <% if record.disabled? %>
- <%= link_to_enable enable_domain_record_path(@domain, record), method: :put %>
- <% else %>
- <%= link_to_disable disable_domain_record_path(@domain, record), method: :put %>
- <% end %>
- </td>
- <td><%= link_to_edit edit_domain_record_path(@domain, record) %></td>
- <td><%= link_to_destroy [@domain, record], method: :delete, data: { confirm: 'Are you sure?' } %></td>
- <% else %>
- <td/>
- <td/>
- <td/>
- <% end %>
- </tr>
+ <tr id="record-<%= record.id %>" class="<%= record.type.downcase %> <%= record.disabled? ? 'warning' : '' %>">
+ <td><%= editable_record_attr(record, :name) %></td>
+ <td><%= record.ttl %></td>
+ <td>IN</td>
+ <td><%= record.type %></td>
+ <% if record.supports_prio? %>
+ <td><%= editable_record_attr(record, :prio) %></td>
+ <% else %>
+ <td>&nbsp;</td>
+ <% end %>
+ <td><%= editable_record_attr(record, :content) %></td>
+ <% if record.classless_delegation? %>
+ <td/>
+ <td/>
+ <td><%= link_to_destroy [@domain, record], method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <% elsif can_edit?(record) %>
+ <td>
+ <% if record.disabled? %>
+ <%= link_to_enable enable_domain_record_path(@domain, record), method: :put %>
+ <% else %>
+ <%= link_to_disable disable_domain_record_path(@domain, record), method: :put %>
+ <% end %>
+ </td>
+ <td><%= link_to_edit edit_domain_record_path(@domain, record) %></td>
+ <td><%= link_to_destroy [@domain, record], method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <% else %>
+ <td/>
+ <td/>
+ <td/>
+ <% end %>
+ </tr>
<% end %>
</tbody>
</table>
diff --git a/config/routes.rb b/config/routes.rb
index 94031d9..bedbe4f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,52 +1,54 @@
Rails.application.routes.draw do
# Override devise user removal
devise_scope :users do
delete :users, to: redirect('/')
end
devise_for :users
root to: redirect('/domains')
resources :groups, only: [:show] do
get :search_member,
to: 'groups#search_member', on: :member
post :members,
to: 'groups#create_member', as: :create_member, on: :member
delete 'member/:user_id',
to: 'groups#destroy_member', as: :destroy_member, on: :member
end
resources :domains do
get :edit_dnssec, to: 'domains#edit_dnssec', on: :member
resources :records, except: [:index, :show] do
# Reuse records#update instead of introducing new controller actions
#
# rubocop:disable Style/AlignHash
put :disable, to: 'records#update', on: :member,
defaults: { record: { disabled: true } }
put :enable, to: 'records#update', on: :member,
defaults: { record: { disabled: false } }
+
+ put :editable, to: 'records#editable', on: :collection
# rubocop:enable Style/AlignHash
end
end
get '/records/search', to: 'records#search'
# Admin
namespace :admin do
root to: redirect('/admin/groups')
resources :groups, except: [:show]
resources :jobs, only: [:index, :destroy]
resources :users, only: [] do
get :orphans, to: 'users#orphans', on: :collection
put :update_groups, to: 'users#update_groups', on: :collection
end
end
# Private
put 'private/replace_ds', to: 'private#replace_ds'
put 'private/trigger_event', to: 'private#trigger_event'
end

Event Timeline