flipper-ui 0.20.0 → 0.25.2
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.
- checksums.yaml +4 -4
- data/examples/ui/authorization.ru +46 -0
- data/examples/ui/basic.ru +21 -34
- data/flipper-ui.gemspec +2 -1
- data/lib/flipper/ui/action.rb +50 -1
- data/lib/flipper/ui/actions/actors_gate.rb +11 -8
- data/lib/flipper/ui/actions/features.rb +2 -2
- data/lib/flipper/ui/actions/file.rb +1 -1
- data/lib/flipper/ui/actions/groups_gate.rb +1 -1
- data/lib/flipper/ui/actions/percentage_of_actors_gate.rb +1 -1
- data/lib/flipper/ui/actions/percentage_of_time_gate.rb +1 -1
- data/lib/flipper/ui/configuration.rb +11 -0
- data/lib/flipper/ui/decorators/feature.rb +3 -3
- data/lib/flipper/ui/middleware.rb +2 -1
- data/lib/flipper/ui/public/css/application.css +7 -0
- data/lib/flipper/ui/public/images/logo.png +0 -0
- data/lib/flipper/ui/public/js/application.js +15 -4
- data/lib/flipper/ui/views/add_actor.erb +1 -1
- data/lib/flipper/ui/views/feature.erb +9 -9
- data/lib/flipper/ui/views/features.erb +3 -6
- data/lib/flipper/ui/views/layout.erb +20 -6
- data/lib/flipper/ui.rb +5 -22
- data/lib/flipper/version.rb +1 -1
- data/spec/flipper/ui/action_spec.rb +0 -2
- data/spec/flipper/ui/actions/actors_gate_spec.rb +70 -7
- data/spec/flipper/ui/actions/add_feature_spec.rb +0 -2
- data/spec/flipper/ui/actions/boolean_gate_spec.rb +18 -2
- data/spec/flipper/ui/actions/feature_spec.rb +18 -2
- data/spec/flipper/ui/actions/features_spec.rb +16 -5
- data/spec/flipper/ui/actions/file_spec.rb +0 -12
- data/spec/flipper/ui/actions/groups_gate_spec.rb +20 -5
- data/spec/flipper/ui/actions/home_spec.rb +0 -2
- data/spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb +18 -3
- data/spec/flipper/ui/actions/percentage_of_time_gate_spec.rb +18 -3
- data/spec/flipper/ui/configuration_spec.rb +11 -2
- data/spec/flipper/ui/decorators/feature_spec.rb +0 -2
- data/spec/flipper/ui/decorators/gate_spec.rb +0 -1
- data/spec/flipper/ui/util_spec.rb +0 -1
- data/spec/flipper/ui_spec.rb +1 -37
- metadata +28 -26
- data/docs/ui/README.md +0 -190
- data/docs/ui/images/banner.png +0 -0
- data/docs/ui/images/description.png +0 -0
- data/docs/ui/images/feature.png +0 -0
- data/docs/ui/images/features.png +0 -0
- data/lib/flipper/ui/public/octicons/LICENSE.txt +0 -9
- data/lib/flipper/ui/public/octicons/README.md +0 -1
- data/lib/flipper/ui/public/octicons/octicons-local.ttf +0 -0
- data/lib/flipper/ui/public/octicons/octicons.css +0 -236
- data/lib/flipper/ui/public/octicons/octicons.eot +0 -0
- data/lib/flipper/ui/public/octicons/octicons.svg +0 -200
- data/lib/flipper/ui/public/octicons/octicons.ttf +0 -0
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca054fcd02084bba5c6217471a0c2113b8325b5e3e7763b5eab3723156b245d7
|
4
|
+
data.tar.gz: 156f7fec4900a85a7473e225d4e688be1621e59ac854f4d68a033fc85ae953bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f2fa40777f1d9dbe2aa9e73a28f212f37d7dfd511a0409c928960ee815df6731d5afb990b3645c33faf55245238b70ccc34a56d07b0625228a30f170fd87487
|
7
|
+
data.tar.gz: 6f36a16dbbb178f78a193d25cdae9dc523b2ae8d069cce1e12e73432ab3ac749b9089e16b075cb44f16b4ed13e78c003be8825eeb4ae4384d73066acf0876bbe
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# Usage:
|
3
|
+
# bundle exec rackup examples/ui/authorization.ru -p 9999
|
4
|
+
# bundle exec shotgun examples/ui/authorization.ru -p 9999
|
5
|
+
# http://localhost:9999/
|
6
|
+
#
|
7
|
+
require 'bundler/setup'
|
8
|
+
require "flipper/ui"
|
9
|
+
require "flipper/adapters/pstore"
|
10
|
+
|
11
|
+
Flipper.register(:admins) { |actor|
|
12
|
+
actor.respond_to?(:admin?) && actor.admin?
|
13
|
+
}
|
14
|
+
|
15
|
+
# Example middleware to allow reading the Flipper UI but nothing else.
|
16
|
+
class FlipperReadOnlyMiddleware
|
17
|
+
def initialize(app)
|
18
|
+
@app = app
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
request = Rack::Request.new(env)
|
23
|
+
|
24
|
+
if request.get?
|
25
|
+
@app.call(env)
|
26
|
+
else
|
27
|
+
[401, {}, ["You can only look"]]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# You can uncomment these to get some default data:
|
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|
|
44
|
+
builder.use Rack::Session::Cookie, secret: "_super_secret"
|
45
|
+
builder.use FlipperReadOnlyMiddleware
|
46
|
+
}
|
data/examples/ui/basic.ru
CHANGED
@@ -1,20 +1,16 @@
|
|
1
1
|
#
|
2
2
|
# Usage:
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# # if you want it to not reload and be really fast
|
4
|
+
# bin/rackup examples/ui/basic.ru -p 9999
|
5
|
+
#
|
6
|
+
# # if you want reloading
|
7
|
+
# bin/shotgun examples/ui/basic.ru -p 9999
|
8
|
+
#
|
5
9
|
# http://localhost:9999/
|
6
10
|
#
|
7
|
-
require
|
8
|
-
require "
|
9
|
-
require "pathname"
|
10
|
-
|
11
|
-
root_path = Pathname(__FILE__).dirname.join("..").expand_path
|
12
|
-
lib_path = root_path.join("lib")
|
13
|
-
$:.unshift(lib_path)
|
14
|
-
|
15
|
-
require "flipper-ui"
|
11
|
+
require 'bundler/setup'
|
12
|
+
require "flipper/ui"
|
16
13
|
require "flipper/adapters/pstore"
|
17
|
-
require "active_support/notifications"
|
18
14
|
|
19
15
|
Flipper.register(:admins) { |actor|
|
20
16
|
actor.respond_to?(:admin?) && actor.admin?
|
@@ -24,21 +20,12 @@ Flipper.register(:early_access) { |actor|
|
|
24
20
|
actor.respond_to?(:early?) && actor.early?
|
25
21
|
}
|
26
22
|
|
27
|
-
# Setup logging of flipper calls.
|
28
|
-
if ENV["LOG"] == "1"
|
29
|
-
$logger = Logger.new(STDOUT)
|
30
|
-
require "flipper/instrumentation/log_subscriber"
|
31
|
-
Flipper::Instrumentation::LogSubscriber.logger = $logger
|
32
|
-
end
|
33
|
-
|
34
|
-
adapter = Flipper::Adapters::PStore.new
|
35
|
-
flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
|
36
|
-
|
37
23
|
Flipper::UI.configure do |config|
|
38
24
|
# config.banner_text = 'Production Environment'
|
39
25
|
# config.banner_class = 'danger'
|
40
26
|
config.feature_creation_enabled = true
|
41
27
|
config.feature_removal_enabled = true
|
28
|
+
config.cloud_recommendation = true
|
42
29
|
# config.show_feature_description_in_list = true
|
43
30
|
config.descriptions_source = lambda do |_keys|
|
44
31
|
{
|
@@ -55,17 +42,17 @@ Flipper::UI.configure do |config|
|
|
55
42
|
end
|
56
43
|
|
57
44
|
# You can uncomment these to get some default data:
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
run Flipper::UI.app
|
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
|
+
|
56
|
+
run Flipper::UI.app { |builder|
|
70
57
|
builder.use Rack::Session::Cookie, secret: "_super_secret"
|
71
58
|
}
|
data/flipper-ui.gemspec
CHANGED
@@ -21,7 +21,8 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.metadata = Flipper::METADATA
|
22
22
|
|
23
23
|
gem.add_dependency 'rack', '>= 1.4', '< 3'
|
24
|
-
gem.add_dependency 'rack-protection', '>= 1.5.3', '
|
24
|
+
gem.add_dependency 'rack-protection', '>= 1.5.3', '<= 2.3.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
|
data/lib/flipper/ui/action.rb
CHANGED
@@ -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
|
30
|
-
error =
|
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
|
-
|
35
|
+
values.each do |value|
|
36
|
+
actor = Flipper::Actor.new(value)
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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 =
|
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/#{
|
59
|
+
redirect_to "/features/#{value}"
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
@@ -35,7 +35,7 @@ module Flipper
|
|
35
35
|
|
36
36
|
redirect_to("/features/#{feature.key}")
|
37
37
|
else
|
38
|
-
error =
|
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 =
|
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 =
|
19
|
+
error = "Invalid percentage of time value: #{exception.message}"
|
20
20
|
redirect_to("/features/#{@feature.key}?error=#{error}")
|
21
21
|
end
|
22
22
|
|
@@ -26,6 +26,10 @@ module Flipper
|
|
26
26
|
# won't see a videoclip of Taylor Swift when there aren't any features
|
27
27
|
attr_accessor :fun
|
28
28
|
|
29
|
+
# Public: Tired of seeing the awesome message about Cloud? Set this to
|
30
|
+
# false and it will go away. Defaults to true.
|
31
|
+
attr_accessor :cloud_recommendation
|
32
|
+
|
29
33
|
# Public: What should show up in the form to add actors. This can be
|
30
34
|
# different per application since flipper_id's can be whatever an
|
31
35
|
# application needs. Defaults to "a flipper id".
|
@@ -40,6 +44,11 @@ module Flipper
|
|
40
44
|
# Default false. Only works when using descriptions.
|
41
45
|
attr_accessor :show_feature_description_in_list
|
42
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
|
+
|
43
52
|
VALID_BANNER_CLASS_VALUES = %w(
|
44
53
|
danger
|
45
54
|
dark
|
@@ -60,9 +69,11 @@ module Flipper
|
|
60
69
|
@feature_creation_enabled = true
|
61
70
|
@feature_removal_enabled = true
|
62
71
|
@fun = true
|
72
|
+
@cloud_recommendation = true
|
63
73
|
@add_actor_placeholder = "a flipper id"
|
64
74
|
@descriptions_source = DEFAULT_DESCRIPTIONS_SOURCE
|
65
75
|
@show_feature_description_in_list = false
|
76
|
+
@actors_separator = ','
|
66
77
|
end
|
67
78
|
|
68
79
|
def using_descriptions?
|
@@ -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
|
Binary file
|
@@ -1,6 +1,17 @@
|
|
1
|
-
$(function() {
|
2
|
-
$(document).on(
|
3
|
-
var $container = $(this).closest(
|
4
|
-
return $container.toggleClass(
|
1
|
+
$(function () {
|
2
|
+
$(document).on("click", ".js-toggle-trigger", function () {
|
3
|
+
var $container = $(this).closest(".js-toggle-container");
|
4
|
+
return $container.toggleClass("toggle-on");
|
5
|
+
});
|
6
|
+
|
7
|
+
$("#delete_feature__button").on("click", function (e) {
|
8
|
+
const featureName = $("#feature_name").val();
|
9
|
+
const promptMessage = prompt(
|
10
|
+
`Are you sure you want to remove this feature from the list of features and disable it for everyone? Please enter the name of the feature to confirm it: ${featureName}`
|
11
|
+
);
|
12
|
+
|
13
|
+
if (promptMessage !== featureName) {
|
14
|
+
e.preventDefault();
|
15
|
+
}
|
5
16
|
});
|
6
17
|
});
|
@@ -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
|
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
|
-
|
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="
|
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-
|
73
|
-
|
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-
|
134
|
-
|
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>
|
@@ -258,11 +258,11 @@
|
|
258
258
|
<p>
|
259
259
|
<%= Flipper::UI.configuration.delete.description %>
|
260
260
|
</p>
|
261
|
-
|
262
|
-
<form action="<%= script_name %>/features/<%= @feature.key %>" method="post" onsubmit="return confirm('Are you sure you want to remove this feature from the list of features and disable it for everyone?')">
|
261
|
+
<form action="<%= script_name %>/features/<%= @feature.key %>" method="post">
|
263
262
|
<%== csrf_input_tag %>
|
263
|
+
<input type="hidden" id="feature_name" name="_feature" value="<%= feature_name %>">
|
264
264
|
<input type="hidden" name="_method" value="DELETE">
|
265
|
-
<button type="submit" name="action" value="Delete" class="btn btn-outline-danger" data-toggle="tooltip" title="Remove feature from list of features and disable it." data-placement="right">Delete</button>
|
265
|
+
<button type="submit" name="action" value="Delete" id="delete_feature__button" class="btn btn-outline-danger" data-toggle="tooltip" title="Remove feature from list of features and disable it." data-placement="right">Delete</button>
|
266
266
|
</form>
|
267
267
|
</div>
|
268
268
|
</div>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<% if @show_blank_slate %>
|
2
|
-
<div class="jumbotron text-center">
|
2
|
+
<div class="jumbotron text-center m-0">
|
3
3
|
<% if Flipper::UI.configuration.fun %>
|
4
4
|
<h4>But I've got a blank space baby...</h4>
|
5
5
|
<p>And I'll flip your features.</p>
|
@@ -8,9 +8,6 @@
|
|
8
8
|
<a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
|
9
9
|
</p>
|
10
10
|
<%- end -%>
|
11
|
-
<div class="embed-responsive embed-responsive-16by9">
|
12
|
-
<iframe class="embed-responsive-item" width="560" height="315" src="https://www.youtube.com/embed/e-ORhEE9VVg" frameborder="0" allowfullscreen></iframe>
|
13
|
-
</div>
|
14
11
|
<% else %>
|
15
12
|
<h4>Getting Started</h4>
|
16
13
|
<p class="mb-1">You have not added any features to configure yet.</p>
|
@@ -40,14 +37,14 @@
|
|
40
37
|
<% @features.each do |feature| %>
|
41
38
|
<div class="feature row align-items-center mt-0 px-3 border-bottom">
|
42
39
|
<div class="col-1 col-md-auto">
|
43
|
-
<span class="
|
40
|
+
<span class="status <%= feature.color_class %>" data-toggle="tooltip" title=<%= feature.state.to_s.capitalize %>></span>
|
44
41
|
</div>
|
45
42
|
<div class="col-10">
|
46
43
|
<a href="<%= "#{script_name}/features/#{feature.key}" %>" class="d-block px-0 py-3 btn text-left text-dark">
|
47
44
|
<div class="text-truncate" style="font-weight: 500"><%= feature.key %></div>
|
48
45
|
<% if Flipper::UI.configuration.show_feature_description_in_list? && Flipper::UI::Util.present?(feature.description) %>
|
49
46
|
<div class="text-muted font-weight-light" style="line-height: 1.4; white-space: initial; padding: 8px 0">
|
50
|
-
|
47
|
+
<%== Sanitize.fragment(feature.description, Sanitize::Config::BASIC) %>
|
51
48
|
</div>
|
52
49
|
<% end %>
|
53
50
|
<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="
|
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
|
-
|
16
|
+
<%== Sanitize.fragment(Flipper::UI.configuration.banner_text, Sanitize::Config::BASIC) %>
|
18
17
|
</div>
|
19
18
|
<%- end -%>
|
20
19
|
|
@@ -35,11 +34,26 @@
|
|
35
34
|
<div>
|
36
35
|
<%== yield %>
|
37
36
|
</div>
|
37
|
+
|
38
|
+
<% if Flipper::UI.configuration.cloud_recommendation %>
|
39
|
+
<div class="d-flex justify-content-center mt-5">
|
40
|
+
<div class="row" style="max-width: 350px;">
|
41
|
+
<div class="col-auto d-flex align-items-center">
|
42
|
+
<a href="https://www.flippercloud.io/?utm_source=oss&utm_medium=ui&utm_campaign=spread_the_love">
|
43
|
+
<img src="<%= script_name %>/images/logo.png" width="50" />
|
44
|
+
</a>
|
45
|
+
</div>
|
46
|
+
<div class="col text-muted p-0">
|
47
|
+
<small>For support, audit history, finer-grained permissions, multi-environment sync, and all your projects in one place check out <a href="https://www.flippercloud.io/?utm_source=oss&utm_medium=ui&utm_campaign=spread_the_love">Flipper Cloud</a>.</small>
|
48
|
+
</div>
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
<% end %>
|
38
52
|
</div>
|
39
53
|
|
40
|
-
<script src="
|
41
|
-
<script src="
|
42
|
-
<script src="
|
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>
|
43
57
|
<script src="<%= script_name %>/js/application.js"></script>
|
44
58
|
</body>
|
45
59
|
</html>
|
data/lib/flipper/ui.rb
CHANGED
@@ -14,24 +14,6 @@ require 'flipper/ui/configuration'
|
|
14
14
|
|
15
15
|
module Flipper
|
16
16
|
module UI
|
17
|
-
class << self
|
18
|
-
# These three configuration options have been moved to Flipper::UI::Configuration
|
19
|
-
deprecated_configuration_options = %w(application_breadcrumb_href
|
20
|
-
feature_creation_enabled
|
21
|
-
feature_removal_enabled)
|
22
|
-
deprecated_configuration_options.each do |attribute_name|
|
23
|
-
send(:define_method, "#{attribute_name}=".to_sym) do
|
24
|
-
raise ConfigurationDeprecated, "The UI configuration for #{attribute_name} has " \
|
25
|
-
"deprecated. This configuration option has moved to Flipper::UI::Configuration"
|
26
|
-
end
|
27
|
-
|
28
|
-
send(:define_method, attribute_name.to_sym) do
|
29
|
-
raise ConfigurationDeprecated, "The UI configuration for #{attribute_name} has " \
|
30
|
-
"deprecated. This configuration option has moved to Flipper::UI::Configuration"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
17
|
def self.root
|
36
18
|
@root ||= Pathname(__FILE__).dirname.expand_path.join('ui')
|
37
19
|
end
|
@@ -39,18 +21,19 @@ module Flipper
|
|
39
21
|
def self.app(flipper = nil, options = {})
|
40
22
|
env_key = options.fetch(:env_key, 'flipper')
|
41
23
|
rack_protection_options = options.fetch(:rack_protection, use: :authenticity_token)
|
24
|
+
|
42
25
|
app = ->(_) { [200, { 'Content-Type' => 'text/html' }, ['']] }
|
43
26
|
builder = Rack::Builder.new
|
44
27
|
yield builder if block_given?
|
45
28
|
builder.use Rack::Protection, rack_protection_options
|
46
29
|
builder.use Rack::MethodOverride
|
47
30
|
builder.use Flipper::Middleware::SetupEnv, flipper, env_key: env_key
|
48
|
-
builder.use Flipper::Middleware
|
49
|
-
builder.use Flipper::UI::Middleware, env_key: env_key
|
31
|
+
builder.use Flipper::UI::Middleware, flipper: flipper, env_key: env_key
|
50
32
|
builder.run app
|
51
33
|
klass = self
|
52
|
-
|
53
|
-
|
34
|
+
app = builder.to_app
|
35
|
+
app.define_singleton_method(:inspect) { klass.inspect } # pretty rake routes output
|
36
|
+
app
|
54
37
|
end
|
55
38
|
|
56
39
|
# Public: yields configuration instance for customizing UI text
|
data/lib/flipper/version.rb
CHANGED