flipper-ui 0.21.0 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
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