diff --git a/.rubocop.yml b/.rubocop.yml index e224bf5..0b70a44 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,32 +1,32 @@ AllCops: DisplayCopNames: true Exclude: - 'lib/capistrano/**/*' - 'vendor/**/*' - 'unicorn.conf.rb' Metrics/LineLength: Max: 120 Metrics/AbcSize: - Max: 16 + Max: 18 Documentation: Enabled: false Style/NegatedIf: Enabled: false Style/Not: Enabled: false Style/WordArray: Enabled: false Style/TrailingComma: Enabled: false Style/BlockDelimiters: Enabled: false Style/TrailingBlankLines: Enabled: false Style/EmptyLinesAroundClassBody: Enabled: false Style/EmptyLinesAroundModuleBody: Enabled: false Lint/HandleExceptions: Enabled: false diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 5a70282..3b17d27 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,30 +1,48 @@ // 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'); } }); + var searchMembersGroup = $('#js-search-member').data('group'); + var searchMembers = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + 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 + }); }); diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb new file mode 100644 index 0000000..1099405 --- /dev/null +++ b/app/controllers/groups_controller.rb @@ -0,0 +1,47 @@ +class GroupsController < ApplicationController + before_action :authenticate_user! + + before_action :group, only: [:show, :create_member, :destroy_member, :search_member] + before_action :user, only: [:destroy_member] + + # GET /groups/1 + def show + end + + # POST /groups/1/members/ + def create_member + @user = User.find_by_email!(params[:email]) + membership = @group.memberships.find_or_create_by!(user_id: @user.id) + + redirect_to @group, notice: "#{membership.user.email} is now a member of #{@group.name}" + end + + # DELETE /groups/1/member/1 + def destroy_member + membership = @group.memberships.find_by!(user_id: user.id) + membership.destroy! + + redirect_to @group, notice: "#{membership.user.email} was successfully removed from #{@group.name}" + end + + def search_member + results = [] + + if params[:q].present? + uids = group.users.pluck(:id) + results = User + .where('email like ?', "#{params[:q]}%") + .where.not(id: uids) # Exclude group members + .limit(10) + end + + render json: results.map { |r| Hash[:id, r.id, :email, r.email] } + end + + private + + def user + @user ||= User.find(params[:user_id]) + end + +end diff --git a/app/helpers/breadcrumb_helper.rb b/app/helpers/breadcrumb_helper.rb index ef98682..0727ae7 100644 --- a/app/helpers/breadcrumb_helper.rb +++ b/app/helpers/breadcrumb_helper.rb @@ -1,56 +1,56 @@ module BreadcrumbHelper # Domain # Domain / group / example.com # Domain / group / example.com / ns1.example.com IN A # Domain / group / example.com / new def breadcrumbs(leaf) crumbs = generate_crumbs_for(leaf) crumbs.each { |c| # Last element should not be a link if c == crumbs.last || c[:link].nil? yield c[:name] else yield link_to(c[:name], c[:link]) end } end private # rubocop:disable all def generate_crumbs_for(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) + crumbs.push(name: crumb.name, link: group_path(crumb)) end end crumbs.push(name: 'Domains', link: '/') crumbs.reverse end # rubocop:enable all end diff --git a/app/views/domains/index.html.erb b/app/views/domains/index.html.erb index fa9426c..f6d30d5 100644 --- a/app/views/domains/index.html.erb +++ b/app/views/domains/index.html.erb @@ -1,31 +1,31 @@ <% @domains.group_by(&:group).each do |group, domains| %> - + <% domains.each do |domain| %> <% end %> <% end %>
<%= group.name %><%= link_to group.name, group_path(group) %> 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/groups/show.html.erb b/app/views/groups/show.html.erb new file mode 100644 index 0000000..8f0df1f --- /dev/null +++ b/app/views/groups/show.html.erb @@ -0,0 +1,24 @@ + + + + + + + + + + <% @group.memberships.includes(:user).each do |membership| %> + + + + + <% end %> + +
MembersControls
<%= membership.user.email %><%= " (you)" if current_user == membership.user %> + <%= link_to 'x', destroy_member_group_path(@group, membership.user_id), method: :delete %> +
+ +<%= bootstrap_form_tag(url: create_member_group_path(@group), layout: :inline) do |f| %> + <%= f.text_field :email, prepend: 'Member', hide_label: true, id: 'js-search-member', data: { group: @group.id } %> + <%= f.submit 'Add', class: 'btn btn-primary' %> +<% end %> diff --git a/config/routes.rb b/config/routes.rb index b98876d..7ef49f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,22 +1,31 @@ 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 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 } } # rubocop:enable Style/AlignHash end end end