Page Menu
Home
GRNET
Search
Configure Global Search
Log In
Files
F1969834
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sun, May 17, 10:50 AM
Size
15 KB
Mime Type
text/x-diff
Expires
Tue, May 19, 10:50 AM (1 d, 2 h)
Engine
blob
Format
Raw Data
Handle
385280
Attached To
rWEBDNS WebDNS (edet4)
View Options
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> </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
Log In to Comment