| @@ -36,15 +36,6 @@ body { padding-top: 60px; } | ||
| 36 | 36 | @extend .control-group.error; | 
| 37 | 37 | } | 
| 38 | 38 |  | 
| 39 | -table.events { | |
| 40 | -  .payload { | |
| 41 | - color: #999; | |
| 42 | - font-size: 12px; | |
| 43 | - //text-align: center; | |
| 44 | - font-family: monospace; | |
| 45 | - } | |
| 46 | -} | |
| 47 | - | |
| 48 | 39 |  .select2 { | 
| 49 | 40 | float: none !important; | 
| 50 | 41 | margin-left: 0 !important; | 
| @@ -0,0 +1,31 @@ | ||
| 1 | +// Sortable table headers | |
| 2 | +.table th a.selected { | |
| 3 | + position: relative; | |
| 4 | + text-decoration: underline; | |
| 5 | + | |
| 6 | +  &.asc:after, &.desc:after { | |
| 7 | + text-decoration: none; | |
| 8 | + position: absolute; | |
| 9 | + top: -5px; | |
| 10 | + right: -12px; | |
| 11 | + font-size: 1.2em; | |
| 12 | + } | |
| 13 | + | |
| 14 | +  &.asc:after { | |
| 15 | + content: '\2193'; | |
| 16 | + } | |
| 17 | + | |
| 18 | +  &.desc:after { | |
| 19 | + content: '\2191'; | |
| 20 | + } | |
| 21 | +} | |
| 22 | + | |
| 23 | +table.events { | |
| 24 | +  .payload { | |
| 25 | + color: #999; | |
| 26 | + font-size: 12px; | |
| 27 | + //text-align: center; | |
| 28 | + font-family: monospace; | |
| 29 | + } | |
| 30 | +} | |
| 31 | + | 
| @@ -1,8 +1,11 @@ | ||
| 1 | 1 | class AgentsController < ApplicationController | 
| 2 | 2 | include DotHelper | 
| 3 | + include SortableTable | |
| 3 | 4 |  | 
| 4 | 5 | def index | 
| 5 | - @agents = current_user.agents.page(params[:page]) | |
| 6 | +    set_table_sort sorts: %w[name last_check_at last_event_at last_receive_at], default: { name: :asc } | |
| 7 | + | |
| 8 | + @agents = current_user.agents.preload(:scenarios, :controllers).reorder(table_sort).page(params[:page]) | |
| 6 | 9 |  | 
| 7 | 10 | respond_to do |format| | 
| 8 | 11 | format.html | 
| @@ -0,0 +1,53 @@ | ||
| 1 | +require 'active_support/concern' | |
| 2 | + | |
| 3 | +module SortableTable | |
| 4 | + extend ActiveSupport::Concern | |
| 5 | + | |
| 6 | + included do | |
| 7 | + helper SortableTableHelper | |
| 8 | + end | |
| 9 | + | |
| 10 | + protected | |
| 11 | + | |
| 12 | + def table_sort | |
| 13 | +    raise("You must call set_table_sort in any action using table_sort.") unless @table_sort_info.present? | |
| 14 | + @table_sort_info[:order] | |
| 15 | + end | |
| 16 | + | |
| 17 | + def set_table_sort(sort_options) | |
| 18 | +    valid_sorts = sort_options[:sorts] || raise("You must specify :sorts as an array of valid sort attributes.") | |
| 19 | +    default = sort_options[:default] || { valid_sorts.first.to_sym => :desc } | |
| 20 | + | |
| 21 | + if params[:sort].present? | |
| 22 | +      attribute, direction = params[:sort].downcase.split('.') | |
| 23 | + unless valid_sorts.include?(attribute) | |
| 24 | + attribute, direction = default.to_a.first | |
| 25 | + end | |
| 26 | + else | |
| 27 | + attribute, direction = default.to_a.first | |
| 28 | + end | |
| 29 | + | |
| 30 | + direction = direction.to_s == 'desc' ? 'desc' : 'asc' | |
| 31 | + | |
| 32 | +    @table_sort_info = { | |
| 33 | +      order: { attribute.to_sym => direction.to_sym }, | |
| 34 | + attribute: attribute, | |
| 35 | + direction: direction | |
| 36 | + } | |
| 37 | + end | |
| 38 | + | |
| 39 | + module SortableTableHelper | |
| 40 | + def sortable_column(attribute, name = attribute.humanize, default_direction = 'desc') | |
| 41 | + selected = @table_sort_info[:attribute].to_s == attribute | |
| 42 | + if selected | |
| 43 | + direction = @table_sort_info[:direction] | |
| 44 | + new_direction = direction.to_s == 'desc' ? 'asc' : 'desc' | |
| 45 | +        classes = "selected #{direction}" | |
| 46 | + else | |
| 47 | + classes = '' | |
| 48 | + new_direction = default_direction | |
| 49 | + end | |
| 50 | +      link_to(name, url_for(sort: "#{attribute}.#{new_direction}"), class: classes) | |
| 51 | + end | |
| 52 | + end | |
| 53 | +end | 
| @@ -1,8 +1,11 @@ | ||
| 1 | 1 | class ScenariosController < ApplicationController | 
| 2 | + include SortableTable | |
| 2 | 3 | skip_before_filter :authenticate_user!, :only => :export | 
| 3 | 4 |  | 
| 4 | 5 | def index | 
| 5 | - @scenarios = current_user.scenarios.page(params[:page]) | |
| 6 | +    set_table_sort sorts: %w[name public], default: { name: :asc } | |
| 7 | + | |
| 8 | + @scenarios = current_user.scenarios.reorder(table_sort).page(params[:page]) | |
| 6 | 9 |  | 
| 7 | 10 | respond_to do |format| | 
| 8 | 11 | format.html | 
| @@ -21,7 +24,9 @@ class ScenariosController < ApplicationController | ||
| 21 | 24 |  | 
| 22 | 25 | def show | 
| 23 | 26 | @scenario = current_user.scenarios.find(params[:id]) | 
| 24 | - @agents = @scenario.agents.preload(:scenarios).page(params[:page]) | |
| 27 | + | |
| 28 | +    set_table_sort sorts: %w[name last_check_at last_event_at last_receive_at], default: { name: :asc } | |
| 29 | + @agents = @scenario.agents.preload(:scenarios, :controllers).reorder(table_sort).page(params[:page]) | |
| 25 | 30 |  | 
| 26 | 31 | respond_to do |format| | 
| 27 | 32 | format.html | 
| @@ -1,8 +1,12 @@ | ||
| 1 | 1 | class ServicesController < ApplicationController | 
| 2 | + include SortableTable | |
| 3 | + | |
| 2 | 4 | before_filter :upgrade_warning, only: :index | 
| 3 | 5 |  | 
| 4 | 6 | def index | 
| 5 | - @services = current_user.services.page(params[:page]) | |
| 7 | +    set_table_sort sorts: %w[provider name global], default: { provider: :asc } | |
| 8 | + | |
| 9 | + @services = current_user.services.reorder(table_sort).page(params[:page]) | |
| 6 | 10 |  | 
| 7 | 11 | respond_to do |format| | 
| 8 | 12 | format.html | 
| @@ -1,6 +1,10 @@ | ||
| 1 | 1 | class UserCredentialsController < ApplicationController | 
| 2 | + include SortableTable | |
| 3 | + | |
| 2 | 4 | def index | 
| 3 | - @user_credentials = current_user.user_credentials.page(params[:page]) | |
| 5 | +    set_table_sort sorts: %w[credential_name credential_value], default: { credential_name: :asc } | |
| 6 | + | |
| 7 | + @user_credentials = current_user.user_credentials.reorder(table_sort).page(params[:page]) | |
| 4 | 8 |  | 
| 5 | 9 | respond_to do |format| | 
| 6 | 10 | format.html | 
| @@ -31,7 +31,7 @@ module AgentHelper | ||
| 31 | 31 | end | 
| 32 | 32 |  | 
| 33 | 33 | def agent_controllers(agent, delimiter = ', ') | 
| 34 | - unless agent.controllers.empty? | |
| 34 | + if agent.controllers.present? | |
| 35 | 35 |        agent.controllers.map { |agent| | 
| 36 | 36 | link_to(agent.name, agent_path(agent)) | 
| 37 | 37 | }.join(delimiter).html_safe | 
| @@ -1,11 +1,11 @@ | ||
| 1 | 1 | <div class='table-responsive'> | 
| 2 | 2 | <table class='table table-striped'> | 
| 3 | 3 | <tr> | 
| 4 | - <th>Name</th> | |
| 4 | + <th><%= sortable_column 'name', 'Name', 'asc' %></th> | |
| 5 | 5 | <th>Schedule</th> | 
| 6 | - <th>Last Check</th> | |
| 7 | - <th>Last Event Out</th> | |
| 8 | - <th>Last Event In</th> | |
| 6 | + <th><%= sortable_column 'last_check_at', 'Last Check' %></th> | |
| 7 | + <th><%= sortable_column 'last_event_at', 'Last Event Out' %></th> | |
| 8 | + <th><%= sortable_column 'last_receive_at', 'Last Event In' %></th> | |
| 9 | 9 | <th>Events Created</th> | 
| 10 | 10 | <th>Working?</th> | 
| 11 | 11 | <th></th> | 
| @@ -12,9 +12,9 @@ | ||
| 12 | 12 |  | 
| 13 | 13 | <table class='table table-striped'> | 
| 14 | 14 | <tr> | 
| 15 | - <th>Name</th> | |
| 15 | + <th><%= sortable_column 'name', 'Name', 'asc' %></th> | |
| 16 | 16 | <th>Agents</th> | 
| 17 | - <th>Public</th> | |
| 17 | + <th><%= sortable_column 'public' %></th> | |
| 18 | 18 | <th></th> | 
| 19 | 19 | </tr> | 
| 20 | 20 |  | 
| @@ -25,9 +25,9 @@ | ||
| 25 | 25 | <div class='table-responsive'> | 
| 26 | 26 | <table class='table table-striped events'> | 
| 27 | 27 | <tr> | 
| 28 | - <th>Provider</th> | |
| 29 | - <th>Username</th> | |
| 30 | - <th>Global?</th> | |
| 28 | + <th><%= sortable_column 'provider', 'Provider', 'asc' %></th> | |
| 29 | + <th><%= sortable_column 'name', 'Name', 'asc' %></th> | |
| 30 | + <th><%= sortable_column 'global', 'Global?' %></th> | |
| 31 | 31 | <th></th> | 
| 32 | 32 | </tr> | 
| 33 | 33 |  | 
| @@ -14,8 +14,8 @@ | ||
| 14 | 14 |  | 
| 15 | 15 | <table class='table table-striped'> | 
| 16 | 16 | <tr> | 
| 17 | - <th>Name</th> | |
| 18 | - <th>Value</th> | |
| 17 | + <th><%= sortable_column 'credential_name', 'Name', 'asc' %></th> | |
| 18 | + <th><%= sortable_column 'credential_value', 'Value', 'asc' %></th> | |
| 19 | 19 | </tr> | 
| 20 | 20 |  | 
| 21 | 21 | <% @user_credentials.each do |user_credential| %> | 
| @@ -0,0 +1,61 @@ | ||
| 1 | +require 'spec_helper' | |
| 2 | + | |
| 3 | +describe SortableTable do | |
| 4 | + class SortableTestController | |
| 5 | + attr_accessor :params | |
| 6 | + | |
| 7 | + def self.helper(foo) | |
| 8 | + end | |
| 9 | + | |
| 10 | + include SortableTable | |
| 11 | + | |
| 12 | + public :set_table_sort | |
| 13 | + public :table_sort | |
| 14 | + end | |
| 15 | + | |
| 16 | + describe "#set_table_sort" do | |
| 17 | +    let(:controller) { SortableTestController.new } | |
| 18 | +    let(:default) { { column2: :asc }} | |
| 19 | +    let(:options) { { sorts: %w[column1 column2], default: default } } | |
| 20 | + | |
| 21 | + it "uses a default when no sort is given" do | |
| 22 | +      controller.params = {} | |
| 23 | + controller.set_table_sort options | |
| 24 | + controller.table_sort.should == default | |
| 25 | + end | |
| 26 | + | |
| 27 | + it "applies the given sort when one is passed in" do | |
| 28 | +      controller.params = { sort: "column1.desc" } | |
| 29 | + controller.set_table_sort options | |
| 30 | +      controller.table_sort.should == { column1: :desc } | |
| 31 | + | |
| 32 | +      controller.params = { sort: "column1.asc" } | |
| 33 | + controller.set_table_sort options | |
| 34 | +      controller.table_sort.should == { column1: :asc } | |
| 35 | + | |
| 36 | +      controller.params = { sort: "column2.desc" } | |
| 37 | + controller.set_table_sort options | |
| 38 | +      controller.table_sort.should == { column2: :desc } | |
| 39 | + end | |
| 40 | + | |
| 41 | + it "ignores unknown directions" do | |
| 42 | +      controller.params = { sort: "column1.foo" } | |
| 43 | + controller.set_table_sort options | |
| 44 | +      controller.table_sort.should == { column1: :asc } | |
| 45 | + | |
| 46 | +      controller.params = { sort: "column1.foo drop tables" } | |
| 47 | + controller.set_table_sort options | |
| 48 | +      controller.table_sort.should == { column1: :asc } | |
| 49 | + end | |
| 50 | + | |
| 51 | + it "ignores unknown columns" do | |
| 52 | +      controller.params = { sort: "foo.asc" } | |
| 53 | + controller.set_table_sort options | |
| 54 | + controller.table_sort.should == default | |
| 55 | + | |
| 56 | +      controller.params = { sort: ";drop table;.asc" } | |
| 57 | + controller.set_table_sort options | |
| 58 | + controller.table_sort.should == default | |
| 59 | + end | |
| 60 | + end | |
| 61 | +end | 
| @@ -55,6 +55,8 @@ RSpec.configure do |config| | ||
| 55 | 55 | config.global_fixtures = :all | 
| 56 | 56 | config.treat_symbols_as_metadata_keys_with_true_values = true | 
| 57 | 57 |  | 
| 58 | + config.render_views | |
| 59 | + | |
| 58 | 60 | config.include Devise::TestHelpers, :type => :controller | 
| 59 | 61 | config.include SpecHelpers | 
| 60 | 62 | config.include Delorean |