flipper-ui 0.2.0.beta1
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 +7 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/Guardfile +26 -0
- data/LICENSE +22 -0
- data/README.md +101 -0
- data/Rakefile +7 -0
- data/examples/basic.ru +44 -0
- data/examples/flipper.html +14 -0
- data/examples/flipper.png +0 -0
- data/flipper-ui.gemspec +21 -0
- data/lib/flipper-ui.rb +1 -0
- data/lib/flipper/ui.rb +23 -0
- data/lib/flipper/ui/action.rb +172 -0
- data/lib/flipper/ui/action_collection.rb +20 -0
- data/lib/flipper/ui/actions/features.rb +21 -0
- data/lib/flipper/ui/actions/file.rb +17 -0
- data/lib/flipper/ui/actions/gate.rb +143 -0
- data/lib/flipper/ui/actions/index.rb +17 -0
- data/lib/flipper/ui/assets/javascripts/application.coffee +305 -0
- data/lib/flipper/ui/assets/javascripts/spine/ajax.coffee +223 -0
- data/lib/flipper/ui/assets/javascripts/spine/list.coffee +43 -0
- data/lib/flipper/ui/assets/javascripts/spine/local.coffee +16 -0
- data/lib/flipper/ui/assets/javascripts/spine/manager.coffee +83 -0
- data/lib/flipper/ui/assets/javascripts/spine/relation.coffee +148 -0
- data/lib/flipper/ui/assets/javascripts/spine/route.coffee +146 -0
- data/lib/flipper/ui/assets/javascripts/spine/spine.coffee +542 -0
- data/lib/flipper/ui/assets/javascripts/spine/version +1 -0
- data/lib/flipper/ui/assets/stylesheets/application.scss +237 -0
- data/lib/flipper/ui/decorators/feature.rb +37 -0
- data/lib/flipper/ui/decorators/gate.rb +36 -0
- data/lib/flipper/ui/error.rb +10 -0
- data/lib/flipper/ui/eruby.rb +11 -0
- data/lib/flipper/ui/middleware.rb +66 -0
- data/lib/flipper/ui/public/css/application.css +183 -0
- data/lib/flipper/ui/public/css/images/animated-overlay.gif +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_flat_10_000000_40x100.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-icons_222222_256x240.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-icons_228ef1_256x240.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-icons_ef8c08_256x240.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-icons_ffd27a_256x240.png +0 -0
- data/lib/flipper/ui/public/css/images/ui-icons_ffffff_256x240.png +0 -0
- data/lib/flipper/ui/public/css/jquery-ui-1.10.3.slider.min.css +5 -0
- data/lib/flipper/ui/public/images/logo.png +0 -0
- data/lib/flipper/ui/public/images/remove.png +0 -0
- data/lib/flipper/ui/public/js/application.js +544 -0
- data/lib/flipper/ui/public/js/handlebars.js +1992 -0
- data/lib/flipper/ui/public/js/jquery-ui-1.10.3.slider.min.js +6 -0
- data/lib/flipper/ui/public/js/jquery.js +9555 -0
- data/lib/flipper/ui/public/js/jquery.min.js +4 -0
- data/lib/flipper/ui/public/js/jquery.min.map +1 -0
- data/lib/flipper/ui/public/js/spine/ajax.js +320 -0
- data/lib/flipper/ui/public/js/spine/list.js +72 -0
- data/lib/flipper/ui/public/js/spine/local.js +29 -0
- data/lib/flipper/ui/public/js/spine/manager.js +157 -0
- data/lib/flipper/ui/public/js/spine/relation.js +260 -0
- data/lib/flipper/ui/public/js/spine/route.js +223 -0
- data/lib/flipper/ui/public/js/spine/spine.js +927 -0
- data/lib/flipper/ui/util.rb +12 -0
- data/lib/flipper/ui/version.rb +5 -0
- data/lib/flipper/ui/views/index.erb +9 -0
- data/lib/flipper/ui/views/layout.erb +161 -0
- data/script/bootstrap +21 -0
- data/script/server +19 -0
- data/script/test +30 -0
- data/spec/flipper/ui/decorators/feature_spec.rb +59 -0
- data/spec/flipper/ui/decorators/gate_spec.rb +47 -0
- data/spec/flipper/ui/util_spec.rb +18 -0
- data/spec/flipper/ui_spec.rb +470 -0
- data/spec/helper.rb +35 -0
- metadata +168 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Flipper
|
|
2
|
+
module UI
|
|
3
|
+
# Internal: Used to detect the action that should be used in the middleware.
|
|
4
|
+
class ActionCollection
|
|
5
|
+
def initialize
|
|
6
|
+
@action_classes = []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def add(action_class)
|
|
10
|
+
@action_classes << action_class
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def action_for_request(request)
|
|
14
|
+
@action_classes.detect { |action_class|
|
|
15
|
+
request.path_info =~ action_class.regex
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'flipper/ui/action'
|
|
2
|
+
require 'flipper/ui/decorators/feature'
|
|
3
|
+
|
|
4
|
+
module Flipper
|
|
5
|
+
module UI
|
|
6
|
+
module Actions
|
|
7
|
+
class Features < UI::Action
|
|
8
|
+
|
|
9
|
+
route %r{features/?\Z}
|
|
10
|
+
|
|
11
|
+
def get
|
|
12
|
+
features = flipper.features.map { |feature|
|
|
13
|
+
Decorators::Feature.new(feature)
|
|
14
|
+
}.sort_by(&:pretty_name)
|
|
15
|
+
|
|
16
|
+
json_response features.map(&:as_json)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'rack/file'
|
|
2
|
+
require 'flipper/ui/action'
|
|
3
|
+
|
|
4
|
+
module Flipper
|
|
5
|
+
module UI
|
|
6
|
+
module Actions
|
|
7
|
+
class File < UI::Action
|
|
8
|
+
|
|
9
|
+
route %r{(images|css|js)/.*\Z}
|
|
10
|
+
|
|
11
|
+
def get
|
|
12
|
+
Rack::File.new(public_path).call(request.env)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require 'flipper/ui/util'
|
|
2
|
+
require 'flipper/ui/action'
|
|
3
|
+
require 'flipper/ui/actions/index'
|
|
4
|
+
require 'flipper/ui/decorators/feature'
|
|
5
|
+
|
|
6
|
+
module Flipper
|
|
7
|
+
module UI
|
|
8
|
+
module Actions
|
|
9
|
+
class Gate < UI::Action
|
|
10
|
+
|
|
11
|
+
# Private: Struct to wrap actors so they can respond to flipper_id.
|
|
12
|
+
FakeActor = Struct.new(:flipper_id)
|
|
13
|
+
|
|
14
|
+
route %r{features/.*/.*/?\Z}
|
|
15
|
+
|
|
16
|
+
# Get should run the index route. All the url does is control what is
|
|
17
|
+
# opened and closed when the page is loaded.
|
|
18
|
+
def get
|
|
19
|
+
run_other_action Index
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# FIXME: Return more than just the gate as json response?
|
|
23
|
+
def post
|
|
24
|
+
feature_name, gate_name = request.path.split('/').pop(2).map{|value| Rack::Utils.unescape value }
|
|
25
|
+
update_gate_method_name = "update_#{gate_name}"
|
|
26
|
+
|
|
27
|
+
unless respond_to?(update_gate_method_name)
|
|
28
|
+
update_gate_method_undefined(gate_name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
feature = flipper[feature_name.to_sym]
|
|
32
|
+
send(update_gate_method_name, feature)
|
|
33
|
+
gate = feature.gate(gate_name)
|
|
34
|
+
value = feature.gate_values[gate.key]
|
|
35
|
+
|
|
36
|
+
json_response Decorators::Gate.new(gate, value).as_json
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update_boolean(feature)
|
|
40
|
+
if params['value'] == 'true'
|
|
41
|
+
feature.enable
|
|
42
|
+
else
|
|
43
|
+
feature.disable
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def update_actor(feature)
|
|
48
|
+
value = params['value']
|
|
49
|
+
|
|
50
|
+
if Util.blank?(value)
|
|
51
|
+
invalid_actor_value(value)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
thing = FakeActor.new(value)
|
|
55
|
+
actor = flipper.actor(thing)
|
|
56
|
+
|
|
57
|
+
case params['operation']
|
|
58
|
+
when 'enable'
|
|
59
|
+
feature.enable actor
|
|
60
|
+
when 'disable'
|
|
61
|
+
feature.disable actor
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update_group(feature)
|
|
66
|
+
group_name = params['value']
|
|
67
|
+
group = flipper.group(group_name)
|
|
68
|
+
|
|
69
|
+
case params['operation']
|
|
70
|
+
when 'enable'
|
|
71
|
+
feature.enable group
|
|
72
|
+
when 'disable'
|
|
73
|
+
feature.disable group
|
|
74
|
+
end
|
|
75
|
+
rescue Flipper::GroupNotRegistered => e
|
|
76
|
+
group_not_registered group_name
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def update_percentage_of_actors(feature)
|
|
80
|
+
value = params['value']
|
|
81
|
+
feature.enable_percentage_of_actors value
|
|
82
|
+
rescue ArgumentError => exception
|
|
83
|
+
invalid_percentage value, exception
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def update_percentage_of_random(feature)
|
|
87
|
+
value = params['value']
|
|
88
|
+
feature.enable_percentage_of_random value
|
|
89
|
+
rescue ArgumentError => exception
|
|
90
|
+
invalid_percentage value, exception
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Private: Returns error response for invalid actor value.
|
|
94
|
+
def invalid_actor_value(value)
|
|
95
|
+
response = {
|
|
96
|
+
status: 'error',
|
|
97
|
+
message: "#{value.inspect} is not a valid actor value.",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
status 422
|
|
101
|
+
halt json_response(response)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Private: Returns error response for invalid percentage value.
|
|
105
|
+
def invalid_percentage(value, exception)
|
|
106
|
+
response = {
|
|
107
|
+
status: 'error',
|
|
108
|
+
message: exception.message,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
status 422
|
|
112
|
+
halt json_response(response)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Private: Returns error response that group was not registered.
|
|
116
|
+
def group_not_registered(group_name)
|
|
117
|
+
response = {status: 'error'}
|
|
118
|
+
|
|
119
|
+
if Util.blank?(group_name)
|
|
120
|
+
status 422
|
|
121
|
+
response[:message] = "Group name is required."
|
|
122
|
+
else
|
|
123
|
+
status 404
|
|
124
|
+
response[:message] = "The group named #{group_name.inspect} has not been registered."
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
halt json_response(response)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Private: Returns error response that gate update method is not defined.
|
|
131
|
+
def update_gate_method_undefined(gate_name)
|
|
132
|
+
response = {
|
|
133
|
+
status: 'error',
|
|
134
|
+
message: "I have no clue how to update the gate named #{gate_name.inspect}.",
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
status 404
|
|
138
|
+
halt json_response(response)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
class Feature extends Spine.Model
|
|
2
|
+
@configure "Feature", "id", "name", "state", "description", "gates"
|
|
3
|
+
@extend Spine.Model.Ajax
|
|
4
|
+
@extend url: "#{Flipper.Config.url}/features"
|
|
5
|
+
|
|
6
|
+
constructor: ->
|
|
7
|
+
super
|
|
8
|
+
@gates = @gates.map (data) =>
|
|
9
|
+
data.feature_id = @id
|
|
10
|
+
new Gate(data)
|
|
11
|
+
|
|
12
|
+
gate: (name) ->
|
|
13
|
+
gates = @gates.filter (gate) ->
|
|
14
|
+
gate.name == name
|
|
15
|
+
gates[0]
|
|
16
|
+
|
|
17
|
+
window.Feature = Feature
|
|
18
|
+
|
|
19
|
+
class Gate extends Spine.Model
|
|
20
|
+
@configure "Gate", "feature_id", "key", "name", "value"
|
|
21
|
+
|
|
22
|
+
constructor: ->
|
|
23
|
+
super
|
|
24
|
+
|
|
25
|
+
url: ->
|
|
26
|
+
"#{Flipper.Config.url}/features/#{encodeURIComponent @feature_id}/#{encodeURIComponent @name}"
|
|
27
|
+
|
|
28
|
+
disableSetMember: (member, success_callback, error_callback) ->
|
|
29
|
+
@setMember('disable', member, success_callback, error_callback)
|
|
30
|
+
|
|
31
|
+
enableSetMember: (member, success_callback, error_callback) ->
|
|
32
|
+
@setMember('enable', member, success_callback, error_callback)
|
|
33
|
+
|
|
34
|
+
setMember: (operation, member, success_callback, error_callback) ->
|
|
35
|
+
options =
|
|
36
|
+
type: 'POST'
|
|
37
|
+
url: @url()
|
|
38
|
+
data:
|
|
39
|
+
operation: operation
|
|
40
|
+
value: member
|
|
41
|
+
success: (data, status, xhr) =>
|
|
42
|
+
Feature.trigger('reload')
|
|
43
|
+
@value = data.value
|
|
44
|
+
success_callback(data, status, xhr) if success_callback
|
|
45
|
+
error: (data, status, error) =>
|
|
46
|
+
response = if data.responseText then $.parseJSON data.responseText else message: "Something went wrong..."
|
|
47
|
+
alert "ERROR: #{response.message}"
|
|
48
|
+
error_callback(data, status) if error_callback
|
|
49
|
+
|
|
50
|
+
$.ajax options
|
|
51
|
+
|
|
52
|
+
save: (opts) ->
|
|
53
|
+
result = super
|
|
54
|
+
@ajaxSave(opts)
|
|
55
|
+
Feature.trigger('reload')
|
|
56
|
+
result
|
|
57
|
+
|
|
58
|
+
ajaxSave: (opts) ->
|
|
59
|
+
options =
|
|
60
|
+
type: 'POST'
|
|
61
|
+
url: @url()
|
|
62
|
+
data:
|
|
63
|
+
value: @value
|
|
64
|
+
error: (data, status, error) =>
|
|
65
|
+
response = if data.responseText then $.parseJSON data.responseText else message: "Something went wrong..."
|
|
66
|
+
alert "ERROR: #{response.message}"
|
|
67
|
+
|
|
68
|
+
$.ajax options
|
|
69
|
+
|
|
70
|
+
class App extends Spine.Controller
|
|
71
|
+
constructor: ->
|
|
72
|
+
super
|
|
73
|
+
@feature_list = new App.FeatureList(el: $('#features'))
|
|
74
|
+
|
|
75
|
+
class App.FeatureList extends Spine.Controller
|
|
76
|
+
constructor: ->
|
|
77
|
+
super
|
|
78
|
+
@feature_controllers = {}
|
|
79
|
+
Feature.bind "refresh", @addAll
|
|
80
|
+
Feature.bind "reload", @reload
|
|
81
|
+
|
|
82
|
+
Feature.one 'refresh', ->
|
|
83
|
+
Spine.Route.setup
|
|
84
|
+
history: true
|
|
85
|
+
|
|
86
|
+
Feature.fetch()
|
|
87
|
+
|
|
88
|
+
Spine.Route.add /features\/(.*)\/(.*)\/?/, (matches) =>
|
|
89
|
+
params =
|
|
90
|
+
id: matches.match[1]
|
|
91
|
+
gate: matches.match[2]
|
|
92
|
+
if controller = @feature_controllers[params.id]
|
|
93
|
+
controller.edit()
|
|
94
|
+
controller.activateGate(params)
|
|
95
|
+
|
|
96
|
+
Spine.Route.add /features\/(.*)\/?/, (matches) =>
|
|
97
|
+
params =
|
|
98
|
+
id: matches.match[1]
|
|
99
|
+
if controller = @feature_controllers[params.id]
|
|
100
|
+
controller.edit()
|
|
101
|
+
controller.openDefaultGate()
|
|
102
|
+
|
|
103
|
+
addOne: (feature) =>
|
|
104
|
+
controller = new App.Feature(feature: feature)
|
|
105
|
+
@feature_controllers[feature.id] = controller
|
|
106
|
+
@append controller.render()
|
|
107
|
+
|
|
108
|
+
addAll: =>
|
|
109
|
+
@html ''
|
|
110
|
+
$('#no_features').hide()
|
|
111
|
+
all_features = Feature.all()
|
|
112
|
+
if all_features.length > 0
|
|
113
|
+
@addOne feature for feature in all_features
|
|
114
|
+
else
|
|
115
|
+
$('#no_features').show()
|
|
116
|
+
|
|
117
|
+
reload: =>
|
|
118
|
+
Feature.fetch()
|
|
119
|
+
@addAll
|
|
120
|
+
|
|
121
|
+
class App.Feature extends Spine.Controller
|
|
122
|
+
elements:
|
|
123
|
+
'.feature': 'dom_feature'
|
|
124
|
+
'.gates': 'dom_gates'
|
|
125
|
+
|
|
126
|
+
events:
|
|
127
|
+
'click .show-settings': 'openFeature'
|
|
128
|
+
'click .hide-settings': 'hide'
|
|
129
|
+
'click [data-tab]': 'clickTab'
|
|
130
|
+
|
|
131
|
+
constructor: ->
|
|
132
|
+
super
|
|
133
|
+
throw "@feature required" if !@feature?
|
|
134
|
+
|
|
135
|
+
render: ->
|
|
136
|
+
@html @template(@feature)
|
|
137
|
+
@gate_list = new App.GateList
|
|
138
|
+
el: @dom_gates
|
|
139
|
+
@el
|
|
140
|
+
|
|
141
|
+
openFeature: (event) ->
|
|
142
|
+
event.preventDefault() if event
|
|
143
|
+
@navigate "#{Flipper.Config.url}/features/#{@feature.id}"
|
|
144
|
+
|
|
145
|
+
openDefaultGate: ->
|
|
146
|
+
@navigate "#{Flipper.Config.url}/features/#{@feature.id}/boolean"
|
|
147
|
+
|
|
148
|
+
template: (feature) ->
|
|
149
|
+
source = $("#feature-template").html()
|
|
150
|
+
template = Handlebars.compile(source)
|
|
151
|
+
template(feature)
|
|
152
|
+
|
|
153
|
+
clickTab: (event) ->
|
|
154
|
+
event.preventDefault()
|
|
155
|
+
tab = $(event.currentTarget)
|
|
156
|
+
name = tab.attr('data-tab')
|
|
157
|
+
@navigate "#{Flipper.Config.url}/features/#{@feature.id}/#{name}"
|
|
158
|
+
|
|
159
|
+
activateGate: (params) ->
|
|
160
|
+
name = params.gate
|
|
161
|
+
@gate_list[name].active(params)
|
|
162
|
+
@el.find('[data-tab]').removeClass('active')
|
|
163
|
+
@el.find("[data-tab=#{name}]").addClass('active')
|
|
164
|
+
|
|
165
|
+
edit: (event) ->
|
|
166
|
+
event.preventDefault() if event
|
|
167
|
+
@dom_feature.addClass('settings')
|
|
168
|
+
|
|
169
|
+
hide: (event) ->
|
|
170
|
+
event.preventDefault() if event
|
|
171
|
+
@dom_feature.removeClass('settings')
|
|
172
|
+
@navigate "#{Flipper.Config.url}/"
|
|
173
|
+
|
|
174
|
+
class App.Gate extends Spine.Controller
|
|
175
|
+
constructor: ->
|
|
176
|
+
super
|
|
177
|
+
@active @renderForParams
|
|
178
|
+
|
|
179
|
+
renderForParams: (params) ->
|
|
180
|
+
@feature = Feature.find(params.id)
|
|
181
|
+
@gate = @feature.gate(params.gate)
|
|
182
|
+
@render()
|
|
183
|
+
|
|
184
|
+
render: ->
|
|
185
|
+
@html @template("#gate-#{@name.replace(/_/g, '-')}-template", @gate)
|
|
186
|
+
$slider = $(".slider-range")
|
|
187
|
+
$slider_value = $slider.siblings("input[type='text']")
|
|
188
|
+
|
|
189
|
+
$slider.slider
|
|
190
|
+
range: "min",
|
|
191
|
+
value: @gate.value,
|
|
192
|
+
min: 0,
|
|
193
|
+
max: 100,
|
|
194
|
+
slide: ( event, ui ) ->
|
|
195
|
+
$slider_value.val( ui.value )
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
$slider_value.val $slider.slider( "value" )
|
|
199
|
+
$slider_value.change ()->
|
|
200
|
+
$slider.slider "value", $(@).val()
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
template: (html_id, context) ->
|
|
204
|
+
source = $(html_id).html()
|
|
205
|
+
template = Handlebars.compile(source)
|
|
206
|
+
template(context)
|
|
207
|
+
|
|
208
|
+
class App.Gate.Boolean extends App.Gate
|
|
209
|
+
elements:
|
|
210
|
+
'input[value=true]': 'input'
|
|
211
|
+
|
|
212
|
+
events:
|
|
213
|
+
'submit form': 'submit'
|
|
214
|
+
|
|
215
|
+
constructor: ->
|
|
216
|
+
@name = 'boolean'
|
|
217
|
+
super
|
|
218
|
+
|
|
219
|
+
submit: (event) ->
|
|
220
|
+
event.preventDefault()
|
|
221
|
+
@gate.value = @input.is(':checked')
|
|
222
|
+
@gate.save()
|
|
223
|
+
@navigate "#{Flipper.Config.url}/"
|
|
224
|
+
|
|
225
|
+
class App.Gate.Set extends App.Gate
|
|
226
|
+
elements:
|
|
227
|
+
'.disable': 'dom_disable'
|
|
228
|
+
'.members': 'dom_members'
|
|
229
|
+
'[name=value]': 'dom_input'
|
|
230
|
+
|
|
231
|
+
events:
|
|
232
|
+
'click .disable': 'disable'
|
|
233
|
+
'submit form': 'submit'
|
|
234
|
+
|
|
235
|
+
disable: (event) ->
|
|
236
|
+
event.preventDefault()
|
|
237
|
+
member = $(event.currentTarget).closest('.member')
|
|
238
|
+
value = member.attr('data-value')
|
|
239
|
+
@gate.disableSetMember value, (data, status, xhr) ->
|
|
240
|
+
member.remove()
|
|
241
|
+
|
|
242
|
+
submit: (event) ->
|
|
243
|
+
event.preventDefault()
|
|
244
|
+
value = @dom_input.val()
|
|
245
|
+
self = @
|
|
246
|
+
|
|
247
|
+
@gate.enableSetMember value, (data, status, xhr) ->
|
|
248
|
+
html = self.template "#gate-member-template", value
|
|
249
|
+
self.dom_members.append html
|
|
250
|
+
self.dom_input.val ''
|
|
251
|
+
|
|
252
|
+
class App.Gate.Group extends App.Gate.Set
|
|
253
|
+
constructor: ->
|
|
254
|
+
@name = 'group'
|
|
255
|
+
super
|
|
256
|
+
|
|
257
|
+
class App.Gate.Actor extends App.Gate.Set
|
|
258
|
+
constructor: ->
|
|
259
|
+
@name = 'actor'
|
|
260
|
+
super
|
|
261
|
+
|
|
262
|
+
class App.Gate.Percentage extends App.Gate
|
|
263
|
+
elements:
|
|
264
|
+
'input[type=text]': 'input'
|
|
265
|
+
|
|
266
|
+
events:
|
|
267
|
+
'submit form': 'submit'
|
|
268
|
+
|
|
269
|
+
validate: ()->
|
|
270
|
+
float_value = parseFloat(@gate.value)
|
|
271
|
+
valid = true
|
|
272
|
+
|
|
273
|
+
if isNaN(float_value) || float_value < 0 || float_value > 100
|
|
274
|
+
alert "The percentage value provided is not valid"
|
|
275
|
+
valid = false
|
|
276
|
+
|
|
277
|
+
return valid
|
|
278
|
+
|
|
279
|
+
submit: (event) ->
|
|
280
|
+
event.preventDefault()
|
|
281
|
+
@gate.value = @input.val()
|
|
282
|
+
return unless @validate()
|
|
283
|
+
@gate.save()
|
|
284
|
+
@navigate "#{Flipper.Config.url}/"
|
|
285
|
+
|
|
286
|
+
class App.Gate.PercentageOfActors extends App.Gate.Percentage
|
|
287
|
+
constructor: ->
|
|
288
|
+
@name = 'percentage_of_actors'
|
|
289
|
+
super
|
|
290
|
+
|
|
291
|
+
class App.Gate.PercentageOfRandom extends App.Gate.Percentage
|
|
292
|
+
constructor: ->
|
|
293
|
+
@name = 'percentage_of_random'
|
|
294
|
+
super
|
|
295
|
+
|
|
296
|
+
class App.GateList extends Spine.Stack
|
|
297
|
+
controllers:
|
|
298
|
+
boolean: App.Gate.Boolean
|
|
299
|
+
group: App.Gate.Group
|
|
300
|
+
actor: App.Gate.Actor
|
|
301
|
+
percentage_of_actors: App.Gate.PercentageOfActors
|
|
302
|
+
percentage_of_random: App.Gate.PercentageOfRandom
|
|
303
|
+
|
|
304
|
+
jQuery ->
|
|
305
|
+
new App(el: $('#app'))
|