diff options
Diffstat (limited to 'app')
23 files changed, 260 insertions, 39 deletions
diff --git a/app/assets/javascripts/users.js b/app/assets/javascripts/users.js index e6c2fcc..e0f1c9d 100644 --- a/app/assets/javascripts/users.js +++ b/app/assets/javascripts/users.js @@ -14,6 +14,7 @@ // var poll_users, + poll_identities, prevent_default, clear_errors, clear_field_errors, @@ -31,6 +32,12 @@ }).done(process); }; + poll_identities = function(query, process) { + return $.get("/identities.json", { + query: query + }).done(process); + }; + clear_errors = function() { return $('#messages').empty(); }; @@ -173,9 +180,8 @@ $('#update_login_and_password').submit(srp.update); $('#update_pgp_key').submit(prevent_default); $('#update_pgp_key').submit(update_user); - return $('#user-typeahead').typeahead({ - source: poll_users - }); + $('#user-typeahead').typeahead({ source: poll_users }); + $('#identity-typeahead').typeahead({ source: poll_identities }); }); }).call(this); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 28206b1..9cd3a55 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -11,6 +11,8 @@ // @import "bootstrap"; @import "bootstrap-responsive"; +// backport bootstrap 3.2 features +@import "backport"; // // LEAP web app specific overrides diff --git a/app/assets/stylesheets/backport.scss b/app/assets/stylesheets/backport.scss new file mode 100644 index 0000000..cadb035 --- /dev/null +++ b/app/assets/stylesheets/backport.scss @@ -0,0 +1,24 @@ +// +// Backporting styles from bootstrap 3.2 +// + + +// List options + +// Unstyled keeps list items block level, just removes default browser padding and list-style +.list-unstyled { + padding-left: 0; + list-style: none; +} + +// Inline turns list items into inline-block +.list-inline { + @extend .list-unstyled; + margin-left: -5px; + + > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; + } +} diff --git a/app/assets/stylesheets/leap.scss b/app/assets/stylesheets/leap.scss index 77104e5..3b7075c 100644 --- a/app/assets/stylesheets/leap.scss +++ b/app/assets/stylesheets/leap.scss @@ -145,6 +145,27 @@ input, textarea { } // +// IDENTITIES +// + +// Color code for the identity labels +.identity{ + &.main_email .label { + @extend .label-info + } + &.alias .label { + @extend .label-success + } + &.forward .label { + @extend .label-warning + } + &.disabled .label { + @extend .label-default + } +} + + +// // BORING DEFAULT MASTHEAD // diff --git a/app/controllers/identities_controller.rb b/app/controllers/identities_controller.rb new file mode 100644 index 0000000..8bd3b28 --- /dev/null +++ b/app/controllers/identities_controller.rb @@ -0,0 +1,34 @@ +class IdentitiesController < ApplicationController + + respond_to :html, :json + before_filter :require_login + before_filter :require_admin + before_filter :fetch_identity, only: :destroy + before_filter :protect_main_email, only: :destroy + + def index + if params[:query].present? + @identities = Identity.address_starts_with(params[:query]).limit(100) + else + @identities = [] + end + respond_with @identities.map(&:login).sort + end + + def destroy + @identity.destroy + redirect_to identities_path + end + + protected + def fetch_identity + @identity = Identity.find(params[:id]) + end + + def protect_main_email + if @identity.status == :main_email + flash[:error] = "You cannot destroy the main email. Remove or Rename the user instead." + redirect_to identities_path + end + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c8e09b6..5951413 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -12,12 +12,12 @@ class UsersController < UsersBaseController respond_to :html def index - if params[:query] + if params[:query].present? if @user = User.find_by_login(params[:query]) redirect_to @user return else - @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ) + @users = User.login_starts_with(params[:query]) end else @users = User.by_created_at.descending diff --git a/app/controllers/v1/users_controller.rb b/app/controllers/v1/users_controller.rb index 8897d01..006e6d8 100644 --- a/app/controllers/v1/users_controller.rb +++ b/app/controllers/v1/users_controller.rb @@ -11,7 +11,7 @@ module V1 # used for autocomplete for admins in the web ui def index if params[:query] - @users = User.by_login.startkey(params[:query]).endkey(params[:query].succ) + @users = User.login_starts_with(params[:query]) respond_with @users.map(&:login).sort else render :json => {'error' => 'query required', 'status' => :unprocessable_entity} diff --git a/app/helpers/link_helper.rb b/app/helpers/link_helper.rb index 55e392b..ddb063e 100644 --- a/app/helpers/link_helper.rb +++ b/app/helpers/link_helper.rb @@ -1,5 +1,30 @@ module LinkHelper + Action = Struct.new(:target, :verb, :options) do + def to_partial_path; 'common/action'; end + def label; options[:label]; end + def class; verb; end + def url + case verb + when :show, :destroy then target + when :edit, :new then [verb, target] + end + end + + def html_options + if verb == :destroy + {method: :delete} + end + end + end + + def actions(target) + target.actions.map do |action| + Action.new target, action, + label: t(".#{action}", cascade: true) + end + end + # # markup for bootstrap button # diff --git a/app/helpers/navigation_helper.rb b/app/helpers/navigation_helper.rb index 19cb934..779ce58 100644 --- a/app/helpers/navigation_helper.rb +++ b/app/helpers/navigation_helper.rb @@ -1,19 +1,19 @@ module NavigationHelper # - # used to create a side navigation link. + # Create a navigation link. # - # Signature is the same as link_to, except it accepts an :active value in the html_options + # Signature is the same as link_to, except... + # * it accepts an :active flag in the html_options + # * it accepts an :icon string in the html_options + # * the label (first arg) will be translated # def link_to_navigation(*args) - if args.last.is_a? Hash - html_options = args.pop.dup - active_class = html_options.delete(:active) ? 'active' : nil - html_options[:class] = [html_options[:class], active_class].join(' ') - args << html_options - else - active_class = nil - end + html_options = args.extract_options! || {} + active_class = extract_active_class!(html_options) + icon = extract_icon!(html_options) + args[0] = icon + translate(args[0], cascade: true) + args << html_options if html_options.present? content_tag :li, :class => active_class do link_to(*args) end @@ -59,6 +59,21 @@ module NavigationHelper private + def extract_active_class!(options) + active_class = options.delete(:active) ? 'active' : nil + options[:class] = [options[:class], active_class].compact.join(' ') + active_class + end + + def extract_icon!(options) + icon = options.delete(:icon) + if icon.present? + content_tag(:i, '', class: 'icon-'+ icon) + else + "" + end + end + def controller_string @controller_string ||= params[:controller].to_s.gsub(/^\//, '') end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb new file mode 100644 index 0000000..35b9358 --- /dev/null +++ b/app/helpers/search_helper.rb @@ -0,0 +1,9 @@ +module SearchHelper + + def search(target) + render 'common/search', path: url_for(target), + id: target.to_s.singularize, + submit_label: t('.search', cascade: true) + end + +end diff --git a/app/helpers/table_helper.rb b/app/helpers/table_helper.rb new file mode 100644 index 0000000..16a7019 --- /dev/null +++ b/app/helpers/table_helper.rb @@ -0,0 +1,13 @@ +module TableHelper + + # we do the translation here so the .key lookup is relative + # to the partial the helper was called from. + def table(content, columns) + render 'common/table', + content: content, + columns: columns, + headers: columns.map {|h| t(".#{h}", cascading: true) }, + none: t('.none', cascading: true) + end + +end diff --git a/app/models/identity.rb b/app/models/identity.rb index e7b5785..eb67b1b 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -46,6 +46,10 @@ class Identity < CouchRest::Model::Base end + def self.address_starts_with(query) + self.by_address.startkey(query).endkey(query + "\ufff0") + end + def self.for(user, attributes = {}) find_for(user, attributes) || build_for(user, attributes) end @@ -95,6 +99,18 @@ class Identity < CouchRest::Model::Base } end + def status + return :blocked if disabled? + case destination + when address + :main_email + when /@#{APP_CONFIG[:domain]}\Z/i, + :alias + else + :forward + end + end + def enabled? self.user_id end @@ -103,6 +119,14 @@ class Identity < CouchRest::Model::Base !enabled? end + def actions + if enabled? + [] # [:show, :edit] + else + [:destroy] + end + end + def disable self.destination = nil self.user_id = nil diff --git a/app/models/user.rb b/app/models/user.rb index f8b9ddc..6bc5841 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -42,6 +42,10 @@ class User < CouchRest::Model::Base view :by_created_at end # end of design + def self.login_starts_with(query) + self.by_login.startkey(query).endkey(query + "\ufff0") + end + def reload @identity = nil super diff --git a/app/views/common/_action.html.haml b/app/views/common/_action.html.haml new file mode 100644 index 0000000..71ffd96 --- /dev/null +++ b/app/views/common/_action.html.haml @@ -0,0 +1,2 @@ +%li.action{class: action.class} + =link_to action.label, action.url, action.html_options diff --git a/app/views/common/_navigation_item.html.haml b/app/views/common/_navigation_item.html.haml new file mode 100644 index 0000000..39b20d7 --- /dev/null +++ b/app/views/common/_navigation_item.html.haml @@ -0,0 +1,9 @@ +-# +-# A very simple navigation link. It takes a symbol, uses it for the +-# translation, path and determining if the link is active. +-# +-# For something more complex use link_to_navigation directly instead. +-# +- item = navigation_item.to_s += link_to_navigation ".#{item}", polymorphic_url(item), + active: controller?(item) diff --git a/app/views/common/_search.html.haml b/app/views/common/_search.html.haml new file mode 100644 index 0000000..9f14903 --- /dev/null +++ b/app/views/common/_search.html.haml @@ -0,0 +1,8 @@ += form_tag path, :method => :get, :class => "form-search" do + .input-append + = text_field_tag :query, params[:query], + id: "#{id}-typeahead", + class: "search-query", + autocomplete: :off + %button.btn{:type => :submit}= submit_label + diff --git a/app/views/common/_table.html.haml b/app/views/common/_table.html.haml new file mode 100644 index 0000000..44762da --- /dev/null +++ b/app/views/common/_table.html.haml @@ -0,0 +1,18 @@ +-# +-# A simple table with headers, content and a message if there is no +-# content. +-# You can use the table helper method to translate the headers and +-# error message. +-# +%table.table.table-striped.table-bordered + %thead + %tr + - headers.each do |header| + %th= header + %tbody + - if content.present? + = render content.all + - else + %tr + %td{:colspan=>headers.count}= none + diff --git a/app/views/identities/_identity.html.haml b/app/views/identities/_identity.html.haml new file mode 100644 index 0000000..7c4aeda --- /dev/null +++ b/app/views/identities/_identity.html.haml @@ -0,0 +1,7 @@ +%tr[identity]{class: identity.status} + %td + %span.label= t(".#{identity.status}", cascading: true) + %td= identity.login + %td= identity.destination + %td + %ul.list-inline= render actions(identity) diff --git a/app/views/identities/index.html.haml b/app/views/identities/index.html.haml new file mode 100644 index 0000000..e4f48eb --- /dev/null +++ b/app/views/identities/index.html.haml @@ -0,0 +1,5 @@ +-@show_navigation = false + += search :identities += table @identities, %w(type username destination actions) +-# = paginate @identities diff --git a/app/views/layouts/_admin_navigation_item.html.haml b/app/views/layouts/_admin_navigation_item.html.haml new file mode 100644 index 0000000..8dd8a39 --- /dev/null +++ b/app/views/layouts/_admin_navigation_item.html.haml @@ -0,0 +1,3 @@ +- item = admin_navigation_item.to_s +%li{:class => ("active" if controller?(item))} + = link_to t(".#{item}", cascade: true), path_for(item) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index e827f60..7b7999a 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,12 +1,8 @@ - if admin? %ul.nav.nav-tabs - = # this navigation isn't quite right. also, we will want to active for an identity controller once it is added. - %li{:class => ("active" if controller?('users', 'overviews') || params[:user_id])} - = link_to t(".users"), users_path - %li{:class => ("active" if controller?('tickets') && !params[:user_id])} - = link_to t(".tickets", cascade: true), tickets_path - %li - = link_to t(:logout), logout_path, :method => :delete + = render partial: 'common/navigation_item', + collection: [:users, :identities, :tickets] + = link_to_navigation :logout, logout_path, :method => :delete - if @user && @show_navigation .lead = @user.email_address diff --git a/app/views/layouts/_navigation.html.haml b/app/views/layouts/_navigation.html.haml index 94f71f7..dccba0c 100644 --- a/app/views/layouts/_navigation.html.haml +++ b/app/views/layouts/_navigation.html.haml @@ -1,7 +1,12 @@ %ul.nav.sidenav - = link_to_navigation t(:overview), @user, :active => (controller?(:users) && action?(:show)) - = link_to_navigation t(:account_settings), edit_user_path(@user), :active => (controller?(:users) && !action?(:show)) + = link_to_navigation :overview, @user, + active: (controller?(:users) && action?(:show)) + = link_to_navigation :account_settings, edit_user_path(@user), + active: (controller?(:users) && !action?(:show)) - # will want link for identity settings - = link_to_navigation t(".tickets"), auto_tickets_path, :active => controller?(:tickets) - = link_to_navigation t(:billing_settings), billing_top_link(@user), :active => controller?(:customer, :payments, :subscriptions, :credit_card_info) if APP_CONFIG[:billing] - = link_to_navigation t(:logout), logout_path, :method => :delete + = link_to_navigation ".tickets", auto_tickets_path, + active: controller?(:tickets) + - if APP_CONFIG[:billing] + = link_to_navigation :billing_settings, billing_top_link(@user), + active: controller?(:customer, :payments, :subscriptions, :credit_card_info) + = link_to_navigation :logout, logout_path, method: :delete diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml index fc1001e..3ed8835 100644 --- a/app/views/users/index.html.haml +++ b/app/views/users/index.html.haml @@ -1,13 +1,4 @@ - @show_navigation = false -= form_tag users_path, :method => :get, :class => "form-search" do - .input-append - = text_field_tag :query, params[:query], :id => 'user-typeahead', :class => "search-query", :autocomplete => :off - %button.btn{:type => :submit}= t(:search) - -%table.table.table-striped - %tr - %th= t(:username) - %th= t(:created) - %th= t(:updated) - = render @users.all += search :users += table @users, %w(username, created, updated) |