diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1678eaa..8761224 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,25 +1,34 @@ 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 private + def group + @group ||= domain.group + end + def domain @domain ||= domain_scope.find(params[:domain_id] || params[:id]) end def record @record ||= record_scope.find(params[:record_id] || params[:id]) end + + def group_scope + @group_scope ||= Group.all + end + def domain_scope - @domain_scope ||= Domain.all + @domain_scope ||= Domain.where(group: group_scope) end def record_scope @record_scope ||= domain.records end end diff --git a/app/controllers/domains_controller.rb b/app/controllers/domains_controller.rb index 9c1e02d..35be16b 100644 --- a/app/controllers/domains_controller.rb +++ b/app/controllers/domains_controller.rb @@ -1,53 +1,60 @@ class DomainsController < ApplicationController + before_action :group_scope + before_action :domain, only: [:show, :edit, :update, :destroy] + before_action :group, only: [:show, :edit, :update, :destroy] # GET /domains def index @domains = domain_scope.all end # GET /domains/1 def show end # GET /domains/new def new @domain = Domain.new end # GET /domains/1/edit def edit end # POST /domains def create @domain = Domain.new(domain_params) if @domain.save redirect_to @domain, notice: "#{@domain.name} was successfully created." else render :new end end # PATCH/PUT /domains/1 def update if @domain.update(domain_params) redirect_to @domain, notice: "#{@domain.name} was successfully updated." else render :edit end end # DELETE /domains/1 def destroy @domain.destroy redirect_to domains_url, notice: "#{@domain.name} was successfully destroyed." end private def domain_params - params.require(:domain).permit(:name, :type) + params.require(:domain).tap { |d| + # Make sure group id is permitted (belongs to group_scope) + d[:group_id] = group_scope.find_by_id(d[:group_id]).try(:id) + }.permit(:name, :type, :group_id) end + end diff --git a/app/helpers/breadcrumb_helper.rb b/app/helpers/breadcrumb_helper.rb index d871ac8..7a5a683 100644 --- a/app/helpers/breadcrumb_helper.rb +++ b/app/helpers/breadcrumb_helper.rb @@ -1,44 +1,47 @@ module BreadcrumbHelper # Domain - # Domain / example.com - # Domain / example.com / ns1.example.com IN A - # Domain / example.com / new + # Domain / group / example.com + # Domain / group / example.com / ns1.example.com IN A + # Domain / group / example.com / new def breadcrumbs(leaf) stack = [] crumbs = [] stack.push leaf if leaf while crumb = stack.pop # rubocop:disable Lint/AssignmentInCondition case crumb when Record if crumb.persisted? crumbs.push( name: "#{crumb.name} IN #{crumb.type}", link: domain_record_path(crumb.domain_id, crumb)) else crumbs.push(name: :new) end stack.push crumb.domain when Domain if crumb.persisted? crumbs.push(name: crumb.name, link: domain_path(crumb)) else crumbs.push(name: :new) end + stack.push crumb.group + when Group + crumbs.push(name: crumb.name) end end crumbs.push(name: 'Domains', link: '/') crumbs.reverse! crumbs.each { |c| # Last element should not be a link - if c == crumbs.last + if c == crumbs.last || c[:link].nil? yield c[:name] else yield link_to(c[:name], c[:link]) end } end end diff --git a/app/models/domain.rb b/app/models/domain.rb index 512f582..cec8552 100644 --- a/app/models/domain.rb +++ b/app/models/domain.rb @@ -1,73 +1,75 @@ class Domain < ActiveRecord::Base self.inheritance_column = :nx def self.domain_types [ 'NATIVE', 'MASTER', 'SLAVE', ] end + belongs_to :group has_many :records has_one :soa, class_name: SOA + validates :group_id, presence: true validates :name, uniqueness: true, presence: true validates :type, presence: true, inclusion: { in: domain_types } after_create :generate_soa attr_writer :serial_strategy def serial_strategy @serial_strategy ||= WebDNS.settings[:serial_strategy] end def reverse? name.end_with?('.in-addr.arpa') || name.end_with?('.ip6.arpa') end # Compute subnet for reverse records def subnet return if not reverse? if name.end_with?('.in-addr.arpa') subnet_v4 elsif name.end_with?('.ip6.arpa') subnet_v6 end end private def subnet_v4 # get ip octets (remove .in-addr.arpa) octets = name.split('.')[0...-2].reverse return if octets.any? { |_| false } mask = 8 * octets.size octets += [0, 0, 0, 0] ip = IPAddr.new octets[0, 4].join('.') [ip, mask].join('/') end def subnet_v6 nibbles = name.split('.')[0...-2].reverse return if nibbles.any? { |_| false } mask = 4 * nibbles.size nibbles += [0] * 32 ip = IPAddr.new nibbles[0, 32].in_groups_of(4).map(&:join).join(':') [ip, mask].join('/') end # Hooks def generate_soa soa_record = SOA.new(domain: self) soa_record.save end end diff --git a/app/models/group.rb b/app/models/group.rb new file mode 100644 index 0000000..e01ea69 --- /dev/null +++ b/app/models/group.rb @@ -0,0 +1,5 @@ +class Group < ActiveRecord::Base + has_many :domains + + validates :name, presence: true, uniqueness: true +end diff --git a/app/views/domains/_form.html.erb b/app/views/domains/_form.html.erb index 3060adf..30a9cfa 100644 --- a/app/views/domains/_form.html.erb +++ b/app/views/domains/_form.html.erb @@ -1,5 +1,6 @@ <%= bootstrap_form_for(@domain, layout: :horizontal, label_col: 'col-sm-2', control_col: 'col-sm-4') do |f| %> <%= f.text_field :name %> + <%= f.collection_select :group_id, @group_scope, :id, :name %> <%= f.select :type, Domain.domain_types %> <%= f.submit 'Save', class: 'btn btn-primary col-sm-offset-2' %> <% end %> diff --git a/app/views/domains/index.html.erb b/app/views/domains/index.html.erb index 01071a9..fa9426c 100644 --- a/app/views/domains/index.html.erb +++ b/app/views/domains/index.html.erb @@ -1,30 +1,31 @@ - - - - - - <% @domains.each do |domain| %> - - - - - - + <% @domains.group_by(&:group).each do |group, domains| %> + + + + + <% domains.each do |domain| %> + + + + + + + <% end %> <% end %>
DomainTypeControls
<%= link_to domain.name, domain %> - <% if domain.reverse? %> - - <% else %> - - <% end %> - <%= link_to 'Edit', edit_domain_path(domain) %><%= link_to 'Destroy', domain, method: :delete, data: { confirm: 'Are you sure?' } %>
<%= group.name %>Controls
<%= link_to domain.name, domain %> + <% if domain.reverse? %> + + <% else %> + + <% end %> + <%= link_to 'Edit', edit_domain_path(domain) %><%= link_to 'Destroy', domain, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Domain »'.html_safe, new_domain_path, class: 'btn btn-lg btn-primary' %>

diff --git a/app/views/shared/_breadcrumbs.html.erb b/app/views/shared/_breadcrumbs.html.erb index 690003b..6d1dd00 100644 --- a/app/views/shared/_breadcrumbs.html.erb +++ b/app/views/shared/_breadcrumbs.html.erb @@ -1,5 +1,5 @@ diff --git a/db/migrate/20151031184819_create_groups.rb b/db/migrate/20151031184819_create_groups.rb new file mode 100644 index 0000000..8841b72 --- /dev/null +++ b/db/migrate/20151031184819_create_groups.rb @@ -0,0 +1,14 @@ +class CreateGroups < ActiveRecord::Migration + def change + create_table :groups do |t| + t.string :name + t.boolean :disabled, default: false + + t.timestamps + end + add_index :groups, :name, unique: true + + add_column :domains, :group_id, :integer + add_index :domains, :group_id + end +end diff --git a/db/structure.sql b/db/structure.sql index 5ea5a9b..daf63aa 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1,180 +1,202 @@ -- MySQL dump 10.15 Distrib 10.0.20-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: webns -- ------------------------------------------------------ -- Server version 10.0.20-MariaDB-3 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `comments` -- DROP TABLE IF EXISTS `comments`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `comments` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `type` varchar(10) NOT NULL, `modified_at` int(11) NOT NULL, `account` varchar(40) NOT NULL, `comment` mediumtext NOT NULL, PRIMARY KEY (`id`), KEY `comments_domain_id_idx` (`domain_id`), KEY `comments_name_type_idx` (`name`,`type`), KEY `comments_order_idx` (`domain_id`,`modified_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `cryptokeys` -- DROP TABLE IF EXISTS `cryptokeys`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cryptokeys` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) NOT NULL, `flags` int(11) NOT NULL, `active` tinyint(1) DEFAULT NULL, `content` text, PRIMARY KEY (`id`), KEY `domainidindex` (`domain_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `domainmetadata` -- DROP TABLE IF EXISTS `domainmetadata`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domainmetadata` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) NOT NULL, `kind` varchar(32) DEFAULT NULL, `content` text, PRIMARY KEY (`id`), KEY `domainmetadata_idx` (`domain_id`,`kind`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `domains` -- DROP TABLE IF EXISTS `domains`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domains` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `master` varchar(128) DEFAULT NULL, `last_check` int(11) DEFAULT NULL, `type` varchar(6) NOT NULL, `notified_serial` int(11) DEFAULT NULL, `account` varchar(40) DEFAULT NULL, + `group_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `name_index` (`name`) + UNIQUE KEY `name_index` (`name`), + KEY `index_domains_on_group_id` (`group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `groups` +-- + +DROP TABLE IF EXISTS `groups`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `groups` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `disabled` tinyint(1) DEFAULT '0', + `created_at` datetime DEFAULT NULL, + `updated_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `index_groups_on_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `records` -- DROP TABLE IF EXISTS `records`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `records` ( `id` int(11) NOT NULL AUTO_INCREMENT, `domain_id` int(11) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `type` varchar(10) DEFAULT NULL, `content` mediumtext, `ttl` int(11) DEFAULT NULL, `prio` int(11) DEFAULT NULL, `change_date` int(11) DEFAULT NULL, `disabled` tinyint(1) DEFAULT '0', `ordername` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `auth` tinyint(1) DEFAULT '1', PRIMARY KEY (`id`), KEY `nametype_index` (`name`,`type`), KEY `domain_id` (`domain_id`), KEY `recordorder` (`domain_id`,`ordername`), CONSTRAINT `records_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domains` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `schema_migrations` -- DROP TABLE IF EXISTS `schema_migrations`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `schema_migrations` ( `version` varchar(255) NOT NULL, UNIQUE KEY `unique_schema_migrations` (`version`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `supermasters` -- DROP TABLE IF EXISTS `supermasters`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `supermasters` ( `ip` varchar(64) NOT NULL, `nameserver` varchar(255) NOT NULL, `account` varchar(40) NOT NULL, PRIMARY KEY (`ip`,`nameserver`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `tsigkeys` -- DROP TABLE IF EXISTS `tsigkeys`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tsigkeys` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `algorithm` varchar(50) DEFAULT NULL, `secret` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `namealgoindex` (`name`,`algorithm`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2015-10-28 11:37:25 +-- Dump completed on 2015-10-31 20:55:16 INSERT INTO schema_migrations (version) VALUES ('20151028123326'); INSERT INTO schema_migrations (version) VALUES ('20151028123327'); +INSERT INTO schema_migrations (version) VALUES ('20151031184819'); + diff --git a/test/factories/domain.rb b/test/factories/domain.rb index aa7eba9..e69573d 100644 --- a/test/factories/domain.rb +++ b/test/factories/domain.rb @@ -1,22 +1,24 @@ FactoryGirl.define do sequence(:domain) { |n| "example#{n}.com" } factory :domain do + group name { generate(:domain) } serial_strategy Strategies::Date type 'NATIVE' end factory :date_domain, class: Domain do + group name { generate(:domain) } serial_strategy Strategies::Date type 'NATIVE' end factory :v4_arpa_domain, parent: :domain do name '2.0.192.in-addr.arpa' end factory :v6_arpa_domain, parent: :domain do name '8.b.d.0.1.0.0.2.ip6.arpa' end end diff --git a/test/factories/group.rb b/test/factories/group.rb new file mode 100644 index 0000000..cd92dc8 --- /dev/null +++ b/test/factories/group.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :group do + sequence(:name) { |n| "group-#{n}" } + end +end diff --git a/test/models/group_test.rb b/test/models/group_test.rb new file mode 100644 index 0000000..eb3d7a1 --- /dev/null +++ b/test/models/group_test.rb @@ -0,0 +1,14 @@ +require 'test_helper' + +class GroupTest < ActiveSupport::TestCase + + setup do + @group = build(:group) + end + + test 'save' do + @group.save + + assert_empty @group.errors + end +end