blazer 2.2.5 → 2.3.1

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

Potentially problematic release.


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

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +113 -29
  5. data/app/assets/javascripts/blazer/Chart.js +13794 -12099
  6. data/app/assets/javascripts/blazer/Sortable.js +3695 -1526
  7. data/app/assets/javascripts/blazer/chartkick.js +296 -46
  8. data/app/assets/javascripts/blazer/daterangepicker.js +194 -269
  9. data/app/assets/javascripts/blazer/jquery.js +1150 -642
  10. data/app/assets/javascripts/blazer/moment-timezone-with-data.js +621 -287
  11. data/app/assets/javascripts/blazer/moment.js +5085 -2460
  12. data/app/assets/stylesheets/blazer/daterangepicker.css +394 -253
  13. data/app/controllers/blazer/base_controller.rb +4 -4
  14. data/app/controllers/blazer/dashboards_controller.rb +5 -2
  15. data/app/controllers/blazer/queries_controller.rb +11 -2
  16. data/app/controllers/blazer/uploads_controller.rb +147 -0
  17. data/app/mailers/blazer/slack_notifier.rb +1 -1
  18. data/app/models/blazer/query.rb +1 -0
  19. data/app/models/blazer/upload.rb +11 -0
  20. data/app/models/blazer/uploads_connection.rb +7 -0
  21. data/app/views/blazer/_nav.html.erb +3 -0
  22. data/app/views/blazer/_variables.html.erb +3 -1
  23. data/app/views/blazer/checks/_form.html.erb +1 -1
  24. data/app/views/blazer/checks/index.html.erb +3 -0
  25. data/app/views/blazer/dashboards/_form.html.erb +1 -1
  26. data/app/views/blazer/dashboards/show.html.erb +1 -1
  27. data/app/views/blazer/queries/home.html.erb +3 -0
  28. data/app/views/blazer/queries/run.html.erb +5 -5
  29. data/app/views/blazer/queries/show.html.erb +3 -1
  30. data/app/views/blazer/uploads/_form.html.erb +27 -0
  31. data/app/views/blazer/uploads/edit.html.erb +3 -0
  32. data/app/views/blazer/uploads/index.html.erb +55 -0
  33. data/app/views/blazer/uploads/new.html.erb +3 -0
  34. data/config/routes.rb +5 -0
  35. data/lib/blazer.rb +20 -0
  36. data/lib/blazer/adapters/influxdb_adapter.rb +45 -0
  37. data/lib/blazer/adapters/sql_adapter.rb +1 -1
  38. data/lib/blazer/result.rb +2 -21
  39. data/lib/blazer/version.rb +1 -1
  40. data/lib/generators/blazer/templates/config.yml.tt +6 -0
  41. data/lib/generators/blazer/templates/install.rb.tt +4 -3
  42. data/lib/generators/blazer/templates/uploads.rb.tt +10 -0
  43. data/lib/generators/blazer/uploads_generator.rb +18 -0
  44. data/lib/tasks/blazer.rake +9 -0
  45. data/licenses/LICENSE-ace.txt +24 -0
  46. data/licenses/LICENSE-bootstrap.txt +21 -0
  47. data/licenses/LICENSE-chart.js.txt +9 -0
  48. data/licenses/LICENSE-chartkick.js.txt +22 -0
  49. data/licenses/LICENSE-daterangepicker.txt +21 -0
  50. data/licenses/LICENSE-fuzzysearch.txt +20 -0
  51. data/licenses/LICENSE-highlight.js.txt +29 -0
  52. data/licenses/LICENSE-jquery-ujs.txt +20 -0
  53. data/licenses/LICENSE-jquery.txt +20 -0
  54. data/licenses/LICENSE-moment-timezone.txt +20 -0
  55. data/licenses/LICENSE-moment.txt +22 -0
  56. data/licenses/LICENSE-selectize.txt +202 -0
  57. data/licenses/LICENSE-sortable.txt +21 -0
  58. data/licenses/LICENSE-stickytableheaders.txt +20 -0
  59. data/licenses/LICENSE-stupidtable.txt +19 -0
  60. data/licenses/LICENSE-vue.txt +21 -0
  61. metadata +33 -7
@@ -96,12 +96,12 @@ module Blazer
96
96
  # when permitted parameters are passed in Rails 6,
97
97
  # they appear to be added as GET parameters
98
98
  # root_url(params.permit(:host))
99
- BLACKLISTED_KEYS = [:controller, :action, :id, :host, :query, :dashboard, :query_id, :query_ids, :table_names, :authenticity_token, :utf8, :_method, :commit, :statement, :data_source, :name, :fork_query_id, :blazer, :run_id, :script_name, :original_script_name]
99
+ UNPERMITTED_KEYS = [:controller, :action, :id, :host, :query, :dashboard, :query_id, :query_ids, :table_names, :authenticity_token, :utf8, :_method, :commit, :statement, :data_source, :name, :fork_query_id, :blazer, :run_id, :script_name, :original_script_name]
100
100
 
101
- # remove blacklisted keys from both params and permitted keys for better sleep
101
+ # remove unpermitted keys from both params and permitted keys for better sleep
102
102
  def variable_params(resource)
103
- permitted_keys = resource.variables - BLACKLISTED_KEYS.map(&:to_s)
104
- params.except(*BLACKLISTED_KEYS).permit(*permitted_keys)
103
+ permitted_keys = resource.variables - UNPERMITTED_KEYS.map(&:to_s)
104
+ params.except(*UNPERMITTED_KEYS).slice(*permitted_keys).permit!
105
105
  end
106
106
  helper_method :variable_params
107
107
 
@@ -21,8 +21,11 @@ module Blazer
21
21
 
22
22
  def show
23
23
  @queries = @dashboard.dashboard_queries.order(:position).preload(:query).map(&:query)
24
+ @statements = []
24
25
  @queries.each do |query|
25
- process_vars(query.statement, query.data_source)
26
+ statement = query.statement.dup
27
+ process_vars(statement, query.data_source)
28
+ @statements << statement
26
29
  end
27
30
  @bind_vars ||= []
28
31
 
@@ -51,7 +54,7 @@ module Blazer
51
54
 
52
55
  def destroy
53
56
  @dashboard.destroy
54
- redirect_to dashboards_path
57
+ redirect_to root_path
55
58
  end
56
59
 
57
60
  def refresh
@@ -38,11 +38,18 @@ module Blazer
38
38
  if params[:fork_query_id]
39
39
  @query.statement ||= Blazer::Query.find(params[:fork_query_id]).try(:statement)
40
40
  end
41
+ if params[:upload_id]
42
+ upload = Blazer::Upload.find(params[:upload_id])
43
+ upload_settings = Blazer.settings["uploads"]
44
+ @query.data_source ||= upload_settings["data_source"]
45
+ @query.statement ||= "SELECT * FROM #{upload.table_name} LIMIT 10"
46
+ end
41
47
  end
42
48
 
43
49
  def create
44
50
  @query = Blazer::Query.new(query_params)
45
51
  @query.creator = blazer_user if @query.respond_to?(:creator)
52
+ @query.status = "active" if @query.respond_to?(:status)
46
53
 
47
54
  if @query.save
48
55
  redirect_to query_path(@query, variable_params(@query))
@@ -64,6 +71,8 @@ module Blazer
64
71
  @sql_errors << error if error
65
72
  end
66
73
 
74
+ @query.update!(status: "active") if @query.try(:status) == "archived"
75
+
67
76
  Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
68
77
  end
69
78
 
@@ -279,7 +288,7 @@ module Blazer
279
288
  @queries = queries_by_ids(Blazer::Audit.where(user_id: blazer_user.id).order(created_at: :desc).limit(500).pluck(:query_id).uniq)
280
289
  else
281
290
  @queries = @queries.limit(limit) if limit
282
- @queries = @queries.order(:name)
291
+ @queries = @queries.active.order(:name)
283
292
  end
284
293
  @queries = @queries.to_a
285
294
 
@@ -300,7 +309,7 @@ module Blazer
300
309
  end
301
310
 
302
311
  def queries_by_ids(favorite_query_ids)
303
- queries = Blazer::Query.named.where(id: favorite_query_ids)
312
+ queries = Blazer::Query.active.named.where(id: favorite_query_ids)
304
313
  queries = queries.includes(:creator) if Blazer.user_class
305
314
  queries = queries.index_by(&:id)
306
315
  favorite_query_ids.map { |query_id| queries[query_id] }.compact
@@ -0,0 +1,147 @@
1
+ module Blazer
2
+ class UploadsController < BaseController
3
+ before_action :ensure_uploads
4
+ before_action :set_upload, only: [:show, :edit, :update, :destroy]
5
+
6
+ def index
7
+ @uploads = Blazer::Upload.order(:table)
8
+ end
9
+
10
+ def new
11
+ @upload = Blazer::Upload.new
12
+ end
13
+
14
+ def create
15
+ @upload = Blazer::Upload.new(upload_params)
16
+ # use creator_id instead of creator
17
+ # since we setup association without checking if column exists
18
+ @upload.creator = blazer_user if @upload.respond_to?(:creator_id=) && blazer_user
19
+
20
+ success = params.require(:upload).key?(:file)
21
+ if success
22
+ Blazer::Upload.transaction do
23
+ success = @upload.save
24
+ if success
25
+ begin
26
+ update_file(@upload)
27
+ rescue CSV::MalformedCSVError, Blazer::UploadError => e
28
+ @upload.errors.add(:base, e.message)
29
+ success = false
30
+ raise ActiveRecord::Rollback
31
+ end
32
+ end
33
+ end
34
+ else
35
+ @upload.errors.add(:base, "File can't be blank")
36
+ end
37
+
38
+ if success
39
+ redirect_to upload_path(@upload)
40
+ else
41
+ render_errors @upload
42
+ end
43
+ end
44
+
45
+ def show
46
+ redirect_to new_query_path(upload_id: @upload.id)
47
+ end
48
+
49
+ def edit
50
+ end
51
+
52
+ def update
53
+ original_table = @upload.table
54
+ @upload.assign_attributes(upload_params)
55
+
56
+ success = nil
57
+ Blazer::Upload.transaction do
58
+ success = @upload.save
59
+ if success
60
+ if params.require(:upload).key?(:file)
61
+ begin
62
+ update_file(@upload, drop: original_table)
63
+ rescue CSV::MalformedCSVError, Blazer::UploadError => e
64
+ @upload.errors.add(:base, e.message)
65
+ success = false
66
+ raise ActiveRecord::Rollback
67
+ end
68
+ elsif @upload.table != original_table
69
+ Blazer.uploads_connection.execute("ALTER TABLE #{Blazer.uploads_table_name(original_table)} RENAME TO #{Blazer.uploads_connection.quote_table_name(@upload.table)}")
70
+ end
71
+ end
72
+ end
73
+
74
+ if success
75
+ redirect_to upload_path(@upload)
76
+ else
77
+ render_errors @upload
78
+ end
79
+ end
80
+
81
+ def destroy
82
+ Blazer.uploads_connection.execute("DROP TABLE IF EXISTS #{@upload.table_name}")
83
+ @upload.destroy
84
+ redirect_to uploads_path
85
+ end
86
+
87
+ private
88
+
89
+ def update_file(upload, drop: nil)
90
+ file = params.require(:upload).fetch(:file)
91
+ raise Blazer::UploadError, "File is not a CSV" if file.content_type != "text/csv"
92
+ raise Blazer::UploadError, "File is too large (maximum is 100MB)" if file.size > 100.megabytes
93
+
94
+ contents = file.read
95
+ rows = CSV.parse(contents, converters: %i[numeric date date_time])
96
+
97
+ # friendly column names
98
+ columns = rows.shift.map { |v| v.to_s.encode("UTF-8").gsub("%", " pct ").parameterize.gsub("-", "_") }
99
+ duplicate_column = columns.find { |c| columns.count(c) > 1 }
100
+ raise Blazer::UploadError, "Duplicate column name: #{duplicate_column}" if duplicate_column
101
+
102
+ column_types =
103
+ columns.size.times.map do |i|
104
+ values = rows.map { |r| r[i] }.uniq.compact
105
+ if values.all? { |v| v.is_a?(Integer) && v >= -9223372036854775808 && v <= 9223372036854775807 }
106
+ "bigint"
107
+ elsif values.all? { |v| v.is_a?(Numeric) }
108
+ "decimal"
109
+ elsif values.all? { |v| v.is_a?(DateTime) }
110
+ "timestamptz"
111
+ elsif values.all? { |v| v.is_a?(Date) }
112
+ "date"
113
+ else
114
+ "text"
115
+ end
116
+ end
117
+
118
+ begin
119
+ # maybe SET LOCAL statement_timeout = '30s'
120
+ # maybe regenerate CSV in Ruby to ensure consistent parsing
121
+ Blazer.uploads_connection.transaction do
122
+ Blazer.uploads_connection.execute("DROP TABLE IF EXISTS #{Blazer.uploads_table_name(drop)}") if drop
123
+ Blazer.uploads_connection.execute("CREATE TABLE #{upload.table_name} (#{columns.map.with_index { |c, i| "#{Blazer.uploads_connection.quote_column_name(c)} #{column_types[i]}" }.join(", ")})")
124
+ Blazer.uploads_connection.raw_connection.copy_data("COPY #{upload.table_name} FROM STDIN CSV HEADER") do
125
+ Blazer.uploads_connection.raw_connection.put_copy_data(contents)
126
+ end
127
+ end
128
+ rescue ActiveRecord::StatementInvalid => e
129
+ raise Blazer::UploadError, "Table already exists" if e.message.include?("PG::DuplicateTable")
130
+ raise e
131
+ end
132
+ end
133
+
134
+ def upload_params
135
+ params.require(:upload).except(:file).permit(:table, :description)
136
+ end
137
+
138
+ def set_upload
139
+ @upload = Blazer::Upload.find(params[:id])
140
+ end
141
+
142
+ # routes aren't added, but also check here
143
+ def ensure_uploads
144
+ render plain: "Uploads not enabled" unless Blazer.uploads?
145
+ end
146
+ end
147
+ end
@@ -62,7 +62,7 @@ module Blazer
62
62
 
63
63
  # checks shouldn't have variables, but in any case,
64
64
  # avoid passing variable params to url helpers
65
- # (known unsafe parameters are removed, but blacklist isn't ideal)
65
+ # (known unsafe parameters are removed, but still not ideal)
66
66
  def self.query_url(id)
67
67
  Blazer::Engine.routes.url_helpers.query_url(id, ActionMailer::Base.default_url_options)
68
68
  end
@@ -8,6 +8,7 @@ module Blazer
8
8
 
9
9
  validates :statement, presence: true
10
10
 
11
+ scope :active, -> { column_names.include?("status") ? where(status: "active") : all }
11
12
  scope :named, -> { where("blazer_queries.name <> ''") }
12
13
 
13
14
  def to_param
@@ -0,0 +1,11 @@
1
+ module Blazer
2
+ class Upload < Record
3
+ belongs_to :creator, optional: true, class_name: Blazer.user_class.to_s if Blazer.user_class
4
+
5
+ validates :table, presence: true, uniqueness: true, format: {with: /\A[a-z0-9_]+\z/, message: "can only contain lowercase letters, numbers, and underscores"}, length: {maximum: 63}
6
+
7
+ def table_name
8
+ Blazer.uploads_table_name(table)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module Blazer
2
+ class UploadsConnection < ActiveRecord::Base
3
+ self.abstract_class = true
4
+
5
+ establish_connection Blazer.settings["uploads"]["url"] if Blazer.uploads?
6
+ end
7
+ end
@@ -6,6 +6,9 @@
6
6
  </button>
7
7
  <ul class="dropdown-menu">
8
8
  <li><%= link_to "Checks", checks_path %></li>
9
+ <% if Blazer.uploads? %>
10
+ <li><%= link_to "Uploads", uploads_path %></li>
11
+ <% end %>
9
12
  <li role="separator" class="divider"></li>
10
13
  <li><%= link_to "New Query", new_query_path %></li>
11
14
  <li><%= link_to "New Dashboard", new_dashboard_path %></li>
@@ -42,6 +42,7 @@
42
42
  singleDatePicker: true,
43
43
  locale: {format: format},
44
44
  autoUpdateInput: false,
45
+ autoApply: true,
45
46
  startDate: input.val().length > 0 ? moment.tz(input.val(), timeZone) : now
46
47
  })
47
48
  // hack to start with empty date
@@ -95,7 +96,8 @@
95
96
  },
96
97
  startDate: dateStr(29),
97
98
  endDate: dateStr(),
98
- opens: "right"
99
+ opens: "right",
100
+ alwaysShowCalendars: true
99
101
  },
100
102
  function(start, end) {
101
103
  setTimeInputs(start, end)
@@ -13,7 +13,7 @@
13
13
  <%= f.select :query_id, [], {include_blank: true} %>
14
14
  </div>
15
15
  <script>
16
- <%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
16
+ <%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
17
17
  <%= blazer_js_var "items", [@check.query_id].compact %>
18
18
 
19
19
  $("#check_query_id").selectize({options: queries, items: items, highlight: false, maxOptions: 100}).parents(".hide").removeClass("hide");
@@ -10,6 +10,9 @@
10
10
  </button>
11
11
  <ul class="dropdown-menu">
12
12
  <li><%= link_to "Home", root_path %></li>
13
+ <% if Blazer.uploads? %>
14
+ <li><%= link_to "Uploads", uploads_path %></li>
15
+ <% end %>
13
16
  <li role="separator" class="divider"></li>
14
17
  <li><%= link_to "New Query", new_query_path %></li>
15
18
  <li><%= link_to "New Dashboard", new_dashboard_path %></li>
@@ -31,7 +31,7 @@
31
31
  <% end %>
32
32
 
33
33
  <script>
34
- <%= blazer_js_var "queries", Blazer::Query.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
34
+ <%= blazer_js_var "queries", Blazer::Query.active.named.order(:name).select("id, name").map { |q| {text: q.name, value: q.id} } %>
35
35
  <%= blazer_js_var "dashboardQueries", @queries || @dashboard.dashboard_queries.order(:position).map(&:query) %>
36
36
 
37
37
  var app = new Vue({
@@ -39,7 +39,7 @@
39
39
  </div>
40
40
  </div>
41
41
  <script>
42
- <%= blazer_js_var "data", {statement: query.statement, query_id: query.id, data_source: query.data_source, only_chart: true} %>
42
+ <%= blazer_js_var "data", {statement: @statements[i], query_id: query.id, data_source: query.data_source, only_chart: true} %>
43
43
 
44
44
  runQuery(data, function (data) {
45
45
  $("#chart-<%= i %>").html(data)
@@ -19,6 +19,9 @@
19
19
  </button>
20
20
  <ul class="dropdown-menu">
21
21
  <li><%= link_to "Checks", checks_path %></li>
22
+ <% if Blazer.uploads? %>
23
+ <li><%= link_to "Uploads", uploads_path %></li>
24
+ <% end %>
22
25
  <li role="separator" class="divider"></li>
23
26
  <li><%= link_to "New Dashboard", new_dashboard_path %></li>
24
27
  <li><%= link_to "New Check", new_check_path %></li>
@@ -107,13 +107,13 @@
107
107
  </script>
108
108
  <% elsif chart_type == "line" %>
109
109
  <% chart_data = @columns[1..-1].each_with_index.map{ |k, i| {name: blazer_series_name(k), data: @rows.map{ |r| [r[0], r[i + 1]] }, library: series_library[i]} } %>
110
- <%= line_chart chart_data, chart_options %>
110
+ <%= line_chart chart_data, **chart_options %>
111
111
  <% elsif chart_type == "line2" %>
112
- <%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, chart_options %>
112
+ <%= line_chart @rows.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.map { |v2| [v2[0], v2[2]] }, library: series_library[i]} }, **chart_options %>
113
113
  <% elsif chart_type == "pie" %>
114
- <%= pie_chart @rows.map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, chart_options %>
114
+ <%= pie_chart @rows.map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[1]] }, **chart_options %>
115
115
  <% elsif chart_type == "bar" %>
116
- <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, chart_options %>
116
+ <%= column_chart (values.size - 1).times.map { |i| name = @columns[i + 1]; {name: blazer_series_name(name), data: @rows.first(20).map { |r| [(@boom[@columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]] } } }, **chart_options %>
117
117
  <% elsif chart_type == "bar2" %>
118
118
  <% first_20 = @rows.group_by { |r| r[0] }.values.first(20).flatten(1) %>
119
119
  <% labels = first_20.map { |r| r[0] }.uniq %>
@@ -123,7 +123,7 @@
123
123
  <% first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s } %>
124
124
  <% end %>
125
125
  <% end %>
126
- <%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, chart_options %>
126
+ <%= column_chart first_20.group_by { |r| v = r[1]; (@boom[@columns[1]] || {})[v.to_s] || v }.each_with_index.map { |(name, v), i| {name: blazer_series_name(name), data: v.sort_by { |r2| labels.index(r2[0]) }.map { |v2| v3 = v2[0]; [(@boom[@columns[0]] || {})[v3.to_s] || v3, v2[2]] }} }, **chart_options %>
127
127
  <% elsif chart_type == "scatter" %>
128
128
  <%= scatter_chart @rows, xtitle: @columns[0], ytitle: @columns[1], **chart_options %>
129
129
  <% elsif @only_chart %>
@@ -56,7 +56,9 @@
56
56
  $("#results").addClass("query-error").html(message)
57
57
  }
58
58
 
59
- <%= blazer_js_var "data", variable_params(@query).merge(statement: @statement, query_id: @query.id, data_source: @query.data_source) %>
59
+ <% data = variable_params(@query).merge(statement: @statement, query_id: @query.id, data_source: @query.data_source) %>
60
+ <% data.merge!(forecast: "t") if params[:forecast] %>
61
+ <%= blazer_js_var "data", data %>
60
62
 
61
63
  runQuery(data, showRun, showError)
62
64
  </script>
@@ -0,0 +1,27 @@
1
+ <%= form_for @upload, html: {class: "small-form"} do |f| %>
2
+ <% if @upload.errors.any? %>
3
+ <div class="alert alert-danger"><%= @upload.errors.full_messages.first %></div>
4
+ <% elsif !@upload.persisted? %>
5
+ <p>Create a database table from a CSV file. The table will be created in the <code><%= Blazer.settings["uploads"]["schema"] %></code> schema.</p>
6
+ <% end %>
7
+
8
+ <div class="form-group">
9
+ <%= f.label :table %>
10
+ <%= f.text_field :table, class: "form-control" %>
11
+ </div>
12
+ <div class="form-group">
13
+ <%= f.label :description %>
14
+ <%= f.text_area :description, placeholder: "Optional", style: "height: 60px;", class: "form-control" %>
15
+ </div>
16
+ <div class="form-group">
17
+ <%= f.label :file %>
18
+ <%= f.file_field :file, accept: "text/csv", style: "margin-top: 6px; margin-bottom: 21px;" %>
19
+ </div>
20
+ <p>
21
+ <% if @upload.persisted? %>
22
+ <%= link_to "Delete", upload_path(@upload), method: :delete, "data-confirm" => "Are you sure?", class: "btn btn-danger" %>
23
+ <% end %>
24
+ <%= f.submit "Save", class: "btn btn-success" %>
25
+ <%= link_to "Back", :back, class: "btn btn-link" %>
26
+ </p>
27
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% blazer_title "Edit Upload" %>
2
+
3
+ <%= render partial: "form" %>
@@ -0,0 +1,55 @@
1
+ <% blazer_title "Uploads" %>
2
+
3
+ <div id="header">
4
+ <div class="pull-right" style="line-height: 34px;">
5
+ <div class="btn-group">
6
+ <%= link_to "New Upload", new_upload_path, class: "btn btn-info" %>
7
+ <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
8
+ <span class="caret"></span>
9
+ <span class="sr-only">Toggle Dropdown</span>
10
+ </button>
11
+ <ul class="dropdown-menu">
12
+ <li><%= link_to "Home", root_path %></li>
13
+ <li><%= link_to "Checks", checks_path %></li>
14
+ <li role="separator" class="divider"></li>
15
+ <li><%= link_to "New Query", new_query_path %></li>
16
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
17
+ <li><%= link_to "New Check", new_check_path %></li>
18
+ </ul>
19
+ </div>
20
+ </div>
21
+
22
+ <input id="search" type="text" placeholder="Start typing a table or person" style="width: 300px; display: inline-block;" class="search form-control" />
23
+ </div>
24
+
25
+ <table id="uploads" class="table">
26
+ <thead>
27
+ <tr>
28
+ <th>Table</th>
29
+ <th style="width: 60%;"></th>
30
+ <% if Blazer.user_class %>
31
+ <th style="width: 20%; text-align: right;">Mastermind</th>
32
+ <% end%>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ <% @uploads.each do |upload| %>
37
+ <tr>
38
+ <td><%= link_to upload.table, edit_upload_path(upload) %></td>
39
+ <td><%= truncate(upload.description, length: 100, separator: " ") %></td>
40
+ <% if Blazer.user_class %>
41
+ <td class="creator"><%= blazer_user && upload.creator == blazer_user ? "You" : upload.creator.try(Blazer.user_name) %></td>
42
+ <% end %>
43
+ </tr>
44
+ <% end %>
45
+ </tbody>
46
+ </table>
47
+
48
+ <script>
49
+ $("#search").on("keyup", function() {
50
+ var value = $(this).val().toLowerCase()
51
+ $("#uploads tbody tr").filter( function() {
52
+ $(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
53
+ })
54
+ }).focus()
55
+ </script>