diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 137bdd4..5a70282 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,17 +1,30 @@ // 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_tree . + +$(function() { + + // Show priority on MX record only + $('#record_type').change(function() { + if ($(this).val() == 'MX') { + $('#record_prio').parents('div.form-group').removeClass('hidden'); + } else { + $('#record_prio').parents('div.form-group').addClass('hidden'); + } + }); + +}); diff --git a/app/controllers/records_controller.rb b/app/controllers/records_controller.rb index 7ce17fa..0d2881e 100644 --- a/app/controllers/records_controller.rb +++ b/app/controllers/records_controller.rb @@ -1,56 +1,56 @@ class RecordsController < ApplicationController before_action :set_domain before_action :set_record, only: [:edit, :update, :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 redirect_to @domain, notice: 'Record was successfully created.' else render :new end end # PATCH/PUT /records/1 def update if @record.update(edit_record_params) redirect_to @domain, notice: 'Record was successfully updated.' else render :edit end end # DELETE /records/1 def destroy @record.destroy redirect_to @domain, notice: 'Record was successfully destroyed.' end private def set_record @record = @domain.records.find(params[:id]) end def set_domain @domain = Domain.find(params[:domain_id]) end def edit_record_params - params.require(:record).permit(:name, :content) + params.require(:record).permit(:name, :content, :prio) end def new_record_params - params.require(:record).permit(:name, :content, :type) + params.require(:record).permit(:name, :content, :type, :prio) end end diff --git a/app/models/mx.rb b/app/models/mx.rb new file mode 100644 index 0000000..15855a6 --- /dev/null +++ b/app/models/mx.rb @@ -0,0 +1,9 @@ +class MX < Record + validates :content, presence: true, hostname: true + validates :name, presence: true, hostname: true + validates :prio, presence: true, prio: true + + def supports_prio? + true + end +end diff --git a/app/models/record.rb b/app/models/record.rb index faf9fdd..46b6829 100644 --- a/app/models/record.rb +++ b/app/models/record.rb @@ -1,54 +1,58 @@ class Record < ActiveRecord::Base belongs_to :domain def self.record_types [ 'SOA', 'NS', 'CNAME', 'A', 'AAAA', 'MX', 'TXT', 'SPF', 'SRV', 'SSHFP', 'PTR', ] end validates :name, presence: true validates :type, inclusion: { in: record_types } before_validation :set_name after_save :update_zone_serial after_destroy :update_zone_serial def short return '' if name == domain.name return '' if name.blank? File.basename(name, ".#{domain.name}") end + def supports_prio? + false + end + # Create record specific urls for all record types # # Overrides default rails STI def self.model_name return super if self == Record Record.model_name end private # Hooks # Powerdns expects full domain names def set_name self.name = domain.name if name.blank? self.name = "#{name}.#{domain.name}" if not name.end_with?(domain.name) end def update_zone_serial # SOA records handle serial themselves return true if type == 'SOA' domain.soa.bump_serial! end end diff --git a/app/views/domains/show.html.erb b/app/views/domains/show.html.erb index b1cef23..42a3499 100644 --- a/app/views/domains/show.html.erb +++ b/app/views/domains/show.html.erb @@ -1,23 +1,24 @@ - + <% @domain.records.each do |record| %> + <% end %>
RecordsRecords Controls
<%= record.name %> IN <%= record.type %><%= record.supports_prio? ? record.prio : '' %> <%= record.content %> <%= link_to 'Edit', edit_domain_record_path(@domain, record) %> <%= link_to 'Remove', [@domain, record], method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Record', new_domain_record_path(@domain) %>

diff --git a/app/views/records/_form.html.erb b/app/views/records/_form.html.erb index 75cf980..6388a97 100644 --- a/app/views/records/_form.html.erb +++ b/app/views/records/_form.html.erb @@ -1,13 +1,14 @@ <%= bootstrap_form_for([@domain, @record], layout: :horizontal, label_col: 'col-sm-2', control_col: 'col-sm-4') do |f| %> <%= f.text_field :name, value: @record.short, label: 'Record', append: ".#{@domain.name}" %> <% if @record.persisted? %> <%= f.static_control :type %> <% else %> <%= f.select :type, Record.record_types %> <% end %> + <%= f.text_field :prio, placeholder: 10, wrapper_class: 'hidden' %> <%= f.text_field :content %> <%= f.submit 'Save', class: 'btn btn-primary col-sm-offset-2' %> <% end %> diff --git a/lib/prio_validator.rb b/lib/prio_validator.rb new file mode 100644 index 0000000..f2395b6 --- /dev/null +++ b/lib/prio_validator.rb @@ -0,0 +1,18 @@ +# Prio should be between [0, 65535] +class PrioValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + # Rails autocasts integer fields to 0 if a non-numerical value is passed + # we override that by using th *_before_type_cast helper method + before_cast = :"#{attribute}_before_type_cast" + raw_value = record.send(before_cast) if record.respond_to?(before_cast) + raw_value ||= value + + val = Integer(raw_value) + if val < 0 || val > 65_535 + record.errors[attribute] << 'is not a valid DNS priority [0, 65535]' + end + + rescue ArgumentError, TypeError + record.errors[attribute] << 'is not a valid DNS priority [0, 65535]' + end +end diff --git a/test/factories/mx.rb b/test/factories/mx.rb new file mode 100644 index 0000000..204ac0d --- /dev/null +++ b/test/factories/mx.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :mx, class: 'MX' do + domain + name '' + prio 10 + content 'ns.example.com' + end +end diff --git a/test/models/mx_test.rb b/test/models/mx_test.rb new file mode 100644 index 0000000..68f8d6e --- /dev/null +++ b/test/models/mx_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' + +class MXTest < ActiveSupport::TestCase + + test 'saves' do + rec = build(:mx) + rec.valid? + assert_empty rec.errors + assert rec.save + end + + test 'supports prio' do + rec = build(:mx) + assert rec.supports_prio?, 'supports prio' + end + + [ + 0, + 10, + 65_535, + ].each { |prio| + test "valid prio #{prio}" do + rec = build(:mx, prio: prio) + rec.valid? + assert_empty rec.errors[:prio], "#{prio} should be valid!" + end + } + + [ + -10, + 65_535 + 1, + 'str', + ].each { |prio| + test "invalid prio #{prio}" do + rec = build(:mx, prio: prio) + rec.valid? + assert_not_empty rec.errors[:prio], "#{prio} should be invalid!" + end + } + +end