flipper-ui 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/examples/ui/authorization.ru +11 -49
  3. data/examples/ui/basic.ru +10 -10
  4. data/flipper-ui.gemspec +1 -0
  5. data/lib/flipper/ui/action.rb +50 -1
  6. data/lib/flipper/ui/actions/actors_gate.rb +11 -8
  7. data/lib/flipper/ui/actions/features.rb +2 -2
  8. data/lib/flipper/ui/actions/file.rb +1 -1
  9. data/lib/flipper/ui/actions/groups_gate.rb +1 -1
  10. data/lib/flipper/ui/actions/percentage_of_actors_gate.rb +1 -1
  11. data/lib/flipper/ui/actions/percentage_of_time_gate.rb +1 -1
  12. data/lib/flipper/ui/configuration.rb +6 -0
  13. data/lib/flipper/ui/decorators/feature.rb +3 -3
  14. data/lib/flipper/ui/middleware.rb +2 -1
  15. data/lib/flipper/ui/public/css/application.css +7 -0
  16. data/lib/flipper/ui/views/add_actor.erb +1 -1
  17. data/lib/flipper/ui/views/feature.erb +6 -6
  18. data/lib/flipper/ui/views/features.erb +2 -2
  19. data/lib/flipper/ui/views/layout.erb +5 -6
  20. data/lib/flipper/ui.rb +2 -2
  21. data/lib/flipper/version.rb +1 -1
  22. data/spec/flipper/ui/action_spec.rb +0 -2
  23. data/spec/flipper/ui/actions/actors_gate_spec.rb +70 -7
  24. data/spec/flipper/ui/actions/add_feature_spec.rb +0 -2
  25. data/spec/flipper/ui/actions/boolean_gate_spec.rb +18 -2
  26. data/spec/flipper/ui/actions/feature_spec.rb +18 -2
  27. data/spec/flipper/ui/actions/features_spec.rb +16 -5
  28. data/spec/flipper/ui/actions/file_spec.rb +0 -12
  29. data/spec/flipper/ui/actions/groups_gate_spec.rb +20 -5
  30. data/spec/flipper/ui/actions/home_spec.rb +0 -2
  31. data/spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb +18 -3
  32. data/spec/flipper/ui/actions/percentage_of_time_gate_spec.rb +18 -3
  33. data/spec/flipper/ui/configuration_spec.rb +0 -2
  34. data/spec/flipper/ui/decorators/feature_spec.rb +0 -2
  35. data/spec/flipper/ui/decorators/gate_spec.rb +0 -1
  36. data/spec/flipper/ui/util_spec.rb +0 -1
  37. data/spec/flipper/ui_spec.rb +0 -15
  38. metadata +19 -18
  39. data/docs/ui/README.md +0 -190
  40. data/docs/ui/images/banner.png +0 -0
  41. data/docs/ui/images/description.png +0 -0
  42. data/docs/ui/images/feature.png +0 -0
  43. data/docs/ui/images/features.png +0 -0
  44. data/lib/flipper/ui/public/octicons/LICENSE.txt +0 -9
  45. data/lib/flipper/ui/public/octicons/README.md +0 -1
  46. data/lib/flipper/ui/public/octicons/octicons-local.ttf +0 -0
  47. data/lib/flipper/ui/public/octicons/octicons.css +0 -236
  48. data/lib/flipper/ui/public/octicons/octicons.eot +0 -0
  49. data/lib/flipper/ui/public/octicons/octicons.svg +0 -200
  50. data/lib/flipper/ui/public/octicons/octicons.ttf +0 -0
  51. data/lib/flipper/ui/public/octicons/octicons.woff +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd4963986e65fb20081d23374099f2a054df7e78a81e2571ad3640831b7d0d81
4
- data.tar.gz: b94fc64dcf6752a2951c8c03bd6161c46e4850f206ebb049f4aa8b0a5b20e881
3
+ metadata.gz: 3a6e8757e6a2726e4cf55323286a04caf186af13c6841a4e8b82b813b3c69f2f
4
+ data.tar.gz: 9ea49e24c52b723f7385da8c90dcae9daf96bef269f0b0e6066df1bc11065411
5
5
  SHA512:
6
- metadata.gz: c46dbab449279c506b9d5ce1d2869392b8e4f0f6b8dab59b527b64a2acf25504cb5fb3e8af2e2601176f0f7c026afdf547196d197d4d0cfad0031e8a26f270bc
7
- data.tar.gz: 95ed1aefbe02cdac6f8e938107a00a41e2e8754fa20351fa35948942557746d863d542c52b3993846828ce327b52aa4c151e1422997baea0e8099ff4c3580988
6
+ metadata.gz: c7e435cc10e1b05c2282c022d977666479de870dc14cad3914e19e17acb6eacaf644fe37bf751f2931fb26525079a07b00b374f4815afdd10ab0e4192448ad27
7
+ data.tar.gz: a3a8fa7a1be586667573b98ee311a157f23591ee4f7bad15b1638aa25a1ec6444f75c909f90cd331d9cd17d8ba8869367755c299a9798e6eedef8583c2833dc7
@@ -5,50 +5,13 @@
5
5
  # http://localhost:9999/
6
6
  #
7
7
  require 'bundler/setup'
8
- require "logger"
9
-
10
8
  require "flipper/ui"
11
9
  require "flipper/adapters/pstore"
12
- require "active_support/notifications"
13
10
 
14
11
  Flipper.register(:admins) { |actor|
15
12
  actor.respond_to?(:admin?) && actor.admin?
16
13
  }
17
14
 
18
- Flipper.register(:early_access) { |actor|
19
- actor.respond_to?(:early?) && actor.early?
20
- }
21
-
22
- # Setup logging of flipper calls.
23
- if ENV["LOG"] == "1"
24
- $logger = Logger.new(STDOUT)
25
- require "flipper/instrumentation/log_subscriber"
26
- Flipper::Instrumentation::LogSubscriber.logger = $logger
27
- end
28
-
29
- adapter = Flipper::Adapters::PStore.new
30
- flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
31
-
32
- Flipper::UI.configure do |config|
33
- # config.banner_text = 'Production Environment'
34
- # config.banner_class = 'danger'
35
- config.feature_creation_enabled = true
36
- config.feature_removal_enabled = true
37
- # config.show_feature_description_in_list = true
38
- config.descriptions_source = lambda do |_keys|
39
- {
40
- "search_performance_another_long_thing" => "Just to test feature name length.",
41
- "gauges_tracking" => "Should we track page views with gaug.es.",
42
- "unused" => "Not used.",
43
- "suits" => "Are suits necessary in business?",
44
- "secrets" => "Secrets are lies.",
45
- "logging" => "Log all the things.",
46
- "new_cache" => "Like the old cache but newer.",
47
- "a/b" => "Why would someone use a slash? I don't know but someone did. Let's make this really long so they regret using slashes. Please don't use slashes.",
48
- }
49
- end
50
- end
51
-
52
15
  # Example middleware to allow reading the Flipper UI but nothing else.
53
16
  class FlipperReadOnlyMiddleware
54
17
  def initialize(app)
@@ -67,18 +30,17 @@ class FlipperReadOnlyMiddleware
67
30
  end
68
31
 
69
32
  # You can uncomment these to get some default data:
70
- # flipper[:search_performance_another_long_thing].enable
71
- # flipper[:gauges_tracking].enable
72
- # flipper[:unused].disable
73
- # flipper[:suits].enable_actor Flipper::Actor.new('1')
74
- # flipper[:suits].enable_actor Flipper::Actor.new('6')
75
- # flipper[:secrets].enable_group :admins
76
- # flipper[:secrets].enable_group :early_access
77
- # flipper[:logging].enable_percentage_of_time 5
78
- # flipper[:new_cache].enable_percentage_of_actors 15
79
- # flipper["something/slashed"].add
80
-
81
- run Flipper::UI.app(flipper) { |builder|
33
+ # Flipper.enable(:search_performance_another_long_thing)
34
+ # Flipper.disable(:gauges_tracking)
35
+ # Flipper.disable(:unused)
36
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
37
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
38
+ # Flipper.enable_group(:secrets, :admins)
39
+ # Flipper.enable_percentage_of_time(:logging, 5)
40
+ # Flipper.enable_percentage_of_actors(:new_cache, 15)
41
+ # Flipper.add("a/b")
42
+
43
+ run Flipper::UI.app { |builder|
82
44
  builder.use Rack::Session::Cookie, secret: "_super_secret"
83
45
  builder.use FlipperReadOnlyMiddleware
84
46
  }
data/examples/ui/basic.ru CHANGED
@@ -42,16 +42,16 @@ Flipper::UI.configure do |config|
42
42
  end
43
43
 
44
44
  # You can uncomment these to get some default data:
45
- # flipper[:search_performance_another_long_thing].enable
46
- # flipper[:gauges_tracking].enable
47
- # flipper[:unused].disable
48
- # flipper[:suits].enable_actor Flipper::Actor.new('1')
49
- # flipper[:suits].enable_actor Flipper::Actor.new('6')
50
- # flipper[:secrets].enable_group :admins
51
- # flipper[:secrets].enable_group :early_access
52
- # flipper[:logging].enable_percentage_of_time 5
53
- # flipper[:new_cache].enable_percentage_of_actors 15
54
- # flipper["a/b"].add
45
+ # Flipper.enable(:search_performance_another_long_thing)
46
+ # Flipper.disable(:gauges_tracking)
47
+ # Flipper.disable(:unused)
48
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
49
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
50
+ # Flipper.enable_group(:secrets, :admins)
51
+ # Flipper.enable_group(:secrets, :early_access)
52
+ # Flipper.enable_percentage_of_time(:logging, 5)
53
+ # Flipper.enable_percentage_of_actors(:new_cache, 15)
54
+ # Flipper.add("a/b")
55
55
 
56
56
  run Flipper::UI.app { |builder|
57
57
  builder.use Rack::Session::Cookie, secret: "_super_secret"
data/flipper-ui.gemspec CHANGED
@@ -24,4 +24,5 @@ Gem::Specification.new do |gem|
24
24
  gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.2.0'
25
25
  gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
26
26
  gem.add_dependency 'erubi', '>= 1.0.0', '< 2.0.0'
27
+ gem.add_dependency 'sanitize', '< 7'
27
28
  end
@@ -3,6 +3,7 @@ require 'flipper/ui/configuration'
3
3
  require 'flipper/ui/error'
4
4
  require 'erubi'
5
5
  require 'json'
6
+ require 'sanitize'
6
7
 
7
8
  module Flipper
8
9
  module UI
@@ -26,6 +27,36 @@ module Flipper
26
27
  'delete'.freeze,
27
28
  ]).freeze
28
29
 
30
+ SOURCES = {
31
+ bootstrap_css: {
32
+ src: 'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css'.freeze,
33
+ hash: 'sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l'.freeze
34
+ }.freeze,
35
+ jquery_js: {
36
+ src: 'https://code.jquery.com/jquery-3.6.0.slim.js'.freeze,
37
+ hash: 'sha256-HwWONEZrpuoh951cQD1ov2HUK5zA5DwJ1DNUXaM6FsY='.freeze
38
+ }.freeze,
39
+ popper_js: {
40
+ src: 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js'.freeze,
41
+ hash: 'sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q'.freeze
42
+ }.freeze,
43
+ bootstrap_js: {
44
+ src: 'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.min.js'.freeze,
45
+ hash: 'sha384-+YQ4JLhjyBLPDQt//I+STsc9iw4uQqACwlvpslubQzn4u2UU2UFM80nGisd026JF'.freeze
46
+ }.freeze
47
+ }.freeze
48
+ SCRIPT_SRCS = SOURCES.values_at(:jquery_js, :popper_js, :bootstrap_js).map { |s| s[:src] }
49
+ STYLE_SRCS = SOURCES.values_at(:bootstrap_css).map { |s| s[:src] }
50
+ CONTENT_SECURITY_POLICY = <<-CSP.delete("\n")
51
+ default-src 'none';
52
+ img-src 'self';
53
+ font-src 'self';
54
+ script-src 'report-sample' 'self' #{SCRIPT_SRCS.join(' ')};
55
+ style-src 'self' 'unsafe-inline' #{STYLE_SRCS.join(' ')};
56
+ style-src-attr 'unsafe-inline' ;
57
+ style-src-elem 'self' #{STYLE_SRCS.join(' ')};
58
+ CSP
59
+
29
60
  # Public: Call this in subclasses so the action knows its route.
30
61
  #
31
62
  # regex - The Regexp that this action should run for.
@@ -130,6 +161,7 @@ module Flipper
130
161
  # Returns a response.
131
162
  def view_response(name)
132
163
  header 'Content-Type', 'text/html'
164
+ header 'Content-Security-Policy', CONTENT_SECURITY_POLICY
133
165
  body = view_with_layout { view_without_layout name }
134
166
  halt [@code, @headers, [body]]
135
167
  end
@@ -151,7 +183,7 @@ module Flipper
151
183
  # location - The String location to set the Location header to.
152
184
  def redirect_to(location)
153
185
  status 302
154
- header 'Location', "#{script_name}#{location}"
186
+ header 'Location', "#{script_name}#{Rack::Utils.escape_path(location)}"
155
187
  halt [@code, @headers, ['']]
156
188
  end
157
189
 
@@ -207,6 +239,7 @@ module Flipper
207
239
  def view(name)
208
240
  path = views_path.join("#{name}.erb")
209
241
  raise "Template does not exist: #{path}" unless path.exist?
242
+
210
243
  eval(Erubi::Engine.new(path.read, escape: true).src)
211
244
  end
212
245
 
@@ -237,6 +270,22 @@ module Flipper
237
270
  def valid_request_method?
238
271
  VALID_REQUEST_METHOD_NAMES.include?(request_method_name)
239
272
  end
273
+
274
+ def bootstrap_css
275
+ SOURCES[:bootstrap_css]
276
+ end
277
+
278
+ def bootstrap_js
279
+ SOURCES[:bootstrap_js]
280
+ end
281
+
282
+ def popper_js
283
+ SOURCES[:popper_js]
284
+ end
285
+
286
+ def jquery_js
287
+ SOURCES[:jquery_js]
288
+ end
240
289
  end
241
290
  end
242
291
  end
@@ -25,19 +25,22 @@ module Flipper
25
25
  def post
26
26
  feature = flipper[feature_name]
27
27
  value = params['value'].to_s.strip
28
+ values = value.split(UI.configuration.actors_separator).map(&:strip).uniq
28
29
 
29
- if Util.blank?(value)
30
- error = Rack::Utils.escape("#{value.inspect} is not a valid actor value.")
30
+ if values.empty?
31
+ error = "#{value.inspect} is not a valid actor value."
31
32
  redirect_to("/features/#{feature.key}/actors?error=#{error}")
32
33
  end
33
34
 
34
- actor = Flipper::Actor.new(value)
35
+ values.each do |value|
36
+ actor = Flipper::Actor.new(value)
35
37
 
36
- case params['operation']
37
- when 'enable'
38
- feature.enable_actor actor
39
- when 'disable'
40
- feature.disable_actor actor
38
+ case params['operation']
39
+ when 'enable'
40
+ feature.enable_actor actor
41
+ when 'disable'
42
+ feature.disable_actor actor
43
+ end
41
44
  end
42
45
 
43
46
  redirect_to("/features/#{feature.key}")
@@ -49,14 +49,14 @@ module Flipper
49
49
  value = params['value'].to_s.strip
50
50
 
51
51
  if Util.blank?(value)
52
- error = Rack::Utils.escape("#{value.inspect} is not a valid feature name.")
52
+ error = "#{value.inspect} is not a valid feature name."
53
53
  redirect_to("/features/new?error=#{error}")
54
54
  end
55
55
 
56
56
  feature = flipper[value]
57
57
  feature.add
58
58
 
59
- redirect_to "/features/#{Rack::Utils.escape_path(value)}"
59
+ redirect_to "/features/#{value}"
60
60
  end
61
61
  end
62
62
  end
@@ -5,7 +5,7 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class File < UI::Action
8
- route %r{(images|css|js|octicons)/.*\Z}
8
+ route %r{(images|css|js)/.*\Z}
9
9
 
10
10
  def get
11
11
  Rack::File.new(public_path).call(request.env)
@@ -35,7 +35,7 @@ module Flipper
35
35
 
36
36
  redirect_to("/features/#{feature.key}")
37
37
  else
38
- error = Rack::Utils.escape("The group named #{value.inspect} has not been registered.")
38
+ error = "The group named #{value.inspect} has not been registered."
39
39
  redirect_to("/features/#{feature.key}/groups?error=#{error}")
40
40
  end
41
41
  end
@@ -16,7 +16,7 @@ module Flipper
16
16
  begin
17
17
  feature.enable_percentage_of_actors params['value']
18
18
  rescue ArgumentError => exception
19
- error = Rack::Utils.escape("Invalid percentage of actors value: #{exception.message}")
19
+ error = "Invalid percentage of actors value: #{exception.message}"
20
20
  redirect_to("/features/#{@feature.key}?error=#{error}")
21
21
  end
22
22
 
@@ -16,7 +16,7 @@ module Flipper
16
16
  begin
17
17
  feature.enable_percentage_of_time params['value']
18
18
  rescue ArgumentError => exception
19
- error = Rack::Utils.escape("Invalid percentage of time value: #{exception.message}")
19
+ error = "Invalid percentage of time value: #{exception.message}"
20
20
  redirect_to("/features/#{@feature.key}?error=#{error}")
21
21
  end
22
22
 
@@ -44,6 +44,11 @@ module Flipper
44
44
  # Default false. Only works when using descriptions.
45
45
  attr_accessor :show_feature_description_in_list
46
46
 
47
+ # Public: What should be used to denote you are trying to add multiple
48
+ # actors at once (instead of just a single actor).
49
+ # Default is comma ",".
50
+ attr_accessor :actors_separator
51
+
47
52
  VALID_BANNER_CLASS_VALUES = %w(
48
53
  danger
49
54
  dark
@@ -68,6 +73,7 @@ module Flipper
68
73
  @add_actor_placeholder = "a flipper id"
69
74
  @descriptions_source = DEFAULT_DESCRIPTIONS_SOURCE
70
75
  @show_feature_description_in_list = false
76
+ @actors_separator = ','
71
77
  end
72
78
 
73
79
  def using_descriptions?
@@ -23,11 +23,11 @@ module Flipper
23
23
  def color_class
24
24
  case feature.state
25
25
  when :on
26
- 'text-success'
26
+ 'bg-success'
27
27
  when :off
28
- 'text-danger'
28
+ 'bg-danger'
29
29
  when :conditional
30
- 'text-warning'
30
+ 'bg-warning'
31
31
  end
32
32
  end
33
33
 
@@ -12,6 +12,7 @@ module Flipper
12
12
  def initialize(app, options = {})
13
13
  @app = app
14
14
  @env_key = options.fetch(:env_key, 'flipper')
15
+ @flipper = options.fetch(:flipper) { Flipper }
15
16
 
16
17
  @action_collection = ActionCollection.new
17
18
 
@@ -43,7 +44,7 @@ module Flipper
43
44
  if action_class.nil?
44
45
  @app.call(env)
45
46
  else
46
- flipper = env.fetch(@env_key)
47
+ flipper = env.fetch(@env_key) { Flipper }
47
48
  action_class.run(flipper, request)
48
49
  end
49
50
  end
@@ -29,3 +29,10 @@ html {
29
29
  .toggle-on .toggle-block-when-off {
30
30
  display: none;
31
31
  }
32
+
33
+ .status {
34
+ height: 10px;
35
+ width: 10px;
36
+ border-radius: 50%;
37
+ display: inline-block;
38
+ }
@@ -8,7 +8,7 @@
8
8
  <h4 class="card-header">Enable Actor for <%= @feature.key %></h4>
9
9
  <div class="card-body">
10
10
  <p>
11
- Turn on this feature for an individual actor.
11
+ Turn on this feature for actors.
12
12
  </p>
13
13
  <form action="<%= script_name %>/features/<%= @feature.key %>/actors" method="post" class="form-inline">
14
14
  <%== csrf_input_tag %>
@@ -9,7 +9,7 @@
9
9
  <div class="col">
10
10
  <div class="card">
11
11
  <div class="card-body">
12
- <%= @feature.description %>
12
+ <%== Sanitize.fragment(@feature.description, Sanitize::Config::BASIC) %>
13
13
  </div>
14
14
  </div>
15
15
  </div>
@@ -21,7 +21,7 @@
21
21
  <div class="card">
22
22
  <%# Gate State Header %>
23
23
  <div class="card-header">
24
- <span class="octicon octicon-squirrel <%= @feature.color_class %> mr-2"></span>
24
+ <span class="status <%= @feature.color_class %> mr-2"></span>
25
25
  <%= @feature.gate_state_title %>
26
26
  </div>
27
27
 
@@ -69,8 +69,8 @@
69
69
  <%== csrf_input_tag %>
70
70
  <input type="hidden" name="operation" value="disable">
71
71
  <input type="hidden" name="value" value="<%= item %>">
72
- <button type="submit" value="Disable" class="btn btn-link btn-sm text-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
73
- <span class="octicon octicon-trashcan"></span>
72
+ <button type="submit" value="Disable" class="btn btn-outline-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
73
+ Remove
74
74
  </button>
75
75
  </form>
76
76
  </div>
@@ -130,8 +130,8 @@
130
130
  <%== csrf_input_tag %>
131
131
  <input type="hidden" name="operation" value="disable">
132
132
  <input type="hidden" name="value" value="<%= item %>">
133
- <button type="submit" value="Disable" class="btn btn-link btn-sm text-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
134
- <span class="octicon octicon-trashcan"></span>
133
+ <button type="submit" value="Disable" class="btn btn-outline-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
134
+ Remove
135
135
  </button>
136
136
  </form>
137
137
  </div>
@@ -40,14 +40,14 @@
40
40
  <% @features.each do |feature| %>
41
41
  <div class="feature row align-items-center mt-0 px-3 border-bottom">
42
42
  <div class="col-1 col-md-auto">
43
- <span class="octicon octicon-squirrel <%= feature.color_class %>" data-toggle="tooltip" title=<%= feature.state.to_s.capitalize %>></span>
43
+ <span class="status <%= feature.color_class %>" data-toggle="tooltip" title=<%= feature.state.to_s.capitalize %>></span>
44
44
  </div>
45
45
  <div class="col-10">
46
46
  <a href="<%= "#{script_name}/features/#{feature.key}" %>" class="d-block px-0 py-3 btn text-left text-dark">
47
47
  <div class="text-truncate" style="font-weight: 500"><%= feature.key %></div>
48
48
  <% if Flipper::UI.configuration.show_feature_description_in_list? && Flipper::UI::Util.present?(feature.description) %>
49
49
  <div class="text-muted font-weight-light" style="line-height: 1.4; white-space: initial; padding: 8px 0">
50
- <%= feature.description %>
50
+ <%== Sanitize.fragment(feature.description, Sanitize::Config::BASIC) %>
51
51
  </div>
52
52
  <% end %>
53
53
  <div class="text-muted text-truncate">
@@ -6,15 +6,14 @@
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
8
8
 
9
- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
10
- <link rel="stylesheet" href="<%= script_name %>/octicons/octicons.css">
9
+ <link rel="stylesheet" href="<%= bootstrap_css[:src] %>" integrity="<%= bootstrap_css[:hash] %>" crossorigin="anonymous">
11
10
  <link rel="stylesheet" href="<%= script_name %>/css/application.css">
12
11
  </head>
13
12
  <body class="py-4">
14
13
  <div class="container mw-600">
15
14
  <%- unless Flipper::UI.configuration.banner_text.nil? -%>
16
15
  <div class="alert alert-<%= Flipper::UI.configuration.banner_class %> text-center font-weight-bold">
17
- <%= Flipper::UI.configuration.banner_text %>
16
+ <%== Sanitize.fragment(Flipper::UI.configuration.banner_text, Sanitize::Config::BASIC) %>
18
17
  </div>
19
18
  <%- end -%>
20
19
 
@@ -52,9 +51,9 @@
52
51
  <% end %>
53
52
  </div>
54
53
 
55
- <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
56
- <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
57
- <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
54
+ <script src="<%= jquery_js[:src] %>" integrity="<%= jquery_js[:hash] %>" crossorigin="anonymous"></script>
55
+ <script src="<%= popper_js[:src] %>" integrity="<%= popper_js[:hash] %>" crossorigin="anonymous"></script>
56
+ <script src="<%= bootstrap_js[:src] %>" integrity="<%= bootstrap_js[:hash] %>" crossorigin="anonymous"></script>
58
57
  <script src="<%= script_name %>/js/application.js"></script>
59
58
  </body>
60
59
  </html>
data/lib/flipper/ui.rb CHANGED
@@ -39,14 +39,14 @@ module Flipper
39
39
  def self.app(flipper = nil, options = {})
40
40
  env_key = options.fetch(:env_key, 'flipper')
41
41
  rack_protection_options = options.fetch(:rack_protection, use: :authenticity_token)
42
+
42
43
  app = ->(_) { [200, { 'Content-Type' => 'text/html' }, ['']] }
43
44
  builder = Rack::Builder.new
44
45
  yield builder if block_given?
45
46
  builder.use Rack::Protection, rack_protection_options
46
47
  builder.use Rack::MethodOverride
47
48
  builder.use Flipper::Middleware::SetupEnv, flipper, env_key: env_key
48
- builder.use Flipper::Middleware::Memoizer, env_key: env_key
49
- builder.use Flipper::UI::Middleware, env_key: env_key
49
+ builder.use Flipper::UI::Middleware, flipper: flipper, env_key: env_key
50
50
  builder.run app
51
51
  klass = self
52
52
  builder.define_singleton_method(:inspect) { klass.inspect } # pretty rake routes output
@@ -1,3 +1,3 @@
1
1
  module Flipper
2
- VERSION = '0.21.0'.freeze
2
+ VERSION = '0.23.0'.freeze
3
3
  end
@@ -1,5 +1,3 @@
1
- require 'helper'
2
-
3
1
  RSpec.describe Flipper::UI::Action do
4
2
  describe 'request methods' do
5
3
  let(:action_subclass) do