flipper-ui 1.2.1 → 1.4.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/examples/ui/basic.ru +4 -0
  3. data/flipper-ui.gemspec +3 -2
  4. data/lib/flipper/ui/action.rb +30 -46
  5. data/lib/flipper/ui/actions/actors_gate.rb +2 -8
  6. data/lib/flipper/ui/actions/add_feature.rb +0 -9
  7. data/lib/flipper/ui/actions/boolean_gate.rb +1 -1
  8. data/lib/flipper/ui/actions/cloud_migrate.rb +26 -0
  9. data/lib/flipper/ui/actions/feature.rb +0 -7
  10. data/lib/flipper/ui/actions/features.rb +2 -9
  11. data/lib/flipper/ui/actions/groups_gate.rb +2 -7
  12. data/lib/flipper/ui/actions/percentage_of_actors_gate.rb +2 -2
  13. data/lib/flipper/ui/actions/percentage_of_time_gate.rb +2 -2
  14. data/lib/flipper/ui/actions/settings.rb +0 -3
  15. data/lib/flipper/ui/configuration.rb +18 -4
  16. data/lib/flipper/ui/middleware.rb +1 -0
  17. data/lib/flipper/ui/public/css/bootstrap.min.css +6 -0
  18. data/lib/flipper/ui/public/js/application.js +17 -33
  19. data/lib/flipper/ui/public/js/bootstrap.min.js +7 -0
  20. data/lib/flipper/ui/public/js/popper.min.js +6 -0
  21. data/lib/flipper/ui/sources.json +5 -0
  22. data/lib/flipper/ui/util.rb +10 -0
  23. data/lib/flipper/ui/views/add_actor.erb +8 -4
  24. data/lib/flipper/ui/views/add_feature.erb +3 -3
  25. data/lib/flipper/ui/views/add_group.erb +14 -9
  26. data/lib/flipper/ui/views/feature.erb +93 -80
  27. data/lib/flipper/ui/views/features.erb +29 -29
  28. data/lib/flipper/ui/views/layout.erb +28 -20
  29. data/lib/flipper/ui/views/settings.erb +31 -4
  30. data/lib/flipper/ui.rb +16 -2
  31. data/lib/flipper/version.rb +1 -1
  32. data/spec/flipper/ui/actions/actors_gate_spec.rb +6 -6
  33. data/spec/flipper/ui/actions/add_feature_spec.rb +1 -1
  34. data/spec/flipper/ui/actions/boolean_gate_spec.rb +1 -1
  35. data/spec/flipper/ui/actions/cloud_migrate_spec.rb +54 -0
  36. data/spec/flipper/ui/actions/feature_spec.rb +28 -0
  37. data/spec/flipper/ui/actions/features_spec.rb +60 -3
  38. data/spec/flipper/ui/actions/groups_gate_spec.rb +6 -6
  39. data/spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb +3 -3
  40. data/spec/flipper/ui/actions/percentage_of_time_gate_spec.rb +3 -3
  41. data/spec/flipper/ui/actions/settings_spec.rb +8 -0
  42. data/spec/flipper/ui/configuration_spec.rb +9 -4
  43. data/spec/flipper/ui_spec.rb +19 -31
  44. metadata +40 -35
  45. data/lib/flipper/ui/public/css/bootstrap-4.6.0.min.css +0 -7
  46. data/lib/flipper/ui/public/js/bootstrap-4.6.0.min.js +0 -7
  47. data/lib/flipper/ui/public/js/popper-1.12.9.min.js +0 -5
@@ -21,15 +21,20 @@
21
21
  <div class="card">
22
22
  <%# Gate State Header %>
23
23
  <div class="card-header">
24
- <span class="status <%= @feature.color_class %> mr-2"></span>
25
- <%= @feature.gate_state_title %>
24
+ <div class="row align-items-center">
25
+ <h4 class="col text-truncate mb-0"><%= feature_name %></h4>
26
+ <div class="col-auto">
27
+ <span class="status <%= @feature.color_class %> me-2"></span>
28
+ <%= @feature.gate_state_title %>
29
+ </div>
30
+ </div>
26
31
  </div>
27
32
 
28
33
  <% unless @feature.boolean_value %>
29
34
  <%# Actors Info and Form %>
30
35
  <div class="card-body border-bottom">
31
36
  <div class="row align-items-center js-toggle-container">
32
- <div class="col col-mr-auto toggle-block-when-off">
37
+ <div class="col col-me-auto toggle-block-when-off">
33
38
  <h6 class="m-0">
34
39
  <strong class="<%= "text-muted" if @feature.actors_value.empty? %>">
35
40
  <% if @feature.actors_value.count > 0 %>
@@ -42,14 +47,18 @@
42
47
  </div>
43
48
  <% if write_allowed? %>
44
49
  <div class="col col-auto toggle-block-when-off">
45
- <button class="btn btn-outline-secondary js-toggle-trigger" data-toggle="collapse" data-target="#add-actor">Add an actor</button>
50
+ <button class="btn btn-outline-secondary js-toggle-trigger" data-bs-toggle="collapse" data-bs-target="#add-actor">Add an actor</button>
46
51
  </div>
47
52
  <div class="col toggle-block-when-on">
48
- <form action="<%= script_name %>/features/<%= @feature.key %>/actors" method="post" class="form-inline">
53
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/actors" method="post" class="row">
49
54
  <%== csrf_input_tag %>
50
55
  <input type="hidden" name="operation" value="enable">
51
- <input type="text" name="value" placeholder="<%= Flipper::UI.configuration.add_actor_placeholder %>" class="form-control form-control-sm mr-sm-2 mb-2 mb-sm-0">
52
- <input type="submit" value="Add Actor" class="btn btn-sm btn-light">
56
+ <div class="col">
57
+ <input type="text" name="value" placeholder="<%= Flipper::UI.configuration.add_actor_placeholder %>" class="form-control">
58
+ </div>
59
+ <div class="col-auto">
60
+ <input type="submit" value="Add Actor" class="btn btn-primary">
61
+ </div>
53
62
  </form>
54
63
  </div>
55
64
  <div class="col col-auto toggle-block-when-on">
@@ -63,7 +72,7 @@
63
72
  <% @feature.actors_value.each do |item| %>
64
73
  <div class="card-body bg-lightest border-bottom py-3">
65
74
  <div class="row align-items-center">
66
- <div class="col col-mr-auto pl-md-5">
75
+ <div class="col col-me-auto ps-md-5">
67
76
  <h6 class="m-0">
68
77
  <% if Flipper::UI::Util.present?(@feature.actor_names[item]) %>
69
78
  <%== Sanitize.fragment("#{@feature.actor_names[item]} (#{item})", Sanitize::Config::BASIC) %>
@@ -74,11 +83,11 @@
74
83
  </div>
75
84
  <div class="col col-auto">
76
85
  <% if write_allowed? %>
77
- <form action="<%= script_name %>/features/<%= @feature.key %>/actors" method="post">
86
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/actors" method="post">
78
87
  <%== csrf_input_tag %>
79
88
  <input type="hidden" name="operation" value="disable">
80
89
  <input type="hidden" name="value" value="<%= item %>">
81
- <button type="submit" value="Disable" class="btn btn-outline-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
90
+ <button type="submit" value="Disable" class="btn btn-outline-danger" data-bs-toggle="tooltip" title="Disable <%= item %>" data-bs-placement="left">
82
91
  Remove
83
92
  </button>
84
93
  </form>
@@ -91,7 +100,7 @@
91
100
  <%# Groups Info and Form %>
92
101
  <div class="card-body border-bottom">
93
102
  <div class="row align-items-center js-toggle-container">
94
- <div class="col col-mr-auto toggle-block-when-off">
103
+ <div class="col col-me-auto toggle-block-when-off">
95
104
  <h6 class="m-0">
96
105
  <strong class="<%= "text-muted" if @feature.groups_value.empty? %>">
97
106
  <% if @feature.groups_value.count > 0 %>
@@ -104,22 +113,24 @@
104
113
  </div>
105
114
  <% if write_allowed? %>
106
115
  <div class="col col-auto toggle-block-when-off">
107
- <button class="btn btn-outline-secondary js-toggle-trigger" data-toggle="collapse" data-target="#add-actor">Add a group</button>
116
+ <button class="btn btn-outline-secondary js-toggle-trigger" data-bs-toggle="collapse" data-bs-target="#add-actor">Add a group</button>
108
117
  </div>
109
118
  <div class="col collapse toggle-block-when-on">
110
119
  <% if @feature.disabled_groups.empty? %>
111
120
  All groups enabled.
112
121
  <% else %>
113
- <form action="<%= script_name %>/features/<%= @feature.key %>/groups" method="post" class="form-inline">
122
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/groups" method="post" class="row">
114
123
  <%== csrf_input_tag %>
115
124
  <input type="hidden" name="operation" value="enable">
116
- <select name="value" class="form-control form-control-sm mr-sm-2 mb-2 mb-sm-0">
117
- <option value="">Select a group...</option>
118
- <% @feature.disabled_groups.each do |group| %>
119
- <option value="<%= group.name %>"><%= group.name %></option>
120
- <% end %>
121
- </select>
122
- <input type="submit" value="Add Group" class="btn btn-sm btn-light btn">
125
+ <div class="col">
126
+ <select name="value" class="form-select">
127
+ <option value="">Select a group...</option>
128
+ <% @feature.disabled_groups.sort_by(&:name).each do |group| %>
129
+ <option value="<%= group.name %>"><%= group.name %></option>
130
+ <% end %>
131
+ </select>
132
+ </div>
133
+ <div class="col-auto"><input type="submit" value="Add Group" class="btn btn-primary btn"></div>
123
134
  </form>
124
135
  <% end %>
125
136
  </div>
@@ -134,16 +145,16 @@
134
145
  <% @feature.groups_value.each do |item| %>
135
146
  <div class="card-body bg-lightest border-bottom py-3">
136
147
  <div class="row align-items-center">
137
- <div class="col col-mr-auto pl-md-5">
148
+ <div class="col col-me-auto ps-md-5">
138
149
  <h6 class="m-0"><%= item %></h6>
139
150
  </div>
140
151
  <div class="col col-auto">
141
152
  <% if write_allowed? %>
142
- <form action="<%= script_name %>/features/<%= @feature.key %>/groups" method="post">
153
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/groups" method="post">
143
154
  <%== csrf_input_tag %>
144
155
  <input type="hidden" name="operation" value="disable">
145
156
  <input type="hidden" name="value" value="<%= item %>">
146
- <button type="submit" value="Disable" class="btn btn-outline-danger" data-toggle="tooltip" title="Disable <%= item %>" data-placement="left">
157
+ <button type="submit" value="Disable" class="btn btn-outline-danger" data-bs-toggle="tooltip" title="Disable <%= item %>" data-bs-placement="left">
147
158
  Remove
148
159
  </button>
149
160
  </form>
@@ -157,7 +168,7 @@
157
168
  <div class="js-toggle-container">
158
169
  <div class="card-body border-bottom">
159
170
  <div class="row align-items-center">
160
- <div class="col col-mr-auto">
171
+ <div class="col col-me-auto">
161
172
  <h6 class="m-0"><strong class="<%= "text-muted" unless @feature.percentage_of_actors_value > 0 %>">Enabled for <%= @feature.percentage_of_actors_value %>% of actors</strong></h6>
162
173
  </div>
163
174
  <% if write_allowed? %>
@@ -173,24 +184,22 @@
173
184
 
174
185
  <% if write_allowed? %>
175
186
  <div class="card-body border-bottom toggle-block-when-on bg-lightest">
176
- <div class="row">
177
- <div class="col-12 col-md-6 mb-3 mb-md-0">
178
- <form action="<%= script_name %>/features/<%= @feature.key %>/percentage_of_actors" method="post">
179
- <%== csrf_input_tag %>
180
- <div class="btn-group">
181
- <% @percentages.each do |number| %>
182
- <input type="submit" name="value" value="<%= number %>%" class="btn btn-light btn-sm" <% if number == @feature.percentage_of_actors_value %>disabled<% end %>>
183
- <% end %>
184
- </div>
185
- </form>
186
- </div>
187
- <div class="col-12 col-md-6">
188
- <form action="<%= script_name %>/features/<%= @feature.key %>/percentage_of_actors" method="post" class="form-inline">
189
- <%== csrf_input_tag %>
190
- <input type="text" name="value" <% if @feature.percentage_of_actors_value > 0 %>value="<%= @feature.percentage_of_actors_value %>"<% end %> placeholder="custom (26, 32, etc.)" class="form-control form-control-sm mr-sm-2 mb-2 mb-md-0">
191
- <input type="submit" name="action" value="Save" class="btn btn-light btn-sm">
192
- </form>
193
- </div>
187
+ <div class="row align-items-center">
188
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/percentage_of_actors" method="post" class="col">
189
+ <%== csrf_input_tag %>
190
+ <div class="btn-group">
191
+ <% @percentages.each do |number| %>
192
+ <input type="submit" name="value" value="<%= number %>%" class="btn btn-light" <% if number == @feature.percentage_of_actors_value %>disabled<% end %>>
193
+ <% end %>
194
+ </div>
195
+ </form>
196
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/percentage_of_actors" method="post" class="col-auto row">
197
+ <%== csrf_input_tag %>
198
+ <div class="col-auto">
199
+ <input style="width:5em;" type="number" max="100" min="0" name="value" <% if @feature.percentage_of_actors_value > 0 %>value="<%= @feature.percentage_of_actors_value %>"<% end %> title="Custom percentage (26, 32, etc.)" placeholder="0" class="form-control">
200
+ </div>
201
+ <div class="col-auto ms-auto"><input type="submit" name="action" value="Save" class="btn btn-primary"></div>
202
+ </form>
194
203
  </div>
195
204
  </div>
196
205
  <% end %>
@@ -200,7 +209,7 @@
200
209
  <div class="js-toggle-container">
201
210
  <div class="card-body border-bottom">
202
211
  <div class="row align-items-center">
203
- <div class="col col-mr-auto">
212
+ <div class="col col-me-auto">
204
213
  <h6 class="m-0"><strong class="<%= "text-muted" unless @feature.percentage_of_time_value > 0 %>">Enabled for <%= @feature.percentage_of_time_value %>% of time</strong></h6>
205
214
  </div>
206
215
  <% if write_allowed? %>
@@ -216,24 +225,22 @@
216
225
 
217
226
  <% if write_allowed? %>
218
227
  <div class="card-body border-bottom toggle-block-when-on bg-lightest">
219
- <div class="row">
220
- <div class="col-12 col-md-6 mb-3 mb-md-0">
221
- <form action="<%= script_name %>/features/<%= @feature.key %>/percentage_of_time" method="post">
222
- <%== csrf_input_tag %>
223
- <div class="btn-group">
224
- <% @percentages.each do |number| %>
225
- <input type="submit" name="value" value="<%= number %>%" class="btn btn-light btn-sm" <% if number == @feature.percentage_of_time_value %>disabled<% end %>>
226
- <% end %>
227
- </div>
228
- </form>
229
- </div>
230
- <div class="col-12 col-md-6">
231
- <form action="<%= script_name %>/features/<%= @feature.key %>/percentage_of_time" method="post" class="form-inline">
232
- <%== csrf_input_tag %>
233
- <input type="text" name="value" <% if @feature.percentage_of_time_value > 0 %>value="<%= @feature.percentage_of_time_value %>"<% end %> placeholder="custom (26, 32, etc.)" class="form-control form-control-sm mr-sm-2 mb-2 mb-md-0">
234
- <input type="submit" name="action" value="Save" class="btn btn-light btn-sm">
235
- </form>
236
- </div>
228
+ <div class="row align-items-center">
229
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/percentage_of_time" method="post" class="col">
230
+ <%== csrf_input_tag %>
231
+ <div class="btn-group">
232
+ <% @percentages.each do |number| %>
233
+ <input type="submit" name="value" value="<%= number %>%" class="btn btn-light" <% if number == @feature.percentage_of_time_value %>disabled<% end %>>
234
+ <% end %>
235
+ </div>
236
+ </form>
237
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/percentage_of_time" method="post" class="col-auto row">
238
+ <%== csrf_input_tag %>
239
+ <div class="col-auto">
240
+ <input style="width:5em;" type="number" max="100" min="0" name="value" <% if @feature.percentage_of_time_value > 0 %>value="<%= @feature.percentage_of_time_value %>"<% end %> title="Custom percentage (26, 32, etc.)" placeholder="0" class="form-control">
241
+ </div>
242
+ <div class="col-auto ms-auto"><input type="submit" name="action" value="Save" class="btn btn-primary"></div>
243
+ </form>
237
244
  </div>
238
245
  </div>
239
246
  <% end %>
@@ -242,19 +249,19 @@
242
249
 
243
250
  <% if write_allowed? %>
244
251
  <div class="card-body">
245
- <form action="<%= script_name %>/features/<%= @feature.key %>/boolean" method="post">
252
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>/boolean" method="post">
246
253
  <%== csrf_input_tag %>
247
254
 
248
255
  <div class="row">
249
256
  <% unless @feature.boolean_value %>
250
- <div class="col">
251
- <button type="submit" name="action" value="Enable" <% if Flipper::UI.configuration.confirm_fully_enable %>id="enable_feature__button"<% end %> class="btn btn-outline-success btn-block">
252
- <span class="d-block" data-toggle="tooltip"
253
- <% if Flipper::UI.configuration.confirm_fully_enable %>
254
- data-confirmation-text="<%= feature_name %>"
255
- <% end %>
256
- title="Enable for everyone"
257
- >
257
+ <div class="col d-grid">
258
+ <button type="submit" name="action" value="Enable" class="btn btn-outline-success"
259
+ <% if Flipper::UI.configuration.confirm_fully_enable %>
260
+ data-confirmation-prompt="Are you sure you want to fully enable this feature for everyone? Please enter the name of the feature to confirm it: <%= feature_name %>"
261
+ data-confirmation-text="<%= feature_name %>"
262
+ <% end %>
263
+ >
264
+ <span class="d-block" data-bs-toggle="tooltip" title="Enable for everyone">
258
265
  Fully Enable
259
266
  </span>
260
267
  </button>
@@ -262,14 +269,14 @@
262
269
  <% end %>
263
270
 
264
271
  <% unless @feature.off? %>
265
- <div class="col">
266
- <button type="submit" name="action" value="Disable" <% if Flipper::UI.configuration.confirm_disable %>id="disable_feature__button"<% end %> class="btn btn-outline-danger btn-block">
267
- <span class="d-block" data-toggle="tooltip"
268
- <% if Flipper::UI.configuration.confirm_disable %>
269
- data-confirmation-text="<%= feature_name %>"
270
- <% end %>
271
- title="Disable for everyone by clearing all percentages, groups and actors."
272
- >
272
+ <div class="col d-grid">
273
+ <button type="submit" name="action" value="Disable" class="btn btn-outline-danger"
274
+ <% if Flipper::UI.configuration.confirm_disable %>
275
+ data-confirmation-prompt="Are you sure you want to disable this feature for everyone? Please enter the name of the feature to confirm it: <%= feature_name %>"
276
+ data-confirmation-text="<%= feature_name %>"
277
+ <% end %>
278
+ >
279
+ <span class="d-block" data-bs-toggle="tooltip" title="Disable for everyone by clearing all percentages, groups and actors.">
273
280
  Disable
274
281
  </span>
275
282
  </button>
@@ -292,11 +299,17 @@
292
299
  <p>
293
300
  <%= Flipper::UI.configuration.delete.description %>
294
301
  </p>
295
- <form action="<%= script_name %>/features/<%= @feature.key %>" method="post">
302
+ <form action="<%= script_name %>/features/<%= Flipper::UI::Util.escape @feature.key %>" method="post">
296
303
  <%== csrf_input_tag %>
297
304
  <input type="hidden" id="feature_name" name="_feature" value="<%= feature_name %>">
298
305
  <input type="hidden" name="_method" value="DELETE">
299
- <button type="submit" name="action" value="Delete" id="delete_feature__button" data-confirmation-text="<%= feature_name %>" class="btn btn-outline-danger" data-toggle="tooltip" title="Remove feature from list of features and disable it." data-placement="right">Delete</button>
306
+ <button type="submit" name="action" value="Delete" id="delete_feature__button" class="btn btn-outline-danger" data-bs-toggle="tooltip" data-bs-placement="right"
307
+ data-confirmation-prompt="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: <%= feature_name%>"
308
+ data-confirmation-text="<%= feature_name %>"
309
+ title="Remove feature from list of features and disable it."
310
+ >
311
+ Delete
312
+ </button>
300
313
  </form>
301
314
  </div>
302
315
  </div>
@@ -1,11 +1,11 @@
1
1
  <% if @show_blank_slate %>
2
- <div class="jumbotron text-center m-0">
2
+ <div class="bg-light p-5 rounded-3 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>
6
6
  <%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
7
7
  <p>
8
- <a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
8
+ <a class="btn btn-primary" href="<%= script_name %>/features/new">Add Feature</a>
9
9
  </p>
10
10
  <%- end -%>
11
11
  <% else %>
@@ -13,7 +13,7 @@
13
13
  <p class="mb-1">You have not added any features to configure yet.</p>
14
14
  <%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
15
15
  <p class="mt-2">
16
- <a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
16
+ <a class="btn btn-primary btn-lg" href="<%= script_name %>/features/new">Add Feature</a>
17
17
  </p>
18
18
  <% else %>
19
19
  <p>
@@ -24,36 +24,36 @@
24
24
  <% end %>
25
25
  </div>
26
26
  <% else %>
27
- <div class="card">
28
- <div class="card-header">
29
- <%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
30
- <div class="float-right">
31
- <a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
32
- </div>
33
- <%- end -%>
34
- <h4 class="m-0">Features</h4>
27
+ <div class="list-group">
28
+ <div class="list-group-item bg-light">
29
+ <div class="row align-items-center">
30
+ <h4 class="col m-0">Features</h4>
31
+ <%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
32
+ <div class="col-auto">
33
+ <a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
34
+ </div>
35
+ <%- end -%>
36
+ </div>
35
37
  </div>
36
- <div class="card-body py-0">
37
- <% @features.each do |feature| %>
38
- <div class="feature row align-items-center mt-0 px-3 border-bottom">
39
- <div class="col-1 col-md-auto">
40
- <span class="status <%= feature.color_class %>" data-toggle="tooltip" title=<%= feature.state.to_s.capitalize %>></span>
38
+ <% @features.each do |feature| %>
39
+ <a href="<%= "#{script_name}/features/#{Flipper::UI::Util.escape(feature.key)}" %>" class="list-group-item list-group-item-action">
40
+ <div class="row align-items-center">
41
+ <div class="col-1 text-center">
42
+ <span class="status <%= feature.color_class %>" data-bs-toggle="tooltip" title=<%= feature.state.to_s.capitalize %>></span>
41
43
  </div>
42
- <div class="col-10">
43
- <a href="<%= "#{script_name}/features/#{feature.key}" %>" class="d-block px-0 py-3 btn text-left text-dark">
44
- <div class="text-truncate" style="font-weight: 500"><%= feature.key %></div>
45
- <% if Flipper::UI.configuration.show_feature_description_in_list? && Flipper::UI::Util.present?(feature.description) %>
46
- <div class="text-muted font-weight-light" style="line-height: 1.4; white-space: initial; padding: 8px 0">
47
- <%== Sanitize.fragment(feature.description, Sanitize::Config::BASIC) %>
48
- </div>
49
- <% end %>
50
- <div class="text-muted text-truncate">
51
- <%== feature.gates_in_words %>
44
+ <div class="col-11">
45
+ <div class="text-truncate" style="font-weight: 500"><%= feature.key %></div>
46
+ <% if Flipper::UI.configuration.show_feature_description_in_list? && Flipper::UI::Util.present?(feature.description) %>
47
+ <div class="text-muted fw-light" style="line-height: 1.4; white-space: initial; padding: 8px 0">
48
+ <%== Sanitize.fragment(feature.description, Flipper::UI::SANITIZE_LIST) %>
52
49
  </div>
53
- </a>
50
+ <% end %>
51
+ <div class="text-muted text-truncate">
52
+ <%== feature.gates_in_words %>
53
+ </div>
54
54
  </div>
55
55
  </div>
56
- <% end %>
57
- </div>
56
+ </a>
57
+ <% end %>
58
58
  </div>
59
59
  <% end %>
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <title><%= @page_title ? "#{@page_title} // " : "" %>Flipper</title>
4
+ <title><%= defined?(@page_title) ? "#{@page_title} // " : "" %>Flipper</title>
5
5
  <meta charset="utf-8">
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -12,43 +12,52 @@
12
12
  <body class="py-4">
13
13
  <div class="container mw-600">
14
14
  <%- unless Flipper::UI.configuration.banner_text.nil? -%>
15
- <div class="alert alert-<%= Flipper::UI.configuration.banner_class %> text-center font-weight-bold">
15
+ <div class="alert alert-<%= Flipper::UI.configuration.banner_class %> text-center fw-bold">
16
16
  <%== Sanitize.fragment(Flipper::UI.configuration.banner_text, Sanitize::Config::BASIC) %>
17
17
  </div>
18
18
  <%- end -%>
19
19
 
20
- <div class="container text-muted small mb-3">
21
- <a href="https://www.flippercloud.io/docs/ui?utm_source=oss&utm_medium=ui&utm_campaign=docs">Docs</a> &bull;
22
- <a href="<%= script_name %>/settings">Settings</a> &bull;
23
- Version: <%= Flipper::VERSION %>
24
- <a href="#" class="badge badge-warning ml-2 d-none" style="font-size:100%" id="new-version-badge" data-version="<%= Flipper::VERSION %>">
20
+ <div class="text-muted small text-end">
21
+ <a href="#" class="badge text-bg-warning ms-2 d-none" style="font-size:100%" id="new-version-badge" data-version="<%= Flipper::VERSION %>">
25
22
  </a>
26
23
 
27
24
  <% if Flipper.deprecated_ruby_version? %>
28
- <a href="https://github.com/flippercloud/flipper/pull/776" class="badge badge-danger ml-2" style="font-size:100%">
25
+ <a href="https://github.com/flippercloud/flipper/pull/776" class="badge text-bg-danger ms-2" style="font-size:100%">
29
26
  Ruby <%= RUBY_VERSION %> deprecated
30
27
  </a>
31
28
  <% end %>
32
29
 
33
30
  <% if defined?(Rails) && Flipper::Engine.deprecated_rails_version? %>
34
- <a href="https://github.com/flippercloud/flipper/pull/776" class="badge badge-danger ml-2" style="font-size:100%">
31
+ <a href="https://github.com/flippercloud/flipper/pull/776" class="badge text-bg-danger ms-2" style="font-size:100%">
35
32
  Rails <%= Rails.version %> deprecated
36
33
  </a>
37
34
  <% end %>
38
35
  </div>
36
+ <nav>
37
+ <ul class="nav nav-underline mb-3 align-items-center">
38
+ <% if Flipper::UI.configuration.application_href %>
39
+ <li class="nav-item">
40
+ <a class="nav-link" href="<%= Flipper::UI.configuration.application_href %>">
41
+ ‹ App
42
+ </a>
43
+ </li>
44
+ <% end %>
39
45
 
40
- <nav aria-label="breadcrumb">
41
- <ol class="breadcrumb bg-white border align-items-center mb-4">
42
- <% @breadcrumbs.each do |breadcrumb| %>
43
- <li class="breadcrumb-item <% if breadcrumb.active? %>active<% end %>">
44
- <% if breadcrumb.active? %>
45
- <%= breadcrumb.text %>
46
- <% else %>
47
- <a href="<%= breadcrumb.href %>"><%= breadcrumb.text %></a>
48
- <% end %>
46
+
47
+ <% Flipper::UI.configuration.nav_items.each do |item| %>
48
+ <li class="nav-item">
49
+ <a href="<%= url_for(item[:href]) %>" class="nav-link<%= " active" if request.url.start_with?(url_for(item[:href])) %>">
50
+ <%= item[:title] %>
51
+ </a>
49
52
  </li>
50
53
  <% end %>
51
- </ol>
54
+
55
+ <li class="nav-item ms-auto text-muted small">
56
+ <a href="https://www.flippercloud.io/docs/ui?utm_source=oss&utm_medium=ui&utm_campaign=docs">Docs</a>
57
+ &bull;
58
+ Version: <%= Flipper::VERSION %>
59
+ </li>
60
+ </ul>
52
61
  </nav>
53
62
 
54
63
  <div>
@@ -71,7 +80,6 @@
71
80
  <% end %>
72
81
  </div>
73
82
 
74
- <script src="<%= script_name + jquery_js[:src] %>" integrity="<%= jquery_js[:hash] %>" crossorigin="anonymous"></script>
75
83
  <script src="<%= script_name + popper_js[:src] %>" integrity="<%= popper_js[:hash] %>" crossorigin="anonymous"></script>
76
84
  <script src="<%= script_name + bootstrap_js[:src] %>" integrity="<%= bootstrap_js[:hash] %>" crossorigin="anonymous"></script>
77
85
  <script src="<%= script_name %>/js/application.js?v=<%= Flipper::VERSION %>"></script>
@@ -1,3 +1,30 @@
1
+ <% if params.key?("error") %>
2
+ <div class="alert alert-danger"><%= params["error"] %></div>
3
+ <% end %>
4
+
5
+ <div class="card mb-4">
6
+ <div class="card-header">
7
+ <h4 class="m-0">Migrate to Flipper Cloud</h4>
8
+ </div>
9
+ <div class="card-body">
10
+ <p>Flipper Cloud gives you audit history, rollback, finer-grained permissions, and multi-environment sync. We even have a free tier!</p>
11
+
12
+ <ul class="mb-3">
13
+ <li>Audit log of every feature change</li>
14
+ <li>Instant rollback to any previous state</li>
15
+ <li>Team permissions and approval workflows</li>
16
+ <li>Sync flags across environments</li>
17
+ </ul>
18
+
19
+ <p>You have <strong><%= flipper.features.count %></strong> <%= flipper.features.count == 1 ? 'feature' : 'features' %> ready to migrate.</p>
20
+
21
+ <form action="<%= script_name %>/settings/cloud" method="post">
22
+ <%== csrf_input_tag %>
23
+ <input type="submit" value="Migrate to Flipper Cloud" class="btn btn-primary">
24
+ </form>
25
+ </div>
26
+ </div>
27
+
1
28
  <div class="card mb-4 ">
2
29
  <div class="card-header">
3
30
  <h4 class="m-0">Export</h4>
@@ -6,7 +33,7 @@
6
33
  <form action="<%= script_name %>/settings/export" method="post">
7
34
  <%== csrf_input_tag %>
8
35
  <p>Download all your feature data in a single file.</p>
9
- <input type="submit" value="Download" class="btn btn-sm btn-light">
36
+ <input type="submit" value="Download" class="btn btn-light">
10
37
  </form>
11
38
  </div>
12
39
  </div>
@@ -17,10 +44,10 @@
17
44
  </div>
18
45
  <div class="card-body">
19
46
  <p>Import a backup file to restore your feature data. This will overwrite any of your existing feature data so be sure you are uploading the correct export.</p>
20
- <form action="<%= script_name %>/settings/import" method="post" class="form-inline" enctype="multipart/form-data">
47
+ <form action="<%= script_name %>/settings/import" method="post" class="row" enctype="multipart/form-data">
21
48
  <%== csrf_input_tag %>
22
- <input type="file" name="file" class="form-control form-control-sm mr-sm-2 mb-2 mb-sm-0">
23
- <input type="submit" value="Import" class="btn btn-sm btn-light">
49
+ <div class="col-auto"><input type="file" name="file" class="form-control"></div>
50
+ <div class="col-auto"><input type="submit" value="Import" class="btn btn-light"></div>
24
51
  </form>
25
52
  </div>
26
53
  </div>
data/lib/flipper/ui.rb CHANGED
@@ -20,12 +20,26 @@ module Flipper
20
20
 
21
21
  def self.app(flipper = nil, options = {})
22
22
  env_key = options.fetch(:env_key, 'flipper')
23
- rack_protection_options = options.fetch(:rack_protection, use: :authenticity_token)
23
+ rack_protection_options = if options.key?(:rack_protection)
24
+ options[:rack_protection]
25
+ else
26
+ {}
27
+ end
24
28
 
25
29
  app = ->(_) { [200, { Rack::CONTENT_TYPE => 'text/html' }, ['']] }
26
30
  builder = Rack::Builder.new
27
31
  yield builder if block_given?
28
- builder.use Rack::Protection, rack_protection_options
32
+
33
+ # Only use Rack::Protection::AuthenticityToken if no other options are
34
+ # provided. Should avoid some pain for some people. If any options are
35
+ # provided then go whole hog and include all of Rack::Protection for
36
+ # backwards compatibility.
37
+ if rack_protection_options.empty?
38
+ builder.use Rack::Protection::AuthenticityToken
39
+ else
40
+ builder.use Rack::Protection, rack_protection_options
41
+ end
42
+
29
43
  builder.use Rack::MethodOverride
30
44
  builder.use Flipper::Middleware::SetupEnv, flipper, env_key: env_key
31
45
  builder.use Flipper::UI::Middleware, flipper: flipper, env_key: env_key
@@ -1,5 +1,5 @@
1
1
  module Flipper
2
- VERSION = '1.2.1'.freeze
2
+ VERSION = '1.4.0'.freeze
3
3
 
4
4
  REQUIRED_RUBY_VERSION = '2.6'.freeze
5
5
  NEXT_REQUIRED_RUBY_VERSION = '3.0'.freeze