From 24d108e15c38ca572d5339a39cb110d9067c0b3d Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 4 Jul 2014 15:36:41 +0200 Subject: backport bootstraps 3.2s list-inline list-unstyled comes for free --- app/assets/stylesheets/application.scss | 2 ++ app/assets/stylesheets/backport.scss | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 app/assets/stylesheets/backport.scss (limited to 'app') 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; + } +} -- cgit v1.2.3 From 87e9ccbcdf4f99dd898b0715750092a27fff7e94 Mon Sep 17 00:00:00 2001 From: Azul Date: Fri, 4 Jul 2014 15:40:54 +0200 Subject: Enable unblocking handles in identities tab There's an identities tab now for admins that will allow unblocking blocked handles. It should be easy to expand for aliases and forwards and other types of actions such as editing. --- app/assets/stylesheets/leap.scss | 21 ++++++++++++++++ app/controllers/identities_controller.rb | 28 ++++++++++++++++++++++ app/helpers/link_helper.rb | 25 +++++++++++++++++++ app/helpers/table_helper.rb | 13 ++++++++++ app/models/identity.rb | 20 ++++++++++++++++ app/views/common/_action.html.haml | 2 ++ app/views/common/_navigation_item.html.haml | 3 +++ app/views/common/_table.html.haml | 18 ++++++++++++++ app/views/identities/_identity.html.haml | 7 ++++++ app/views/identities/index.html.haml | 4 ++++ app/views/layouts/_admin_navigation_item.html.haml | 3 +++ app/views/layouts/_header.html.haml | 6 +---- 12 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 app/controllers/identities_controller.rb create mode 100644 app/helpers/table_helper.rb create mode 100644 app/views/common/_action.html.haml create mode 100644 app/views/common/_navigation_item.html.haml create mode 100644 app/views/common/_table.html.haml create mode 100644 app/views/identities/_identity.html.haml create mode 100644 app/views/identities/index.html.haml create mode 100644 app/views/layouts/_admin_navigation_item.html.haml (limited to 'app') 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 @@ -144,6 +144,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..624e38a --- /dev/null +++ b/app/controllers/identities_controller.rb @@ -0,0 +1,28 @@ +class IdentitiesController < ApplicationController + + before_filter :require_login + before_filter :require_admin + before_filter :fetch_identity, only: :destroy + before_filter :protect_main_email, only: :destroy + + def index + @identities = Identity.all + 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/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/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..e09d7f2 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -95,6 +95,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 +115,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/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..02c54c8 --- /dev/null +++ b/app/views/common/_navigation_item.html.haml @@ -0,0 +1,3 @@ +- item = navigation_item.to_s +%li{:class => ("active" if controller?(item))} + = link_to t(".#{item}", cascade: true), polymorphic_url(item) diff --git a/app/views/common/_table.html.haml b/app/views/common/_table.html.haml new file mode 100644 index 0000000..3c6bf2e --- /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.any? + = 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..cabcccd --- /dev/null +++ b/app/views/identities/index.html.haml @@ -0,0 +1,4 @@ +-@show_navigation = false + += 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..ff14af9 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,10 +1,6 @@ - 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 + = render partial: 'common/navigation_item', collection: [:users, :identities, :tickets] %li = link_to t(:logout), logout_path, :method => :delete - if @user && @show_navigation -- cgit v1.2.3 From bdb4b0e275c205b0b44bbe3cc4ec4c162b309b37 Mon Sep 17 00:00:00 2001 From: Azul Date: Sat, 5 Jul 2014 11:41:43 +0200 Subject: make link_to_navigation more generic and reuse it Use link_to_navigation for all important navigation items. It creates a link in a list item for use with bootstrap. It supports an :active flag and an :icon option in the html_options now. It also translates the label. This way it can be used in a lot of places as the generic navigation link. --- app/helpers/navigation_helper.rb | 35 ++++++++++++++++++++--------- app/views/common/_navigation_item.html.haml | 10 +++++++-- app/views/layouts/_header.html.haml | 6 ++--- app/views/layouts/_navigation.html.haml | 15 ++++++++----- 4 files changed, 46 insertions(+), 20 deletions(-) (limited to 'app') 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/views/common/_navigation_item.html.haml b/app/views/common/_navigation_item.html.haml index 02c54c8..39b20d7 100644 --- a/app/views/common/_navigation_item.html.haml +++ b/app/views/common/_navigation_item.html.haml @@ -1,3 +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 -%li{:class => ("active" if controller?(item))} - = link_to t(".#{item}", cascade: true), polymorphic_url(item) += link_to_navigation ".#{item}", polymorphic_url(item), + active: controller?(item) diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index ff14af9..7b7999a 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -1,8 +1,8 @@ - if admin? %ul.nav.nav-tabs - = render partial: 'common/navigation_item', collection: [:users, :identities, :tickets] - %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 -- cgit v1.2.3 From 0cc11ebb609de225fbeacbf80788b992b88b6ce6 Mon Sep 17 00:00:00 2001 From: Azul Date: Wed, 9 Jul 2014 12:51:16 +0200 Subject: list identities based on search only --- app/assets/javascripts/users.js | 12 +++++++++--- app/controllers/identities_controller.rb | 8 +++++++- app/controllers/users_controller.rb | 4 ++-- app/controllers/v1/users_controller.rb | 2 +- app/helpers/search_helper.rb | 9 +++++++++ app/models/identity.rb | 4 ++++ app/models/user.rb | 4 ++++ app/views/common/_search.html.haml | 8 ++++++++ app/views/common/_table.html.haml | 2 +- app/views/identities/index.html.haml | 1 + app/views/users/index.html.haml | 13 ++----------- 11 files changed, 48 insertions(+), 19 deletions(-) create mode 100644 app/helpers/search_helper.rb create mode 100644 app/views/common/_search.html.haml (limited to 'app') 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/controllers/identities_controller.rb b/app/controllers/identities_controller.rb index 624e38a..8bd3b28 100644 --- a/app/controllers/identities_controller.rb +++ b/app/controllers/identities_controller.rb @@ -1,12 +1,18 @@ 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 - @identities = Identity.all + 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 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/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/models/identity.rb b/app/models/identity.rb index e09d7f2..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 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/_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 index 3c6bf2e..44762da 100644 --- a/app/views/common/_table.html.haml +++ b/app/views/common/_table.html.haml @@ -10,7 +10,7 @@ - headers.each do |header| %th= header %tbody - - if content.any? + - if content.present? = render content.all - else %tr diff --git a/app/views/identities/index.html.haml b/app/views/identities/index.html.haml index cabcccd..e4f48eb 100644 --- a/app/views/identities/index.html.haml +++ b/app/views/identities/index.html.haml @@ -1,4 +1,5 @@ -@show_navigation = false += search :identities = table @identities, %w(type username destination actions) -# = paginate @identities 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) -- cgit v1.2.3