pghero 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of pghero might be problematic. Click here for more details.

@@ -7,5 +7,5 @@
7
7
 
8
8
  <p><%= button_to "Kill all connections", kill_all_path, class: "btn btn-danger" %></p>
9
9
 
10
- <p class="text-muted">You may need to restart your app server afterwards.</p>
10
+ <p class="text-muted">You may need to restart your app servers afterwards.</p>
11
11
  </div>
@@ -6,9 +6,9 @@
6
6
  <% end %>
7
7
  </h1>
8
8
 
9
- <h1>Size <small>MB</small></h1>
9
+ <h1>Size</h1>
10
10
  <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
11
11
  <script>
12
- new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null})
12
+ new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, min: null, bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
13
13
  </script>
14
14
  </div>
@@ -49,19 +49,19 @@
49
49
  <h1>Total Time <small>ms</small></h1>
50
50
  <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
51
51
  <script>
52
- new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
52
+ new Chartkick.LineChart("chart-1", <%= json_escape(@chart_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
53
53
  </script>
54
54
 
55
55
  <h1>Average Time <small>ms</small></h1>
56
56
  <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
57
57
  <script>
58
- new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
58
+ new Chartkick.LineChart("chart-2", <%= json_escape(@chart2_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
59
59
  </script>
60
60
 
61
61
  <h1>Calls</h1>
62
62
  <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
63
63
  <script>
64
- new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false})
64
+ new Chartkick.LineChart("chart-3", <%= json_escape(@chart3_data.to_json).html_safe %>, {colors: ["#5bc0de"], legend: false, library: {tooltips: {intersect: false, mode: "index"}}})
65
65
  </script>
66
66
  <% else %>
67
67
  <p>
@@ -6,7 +6,7 @@
6
6
  <% if @system_stats_enabled %>
7
7
  <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
8
8
  <script>
9
- new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"]})
9
+ new Chartkick.LineChart("chart-1", <%= json_escape(free_space_stats_path.to_json).html_safe %>, {colors: ["#5bc0de"], bytes: true, library: {tooltips: {intersect: false, mode: "index"}}})
10
10
  </script>
11
11
  <% end %>
12
12
 
@@ -30,7 +30,7 @@
30
30
  </p>
31
31
 
32
32
  <div id="migration" style="display: none;">
33
- <pre>rails g migration remove_unused_indexes</pre>
33
+ <pre>rails generate migration remove_unused_indexes</pre>
34
34
  <p>And paste</p>
35
35
  <pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
36
36
  remove_index <%= query[:table].to_sym.inspect %>, name: <%= query[:index].to_s.inspect %><% end %></pre>
@@ -9,26 +9,26 @@
9
9
  <h1>CPU</h1>
10
10
  <div id="chart-1" class="chart" style="margin-bottom: 20px;">Loading...</div>
11
11
  <script>
12
- new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"]})
12
+ new Chartkick.LineChart("chart-1", <%= json_escape(cpu_usage_path(path_options).to_json).html_safe %>, {max: 100, colors: ["#5bc0de"], suffix: "%", library: {tooltips: {intersect: false, mode: "index"}}})
13
13
  </script>
14
14
 
15
15
  <h1>Load</h1>
16
16
  <div id="chart-2" class="chart" style="margin-bottom: 20px;">Loading...</div>
17
17
  <script>
18
- new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"]})
18
+ new Chartkick.LineChart("chart-2", <%= json_escape(load_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de", "#d9534f"], library: {tooltips: {intersect: false, mode: "nearest"}}})
19
19
  </script>
20
20
 
21
21
  <h1>Connections</h1>
22
22
  <div id="chart-3" class="chart" style="margin-bottom: 20px;">Loading...</div>
23
23
  <script>
24
- new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
24
+ new Chartkick.LineChart("chart-3", <%= json_escape(connection_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
25
25
  </script>
26
26
 
27
27
  <% if @database.replica? %>
28
28
  <h1>Replication Lag</h1>
29
29
  <div id="chart-4" class="chart" style="margin-bottom: 20px;">Loading...</div>
30
30
  <script>
31
- new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"]})
31
+ new Chartkick.LineChart("chart-4", <%= json_escape(replication_lag_stats_path(path_options).to_json).html_safe %>, {colors: ["#5bc0de"], library: {tooltips: {intersect: false, mode: "index"}}})
32
32
  </script>
33
33
  <% end %>
34
34
  </div>
@@ -1,5 +1,5 @@
1
1
  databases:
2
- main:
2
+ primary:
3
3
  # Database URL (defaults to app database)
4
4
  # url: <%%= ENV["DATABASE_URL"] %>
5
5
 
@@ -24,3 +24,16 @@ databases:
24
24
 
25
25
  # Time zone (defaults to app time zone)
26
26
  # time_zone: "Pacific Time (US & Canada)"
27
+
28
+ # Basic authentication
29
+ # username: admin
30
+ # password: secret
31
+
32
+ # Stats database URL (defaults to app database)
33
+ # stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
34
+
35
+ # AWS configuration (defaults to app AWS config)
36
+ # also need aws_db_instance_identifier with each database
37
+ # aws_access_key_id: ...
38
+ # aws_secret_access_key: ...
39
+ # aws_region: us-east-1
@@ -5,6 +5,7 @@ require "forwardable"
5
5
  # methods
6
6
  require "pghero/methods/basic"
7
7
  require "pghero/methods/connections"
8
+ require "pghero/methods/constraints"
8
9
  require "pghero/methods/explain"
9
10
  require "pghero/methods/indexes"
10
11
  require "pghero/methods/kill"
@@ -53,7 +54,7 @@ module PgHero
53
54
  :best_index, :blocked_queries, :connection_sources, :connection_states, :connection_stats,
54
55
  :cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
55
56
  :duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
56
- :index_hit_rate, :index_usage, :indexes, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
57
+ :index_hit_rate, :index_usage, :indexes, :invalid_constraints, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
57
58
  :last_stats_reset_time, :long_running_queries, :maintenance_info, :missing_indexes, :query_stats,
58
59
  :query_stats_available?, :query_stats_enabled?, :query_stats_extension_enabled?, :query_stats_readable?,
59
60
  :rds_stats, :read_iops_stats, :region, :relation_sizes, :replica?, :replication_lag, :replication_lag_stats,
@@ -70,6 +71,22 @@ module PgHero
70
71
  @time_zone || Time.zone
71
72
  end
72
73
 
74
+ # use method instead of attr_accessor to ensure
75
+ # this works if variable set after PgHero is loaded
76
+ def username
77
+ @username ||= config["username"] || ENV["PGHERO_USERNAME"]
78
+ end
79
+
80
+ # use method instead of attr_accessor to ensure
81
+ # this works if variable set after PgHero is loaded
82
+ def password
83
+ @password ||= config["password"] || ENV["PGHERO_PASSWORD"]
84
+ end
85
+
86
+ def stats_database_url
87
+ @stats_database_url ||= config["stats_database_url"] || ENV["PGHERO_STATS_DATABASE_URL"]
88
+ end
89
+
73
90
  def config
74
91
  @config ||= begin
75
92
  require "erb"
@@ -89,13 +106,23 @@ module PgHero
89
106
  elsif config_file_exists
90
107
  raise "Invalid config file"
91
108
  else
92
- {
93
- "databases" => {
94
- "primary" => {
95
- "url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config,
96
- "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"]
97
- }
109
+ databases = {}
110
+
111
+ if !ENV["PGHERO_DATABASE_URL"] && spec_supported?
112
+ ActiveRecord::Base.configurations.configs_for(env_name: env, include_replicas: true).each do |db|
113
+ databases[db.spec_name] = {"spec" => db.spec_name}
114
+ end
115
+ end
116
+
117
+ if databases.empty?
118
+ databases["primary"] = {
119
+ "url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config,
120
+ "db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"]
98
121
  }
122
+ end
123
+
124
+ {
125
+ "databases" => databases
99
126
  }
100
127
  end
101
128
  end
@@ -163,6 +190,11 @@ module PgHero
163
190
  end
164
191
  end
165
192
 
193
+ # private
194
+ def spec_supported?
195
+ ActiveRecord::VERSION::MAJOR >= 6
196
+ end
197
+
166
198
  private
167
199
 
168
200
  def each_database
@@ -2,6 +2,7 @@ module PgHero
2
2
  class Database
3
3
  include Methods::Basic
4
4
  include Methods::Connections
5
+ include Methods::Constraints
5
6
  include Methods::Explain
6
7
  include Methods::Indexes
7
8
  include Methods::Kill
@@ -28,10 +29,6 @@ module PgHero
28
29
  @name ||= @config["name"] || id.titleize
29
30
  end
30
31
 
31
- def db_instance_identifier
32
- @db_instance_identifier ||= @config["db_instance_identifier"]
33
- end
34
-
35
32
  def capture_query_stats?
36
33
  config["capture_query_stats"] != false
37
34
  end
@@ -64,11 +61,39 @@ module PgHero
64
61
  (config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 100.megabytes).to_i
65
62
  end
66
63
 
64
+ def aws_access_key_id
65
+ config["aws_access_key_id"] || PgHero.config["aws_access_key_id"] || ENV["PGHERO_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY_ID"]
66
+ end
67
+
68
+ def aws_secret_access_key
69
+ config["aws_secret_access_key"] || PgHero.config["aws_secret_access_key"] || ENV["PGHERO_SECRET_ACCESS_KEY"] || ENV["AWS_SECRET_ACCESS_KEY"]
70
+ end
71
+
72
+ def aws_region
73
+ config["aws_region"] || PgHero.config["aws_region"] || ENV["PGHERO_REGION"] || ENV["AWS_REGION"] || (defined?(Aws) && Aws.config[:region]) || "us-east-1"
74
+ end
75
+
76
+ def aws_db_instance_identifier
77
+ @db_instance_identifier ||= config["aws_db_instance_identifier"] || config["db_instance_identifier"]
78
+ end
79
+
80
+ # TODO remove in next major version
81
+ alias_method :access_key_id, :aws_access_key_id
82
+ alias_method :secret_access_key, :aws_secret_access_key
83
+ alias_method :region, :aws_region
84
+ alias_method :db_instance_identifier, :aws_db_instance_identifier
85
+
67
86
  private
68
87
 
69
88
  def connection_model
70
89
  @connection_model ||= begin
71
90
  url = config["url"]
91
+ if !url && config["spec"]
92
+ raise Error, "Spec requires Rails 6+" unless PgHero.spec_supported?
93
+ resolved = ActiveRecord::Base.configurations.configs_for(env_name: PgHero.env, spec_name: config["spec"], include_replicas: true)
94
+ raise Error, "Spec not found: #{config["spec"]}" unless resolved
95
+ url = resolved.config
96
+ end
72
97
  Class.new(PgHero::Connection) do
73
98
  def self.name
74
99
  "PgHero::Connection::Database#{object_id}"
@@ -0,0 +1,30 @@
1
+ module PgHero
2
+ module Methods
3
+ module Constraints
4
+ # referenced fields can be nil
5
+ # as not all constraints are foreign keys
6
+ def invalid_constraints
7
+ select_all <<-SQL
8
+ SELECT
9
+ nsp.nspname AS schema,
10
+ rel.relname AS table,
11
+ con.conname AS name,
12
+ fnsp.nspname AS referenced_schema,
13
+ frel.relname AS referenced_table
14
+ FROM
15
+ pg_catalog.pg_constraint con
16
+ INNER JOIN
17
+ pg_catalog.pg_class rel ON rel.oid = con.conrelid
18
+ LEFT JOIN
19
+ pg_catalog.pg_class frel ON frel.oid = con.confrelid
20
+ LEFT JOIN
21
+ pg_catalog.pg_namespace nsp ON nsp.oid = con.connamespace
22
+ LEFT JOIN
23
+ pg_catalog.pg_namespace fnsp ON fnsp.oid = frel.relnamespace
24
+ WHERE
25
+ con.convalidated = 'f'
26
+ SQL
27
+ end
28
+ end
29
+ end
30
+ end
@@ -195,7 +195,7 @@ module PgHero
195
195
  FROM
196
196
  pg_index
197
197
  JOIN
198
- pg_class ON pg_class.oid=pg_index.indexrelid
198
+ pg_class ON pg_class.oid = pg_index.indexrelid
199
199
  JOIN
200
200
  pg_namespace ON pg_namespace.oid = pg_class.relnamespace
201
201
  JOIN
@@ -70,22 +70,6 @@ module PgHero
70
70
  def system_stats_enabled?
71
71
  !!((defined?(Aws) || defined?(AWS)) && db_instance_identifier)
72
72
  end
73
-
74
- def access_key_id
75
- ENV["PGHERO_ACCESS_KEY_ID"] || ENV["AWS_ACCESS_KEY_ID"]
76
- end
77
-
78
- def secret_access_key
79
- ENV["PGHERO_SECRET_ACCESS_KEY"] || ENV["AWS_SECRET_ACCESS_KEY"]
80
- end
81
-
82
- def region
83
- ENV["PGHERO_REGION"] || ENV["AWS_REGION"] || (defined?(Aws) && Aws.config[:region]) || "us-east-1"
84
- end
85
-
86
- def db_instance_identifier
87
- databases[current_database].db_instance_identifier
88
- end
89
73
  end
90
74
  end
91
75
  end
@@ -1,6 +1,6 @@
1
1
  module PgHero
2
2
  class Stats < ActiveRecord::Base
3
3
  self.abstract_class = true
4
- establish_connection ENV["PGHERO_STATS_DATABASE_URL"] if ENV["PGHERO_STATS_DATABASE_URL"]
4
+ establish_connection PgHero.stats_database_url if PgHero.stats_database_url
5
5
  end
6
6
  end
@@ -1,3 +1,3 @@
1
1
  module PgHero
2
- VERSION = "2.3.0"
2
+ VERSION = "2.4.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pghero
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-19 00:00:00.000000000 Z
11
+ date: 2019-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -124,10 +124,10 @@ files:
124
124
  - app/assets/javascripts/pghero/chartkick.js
125
125
  - app/assets/javascripts/pghero/highlight.pack.js
126
126
  - app/assets/javascripts/pghero/jquery.js
127
- - app/assets/javascripts/pghero/jquery.nouislider.min.js
127
+ - app/assets/javascripts/pghero/nouislider.js
128
128
  - app/assets/stylesheets/pghero/application.css
129
129
  - app/assets/stylesheets/pghero/arduino-light.css
130
- - app/assets/stylesheets/pghero/jquery.nouislider.css
130
+ - app/assets/stylesheets/pghero/nouislider.css
131
131
  - app/controllers/pg_hero/home_controller.rb
132
132
  - app/helpers/pg_hero/home_helper.rb
133
133
  - app/views/layouts/pg_hero/application.html.erb
@@ -161,6 +161,7 @@ files:
161
161
  - lib/pghero/engine.rb
162
162
  - lib/pghero/methods/basic.rb
163
163
  - lib/pghero/methods/connections.rb
164
+ - lib/pghero/methods/constraints.rb
164
165
  - lib/pghero/methods/explain.rb
165
166
  - lib/pghero/methods/indexes.rb
166
167
  - lib/pghero/methods/kill.rb
@@ -1,31 +0,0 @@
1
- /*
2
-
3
- $.Link (part of noUiSlider) - WTFPL */
4
- (function(c){function m(a,c,d){if((a[c]||a[d])&&a[c]===a[d])throw Error("(Link) '"+c+"' can't match '"+d+"'.'");}function r(a){void 0===a&&(a={});if("object"!==typeof a)throw Error("(Format) 'format' option must be an object.");var h={};c(u).each(function(c,n){if(void 0===a[n])h[n]=A[c];else if(typeof a[n]===typeof A[c]){if("decimals"===n&&(0>a[n]||7<a[n]))throw Error("(Format) 'format.decimals' option must be between 0 and 7.");h[n]=a[n]}else throw Error("(Format) 'format."+n+"' must be a "+typeof A[c]+
5
- ".");});m(h,"mark","thousand");m(h,"prefix","negative");m(h,"prefix","negativeBefore");this.r=h}function k(a,h){"object"!==typeof a&&c.error("(Link) Initialize with an object.");return new k.prototype.p(a.target||function(){},a.method,a.format||{},h)}var u="decimals mark thousand prefix postfix encoder decoder negative negativeBefore to from".split(" "),A=[2,".","","","",function(a){return a},function(a){return a},"-","",function(a){return a},function(a){return a}];r.prototype.a=function(a){return this.r[a]};
6
- r.prototype.L=function(a){function c(a){return a.split("").reverse().join("")}a=this.a("encoder")(a);var d=this.a("decimals"),n="",k="",m="",r="";0===parseFloat(a.toFixed(d))&&(a="0");0>a&&(n=this.a("negative"),k=this.a("negativeBefore"));a=Math.abs(a).toFixed(d).toString();a=a.split(".");this.a("thousand")?(m=c(a[0]).match(/.{1,3}/g),m=c(m.join(c(this.a("thousand"))))):m=a[0];this.a("mark")&&1<a.length&&(r=this.a("mark")+a[1]);return this.a("to")(k+this.a("prefix")+n+m+r+this.a("postfix"))};r.prototype.w=
7
- function(a){function c(a){return a.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g,"\\$&")}var d;if(null===a||void 0===a)return!1;a=this.a("from")(a);a=a.toString();d=a.replace(RegExp("^"+c(this.a("negativeBefore"))),"");a!==d?(a=d,d="-"):d="";a=a.replace(RegExp("^"+c(this.a("prefix"))),"");this.a("negative")&&(d="",a=a.replace(RegExp("^"+c(this.a("negative"))),"-"));a=a.replace(RegExp(c(this.a("postfix"))+"$"),"").replace(RegExp(c(this.a("thousand")),"g"),"").replace(this.a("mark"),".");a=this.a("decoder")(parseFloat(d+
8
- a));return isNaN(a)?!1:a};k.prototype.K=function(a,h){this.method=h||"html";this.j=c(a.replace("-tooltip-","")||"<div/>")[0]};k.prototype.H=function(a){this.method="val";this.j=document.createElement("input");this.j.name=a;this.j.type="hidden"};k.prototype.G=function(a){function h(a,c){return[c?null:a,c?a:null]}var d=this;this.method="val";this.target=a.on("change",function(a){d.B.val(h(c(a.target).val(),d.t),{link:d,set:!0})})};k.prototype.p=function(a,h,d,k){this.g=d;this.update=!k;if("string"===
9
- typeof a&&0===a.indexOf("-tooltip-"))this.K(a,h);else if("string"===typeof a&&0!==a.indexOf("-"))this.H(a);else if("function"===typeof a)this.target=!1,this.method=a;else{if(a instanceof c||c.zepto&&c.zepto.isZ(a)){if(!h){if(a.is("input, select, textarea")){this.G(a);return}h="html"}if("function"===typeof h||"string"===typeof h&&a[h]){this.method=h;this.target=a;return}}throw new RangeError("(Link) Invalid Link.");}};k.prototype.write=function(a,c,d,k){if(!this.update||!1!==k)if(this.u=a,this.F=a=
10
- this.format(a),"function"===typeof this.method)this.method.call(this.target[0]||d[0],a,c,d);else this.target[this.method](a,c,d)};k.prototype.q=function(a){this.g=new r(c.extend({},a,this.g instanceof r?this.g.r:this.g))};k.prototype.J=function(a){this.B=a};k.prototype.I=function(a){this.t=a};k.prototype.format=function(a){return this.g.L(a)};k.prototype.A=function(a){return this.g.w(a)};k.prototype.p.prototype=k.prototype;c.Link=k})(window.jQuery||window.Zepto);/*
11
-
12
- $.fn.noUiSlider - WTFPL - refreshless.com/nouislider/ */
13
- (function(c){function m(e){return"number"===typeof e&&!isNaN(e)&&isFinite(e)}function r(e){return c.isArray(e)?e:[e]}function k(e,b){e.addClass(b);setTimeout(function(){e.removeClass(b)},300)}function u(e,b){return 100*b/(e[1]-e[0])}function A(e,b){if(b>=e.d.slice(-1)[0])return 100;for(var a=1,c,f,d;b>=e.d[a];)a++;c=e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return d+u(c,0>c[0]?b+Math.abs(c[0]):b-c[0])/(100/(e.c[a]-d))}function a(e,b){if(100<=b)return e.d.slice(-1)[0];for(var a=1,c,f,d;b>=e.c[a];)a++;c=
14
- e.d[a-1];f=e.d[a];d=e.c[a-1];c=[c,f];return 100/(e.c[a]-d)*(b-d)*(c[1]-c[0])/100+c[0]}function h(a,b){for(var c=1,g;(a.dir?100-b:b)>=a.c[c];)c++;if(a.m)return g=a.c[c-1],c=a.c[c],b-g>(c-g)/2?c:g;a.h[c-1]?(g=a.h[c-1],c=a.c[c-1]+Math.round((b-a.c[c-1])/g)*g):c=b;return c}function d(a,b){if(!m(b))throw Error("noUiSlider: 'step' is not numeric.");a.h[0]=b}function n(a,b){if("object"!==typeof b||c.isArray(b))throw Error("noUiSlider: 'range' is not an object.");if(void 0===b.min||void 0===b.max)throw Error("noUiSlider: Missing 'min' or 'max' in 'range'.");
15
- c.each(b,function(b,g){var d;"number"===typeof g&&(g=[g]);if(!c.isArray(g))throw Error("noUiSlider: 'range' contains invalid value.");d="min"===b?0:"max"===b?100:parseFloat(b);if(!m(d)||!m(g[0]))throw Error("noUiSlider: 'range' value isn't numeric.");a.c.push(d);a.d.push(g[0]);d?a.h.push(isNaN(g[1])?!1:g[1]):isNaN(g[1])||(a.h[0]=g[1])});c.each(a.h,function(b,c){if(!c)return!0;a.h[b]=u([a.d[b],a.d[b+1]],c)/(100/(a.c[b+1]-a.c[b]))})}function E(a,b){"number"===typeof b&&(b=[b]);if(!c.isArray(b)||!b.length||
16
- 2<b.length)throw Error("noUiSlider: 'start' option is incorrect.");a.b=b.length;a.start=b}function I(a,b){a.m=b;if("boolean"!==typeof b)throw Error("noUiSlider: 'snap' option must be a boolean.");}function J(a,b){if("lower"===b&&1===a.b)a.i=1;else if("upper"===b&&1===a.b)a.i=2;else if(!0===b&&2===a.b)a.i=3;else if(!1===b)a.i=0;else throw Error("noUiSlider: 'connect' option doesn't match handle count.");}function D(a,b){switch(b){case "horizontal":a.k=0;break;case "vertical":a.k=1;break;default:throw Error("noUiSlider: 'orientation' option is invalid.");
17
- }}function K(a,b){if(2<a.c.length)throw Error("noUiSlider: 'margin' option is only supported on linear sliders.");a.margin=u(a.d,b);if(!m(b))throw Error("noUiSlider: 'margin' option must be numeric.");}function L(a,b){switch(b){case "ltr":a.dir=0;break;case "rtl":a.dir=1;a.i=[0,2,1,3][a.i];break;default:throw Error("noUiSlider: 'direction' option was not recognized.");}}function M(a,b){if("string"!==typeof b)throw Error("noUiSlider: 'behaviour' must be a string containing options.");var c=0<=b.indexOf("snap");
18
- a.n={s:0<=b.indexOf("tap")||c,extend:0<=b.indexOf("extend"),v:0<=b.indexOf("drag"),fixed:0<=b.indexOf("fixed"),m:c}}function N(a,b,d){a.o=[b.lower,b.upper];a.g=b.format;c.each(a.o,function(a,e){if(!c.isArray(e))throw Error("noUiSlider: 'serialization."+(a?"upper":"lower")+"' must be an array.");c.each(e,function(){if(!(this instanceof c.Link))throw Error("noUiSlider: 'serialization."+(a?"upper":"lower")+"' can only contain Link instances.");this.I(a);this.J(d);this.q(b.format)})});a.dir&&1<a.b&&a.o.reverse()}
19
- function O(a,b){var f={c:[],d:[],h:[!1],margin:0},g;g={step:{e:!1,f:d},start:{e:!0,f:E},connect:{e:!0,f:J},direction:{e:!0,f:L},range:{e:!0,f:n},snap:{e:!1,f:I},orientation:{e:!1,f:D},margin:{e:!1,f:K},behaviour:{e:!0,f:M},serialization:{e:!0,f:N}};a=c.extend({connect:!1,direction:"ltr",behaviour:"tap",orientation:"horizontal"},a);a.serialization=c.extend({lower:[],upper:[],format:{}},a.serialization);c.each(g,function(c,d){if(void 0===a[c]){if(d.e)throw Error("noUiSlider: '"+c+"' is required.");
20
- return!0}d.f(f,a[c],b)});f.style=f.k?"top":"left";return f}function P(a,b){var d=c("<div><div/></div>").addClass(f[2]),g=["-lower","-upper"];a.dir&&g.reverse();d.children().addClass(f[3]+" "+f[3]+g[b]);return d}function Q(a,b){b.j&&(b=new c.Link({target:c(b.j).clone().appendTo(a),method:b.method,format:b.g},!0));return b}function R(a,b){var d,f=[];for(d=0;d<a.b;d++){var k=f,h=d,m=a.o[d],n=b[d].children(),r=a.g,s=void 0,v=[],s=new c.Link({},!0);s.q(r);v.push(s);for(s=0;s<m.length;s++)v.push(Q(n,m[s]));
21
- k[h]=v}return f}function S(a,b,c){switch(a){case 1:b.addClass(f[7]);c[0].addClass(f[6]);break;case 3:c[1].addClass(f[6]);case 2:c[0].addClass(f[7]);case 0:b.addClass(f[6])}}function T(a,b){var c,d=[];for(c=0;c<a.b;c++)d.push(P(a,c).appendTo(b));return d}function U(a,b){b.addClass([f[0],f[8+a.dir],f[4+a.k]].join(" "));return c("<div/>").appendTo(b).addClass(f[1])}function V(d,b,m){function g(){return t[["width","height"][b.k]]()}function n(a){var b,c=[q.val()];for(b=0;b<a.length;b++)q.trigger(a[b],
22
- c)}function u(d,p,e){var g=d[0]!==l[0][0]?1:0,H=x[0]+b.margin,k=x[1]-b.margin;e&&1<l.length&&(p=g?Math.max(p,H):Math.min(p,k));100>p&&(p=h(b,p));p=Math.max(Math.min(parseFloat(p.toFixed(7)),100),0);if(p===x[g])return 1===l.length?!1:p===H||p===k?0:!1;d.css(b.style,p+"%");d.is(":first-child")&&d.toggleClass(f[17],50<p);x[g]=p;b.dir&&(p=100-p);c(y[g]).each(function(){this.write(a(b,p),d.children(),q)});return!0}function B(a,b,c){c||k(q,f[14]);u(a,b,!1);n(["slide","set","change"])}function w(a,c,d,e){a=
23
- a.replace(/\s/g,".nui ")+".nui";c.on(a,function(a){var c=q.attr("disabled");if(q.hasClass(f[14])||void 0!==c&&null!==c)return!1;a.preventDefault();var c=0===a.type.indexOf("touch"),p=0===a.type.indexOf("mouse"),F=0===a.type.indexOf("pointer"),g,k,l=a;0===a.type.indexOf("MSPointer")&&(F=!0);a.originalEvent&&(a=a.originalEvent);c&&(g=a.changedTouches[0].pageX,k=a.changedTouches[0].pageY);if(p||F)F||void 0!==window.pageXOffset||(window.pageXOffset=document.documentElement.scrollLeft,window.pageYOffset=
24
- document.documentElement.scrollTop),g=a.clientX+window.pageXOffset,k=a.clientY+window.pageYOffset;l.C=[g,k];l.cursor=p;a=l;a.l=a.C[b.k];d(a,e)})}function C(a,c){var b=c.b||l,d,e=!1,e=100*(a.l-c.start)/g(),f=b[0][0]!==l[0][0]?1:0;var k=c.D;d=e+k[0];e+=k[1];1<b.length?(0>d&&(e+=Math.abs(d)),100<e&&(d-=e-100),d=[Math.max(Math.min(d,100),0),Math.max(Math.min(e,100),0)]):d=[d,e];e=u(b[0],d[f],1===b.length);1<b.length&&(e=u(b[1],d[f?0:1],!1)||e);e&&n(["slide"])}function s(a){c("."+f[15]).removeClass(f[15]);
25
- a.cursor&&c("body").css("cursor","").off(".nui");G.off(".nui");q.removeClass(f[12]);n(["set","change"])}function v(a,b){1===b.b.length&&b.b[0].children().addClass(f[15]);a.stopPropagation();w(z.move,G,C,{start:a.l,b:b.b,D:[x[0],x[l.length-1]]});w(z.end,G,s,null);a.cursor&&(c("body").css("cursor",c(a.target).css("cursor")),1<l.length&&q.addClass(f[12]),c("body").on("selectstart.nui",!1))}function D(a){var d=a.l,e=0;a.stopPropagation();c.each(l,function(){e+=this.offset()[b.style]});e=d<e/2||1===l.length?
26
- 0:1;d-=t.offset()[b.style];d=100*d/g();B(l[e],d,b.n.m);b.n.m&&v(a,{b:[l[e]]})}function E(a){var c=(a=a.l<t.offset()[b.style])?0:100;a=a?0:l.length-1;B(l[a],c,!1)}var q=c(d),x=[-1,-1],t,y,l;if(q.hasClass(f[0]))throw Error("Slider was already initialized.");t=U(b,q);l=T(b,t);y=R(b,l);S(b.i,q,l);(function(a){var b;if(!a.fixed)for(b=0;b<l.length;b++)w(z.start,l[b].children(),v,{b:[l[b]]});a.s&&w(z.start,t,D,{b:l});a.extend&&(q.addClass(f[16]),a.s&&w(z.start,q,E,{b:l}));a.v&&(b=t.find("."+f[7]).addClass(f[10]),
27
- a.fixed&&(b=b.add(t.children().not(b).children())),w(z.start,b,v,{b:l}))})(b.n);d.vSet=function(){var a=Array.prototype.slice.call(arguments,0),d,e,g,h,m,s,t=r(a[0]);"object"===typeof a[1]?(d=a[1].set,e=a[1].link,g=a[1].update,h=a[1].animate):!0===a[1]&&(d=!0);b.dir&&1<b.b&&t.reverse();h&&k(q,f[14]);a=1<l.length?3:1;1===t.length&&(a=1);for(m=0;m<a;m++)h=e||y[m%2][0],h=h.A(t[m%2]),!1!==h&&(h=A(b,h),b.dir&&(h=100-h),!0!==u(l[m%2],h,!0)&&c(y[m%2]).each(function(a){if(!a)return s=this.u,!0;this.write(s,
28
- l[m%2].children(),q,g)}));!0===d&&n(["set"]);return this};d.vGet=function(){var a,c=[];for(a=0;a<b.b;a++)c[a]=y[a][0].F;return 1===c.length?c[0]:b.dir?c.reverse():c};d.destroy=function(){c.each(y,function(){c.each(this,function(){this.target&&this.target.off(".nui")})});c(this).off(".nui").removeClass(f.join(" ")).empty();return m};q.val(b.start)}function W(a){if(!this.length)throw Error("noUiSlider: Can't initialize slider on empty selection.");var b=O(a,this);return this.each(function(){V(this,
29
- b,a)})}function X(a){return this.each(function(){var b=c(this).val(),d=this.destroy(),f=c.extend({},d,a);c(this).noUiSlider(f);d.start===f.start&&c(this).val(b)})}function B(){return this[0][arguments.length?"vSet":"vGet"].apply(this[0],arguments)}var G=c(document),C=c.fn.val,z=window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",
30
- end:"mouseup touchend"},f="noUi-target noUi-base noUi-origin noUi-handle noUi-horizontal noUi-vertical noUi-background noUi-connect noUi-ltr noUi-rtl noUi-dragable noUi-state-drag noUi-state-tap noUi-active noUi-extended noUi-stacking".split(" ");c.fn.val=function(){var a=arguments,b=c(this[0]);return arguments.length?this.each(function(){(c(this).hasClass(f[0])?B:C).apply(c(this),a)}):(b.hasClass(f[0])?B:C).call(b)};c.noUiSlider={Link:c.Link};c.fn.noUiSlider=function(a,b){return(b?X:W).call(this,
31
- a)}})(window.jQuery||window.Zepto);
@@ -1,165 +0,0 @@
1
-
2
- /* Functional styling;
3
- * These styles are required for noUiSlider to function.
4
- * You don't need to change these rules to apply your design.
5
- */
6
- .noUi-target,
7
- .noUi-target * {
8
- -webkit-touch-callout: none;
9
- -webkit-user-select: none;
10
- -ms-touch-action: none;
11
- -ms-user-select: none;
12
- -moz-user-select: none;
13
- -moz-box-sizing: border-box;
14
- box-sizing: border-box;
15
- }
16
- .noUi-base {
17
- width: 100%;
18
- height: 100%;
19
- position: relative;
20
- }
21
- .noUi-origin {
22
- position: absolute;
23
- right: 0;
24
- top: 0;
25
- left: 0;
26
- bottom: 0;
27
- }
28
- .noUi-handle {
29
- position: relative;
30
- z-index: 1;
31
- }
32
- .noUi-stacking .noUi-handle {
33
- /* This class is applied to the lower origin when
34
- its values is > 50%. */
35
- z-index: 10;
36
- }
37
- .noUi-stacking + .noUi-origin {
38
- /* Fix stacking order in IE7, which incorrectly
39
- creates a new context for the origins. */
40
- *z-index: -1;
41
- }
42
- .noUi-state-tap .noUi-origin {
43
- -webkit-transition: left 0.3s, top 0.3s;
44
- transition: left 0.3s, top 0.3s;
45
- }
46
- .noUi-state-drag * {
47
- cursor: inherit !important;
48
- }
49
-
50
- /* Slider size and handle placement;
51
- */
52
- .noUi-horizontal {
53
- height: 18px;
54
- }
55
- .noUi-horizontal .noUi-handle {
56
- width: 34px;
57
- height: 28px;
58
- left: -17px;
59
- top: -6px;
60
- }
61
- .noUi-horizontal.noUi-extended {
62
- padding: 0 15px;
63
- }
64
- .noUi-horizontal.noUi-extended .noUi-origin {
65
- right: -15px;
66
- }
67
- .noUi-vertical {
68
- width: 18px;
69
- }
70
- .noUi-vertical .noUi-handle {
71
- width: 28px;
72
- height: 34px;
73
- left: -6px;
74
- top: -17px;
75
- }
76
- .noUi-vertical.noUi-extended {
77
- padding: 15px 0;
78
- }
79
- .noUi-vertical.noUi-extended .noUi-origin {
80
- bottom: -15px;
81
- }
82
-
83
- /* Styling;
84
- */
85
- .noUi-background {
86
- background: #FAFAFA;
87
- box-shadow: inset 0 1px 1px #f0f0f0;
88
- }
89
- .noUi-connect {
90
- background: #3FB8AF;
91
- box-shadow: inset 0 0 3px rgba(51,51,51,0.45);
92
- -webkit-transition: background 450ms;
93
- transition: background 450ms;
94
- }
95
- .noUi-origin {
96
- border-radius: 2px;
97
- }
98
- .noUi-target {
99
- border-radius: 4px;
100
- border: 1px solid #D3D3D3;
101
- box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB;
102
- }
103
- .noUi-target.noUi-connect {
104
- box-shadow: inset 0 0 3px rgba(51,51,51,0.45), 0 3px 6px -5px #BBB;
105
- }
106
-
107
- /* Handles and cursors;
108
- */
109
- .noUi-dragable {
110
- cursor: w-resize;
111
- }
112
- .noUi-vertical .noUi-dragable {
113
- cursor: n-resize;
114
- }
115
- .noUi-handle {
116
- border: 1px solid #D9D9D9;
117
- border-radius: 3px;
118
- background: #FFF;
119
- cursor: default;
120
- box-shadow: inset 0 0 1px #FFF,
121
- inset 0 1px 7px #EBEBEB,
122
- 0 3px 6px -3px #BBB;
123
- }
124
- .noUi-active {
125
- box-shadow: inset 0 0 1px #FFF,
126
- inset 0 1px 7px #DDD,
127
- 0 3px 6px -3px #BBB;
128
- }
129
-
130
- /* Handle stripes;
131
- */
132
- .noUi-handle:before,
133
- .noUi-handle:after {
134
- content: "";
135
- display: block;
136
- position: absolute;
137
- height: 14px;
138
- width: 1px;
139
- background: #E8E7E6;
140
- left: 14px;
141
- top: 6px;
142
- }
143
- .noUi-handle:after {
144
- left: 17px;
145
- }
146
- .noUi-vertical .noUi-handle:before,
147
- .noUi-vertical .noUi-handle:after {
148
- width: 14px;
149
- height: 1px;
150
- left: 6px;
151
- top: 14px;
152
- }
153
- .noUi-vertical .noUi-handle:after {
154
- top: 17px;
155
- }
156
-
157
- /* Disabled state;
158
- */
159
- [disabled].noUi-connect,
160
- [disabled] .noUi-connect {
161
- background: #B8B8B8;
162
- }
163
- [disabled] .noUi-handle {
164
- cursor: not-allowed;
165
- }