summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorazul <azul@leap.se>2014-07-09 13:25:31 +0200
committerazul <azul@leap.se>2014-07-09 13:25:31 +0200
commitdc740e4311101bf7297996788b25a99edafbe759 (patch)
treed989456179551fb167f6c41ba4b54bbd4a77a7ac /app
parent19da5429308412c19176733d2b32ccbf2c08df1c (diff)
parent0cc11ebb609de225fbeacbf80788b992b88b6ce6 (diff)
Merge pull request #173 from azul/feature/unblock-handles
Allow admins to unblock handles
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/users.js12
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/backport.scss24
-rw-r--r--app/assets/stylesheets/leap.scss21
-rw-r--r--app/controllers/identities_controller.rb34
-rw-r--r--app/controllers/users_controller.rb4
-rw-r--r--app/controllers/v1/users_controller.rb2
-rw-r--r--app/helpers/link_helper.rb25
-rw-r--r--app/helpers/navigation_helper.rb35
-rw-r--r--app/helpers/search_helper.rb9
-rw-r--r--app/helpers/table_helper.rb13
-rw-r--r--app/models/identity.rb24
-rw-r--r--app/models/user.rb4
-rw-r--r--app/views/common/_action.html.haml2
-rw-r--r--app/views/common/_navigation_item.html.haml9
-rw-r--r--app/views/common/_search.html.haml8
-rw-r--r--app/views/common/_table.html.haml18
-rw-r--r--app/views/identities/_identity.html.haml7
-rw-r--r--app/views/identities/index.html.haml5
-rw-r--r--app/views/layouts/_admin_navigation_item.html.haml3
-rw-r--r--app/views/layouts/_header.html.haml10
-rw-r--r--app/views/layouts/_navigation.html.haml15
-rw-r--r--app/views/users/index.html.haml13
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)