switchlet 0.1.4 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +14 -4
- data/app/controllers/switchlet/flags_controller.rb +9 -1
- data/app/views/switchlet/flags/index.html.erb +103 -3
- data/config/routes.rb +1 -1
- data/lib/generators/switchlet/add_description_generator.rb +22 -0
- data/lib/generators/switchlet/templates/add_description_to_switchlet_flags.rb +7 -0
- data/lib/generators/switchlet/templates/create_switchlet_flags.rb +3 -2
- data/lib/switchlet/version.rb +1 -1
- data/lib/switchlet.rb +15 -4
- data/switchlet-0.1.4.gem +0 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a8309b12036dc97dd351cf840b260b942f7a481b16f2d2256449f3d14afe1175
|
|
4
|
+
data.tar.gz: b89973e26a4365e673141e017246d67c066af6a5704f860ce76cc7efc311ffa6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5693ae735fde0e0a420e09433a7601f0dda586aa2afd0cf3309a56a0e6430beafc6036e02cbd8cebfdbf9af98c40bbc1e87e4cc16daeefe91893d613e5431f1e
|
|
7
|
+
data.tar.gz: d1e017ab424b46f7e28d82905e6ab281305cd188ccf873f7b645c5c7888e2b865c2c3a069fdcad28d04f3e0b974bcd42257f153b7c78d92353b0a7f5448c3119
|
data/README.md
CHANGED
|
@@ -27,14 +27,23 @@ Switchlet.enabled?(:my_feature) # => false
|
|
|
27
27
|
# Enable a feature
|
|
28
28
|
Switchlet.enable!(:my_feature) # => true
|
|
29
29
|
|
|
30
|
+
# Enable a feature with description
|
|
31
|
+
Switchlet.enable!(:my_feature, description: "New payment system") # => true
|
|
32
|
+
|
|
30
33
|
# Disable a feature
|
|
31
34
|
Switchlet.disable!(:my_feature) # => false
|
|
32
35
|
|
|
36
|
+
# Disable with description
|
|
37
|
+
Switchlet.disable!(:my_feature, description: "Temporarily disabled") # => false
|
|
38
|
+
|
|
39
|
+
# Set or update description
|
|
40
|
+
Switchlet.set_description!(:my_feature, "Updated description") # => "Updated description"
|
|
41
|
+
|
|
33
42
|
# Delete a feature flag
|
|
34
43
|
Switchlet.delete!(:my_feature) # => nil
|
|
35
44
|
|
|
36
|
-
# List all feature flags
|
|
37
|
-
Switchlet.list # => [{ name: "my_feature", enabled: true, updated_at: Time }]
|
|
45
|
+
# List all feature flags (includes descriptions)
|
|
46
|
+
Switchlet.list # => [{ name: "my_feature", enabled: true, description: "New payment system", updated_at: Time }]
|
|
38
47
|
```
|
|
39
48
|
|
|
40
49
|
## Web UI
|
|
@@ -47,9 +56,10 @@ mount Switchlet::Engine => "/switchlet"
|
|
|
47
56
|
```
|
|
48
57
|
|
|
49
58
|
Then visit `/switchlet` in your browser to:
|
|
50
|
-
- View all feature flags
|
|
59
|
+
- View all feature flags with descriptions
|
|
51
60
|
- Toggle flags ON/OFF
|
|
52
|
-
- Create new flags
|
|
61
|
+
- Create new flags with optional descriptions
|
|
62
|
+
- Edit descriptions inline (click to edit)
|
|
53
63
|
- Delete existing flags
|
|
54
64
|
|
|
55
65
|
### Securing the Web Interface
|
|
@@ -21,14 +21,22 @@ module Switchlet
|
|
|
21
21
|
|
|
22
22
|
def create
|
|
23
23
|
flag_name = params[:flag_name].strip
|
|
24
|
+
description = params[:description]&.strip
|
|
24
25
|
if flag_name.present?
|
|
25
|
-
Switchlet.enable!(flag_name)
|
|
26
|
+
Switchlet.enable!(flag_name, description: description)
|
|
26
27
|
redirect_to switchlet.flags_path, notice: "Flag '#{flag_name}' created and enabled"
|
|
27
28
|
else
|
|
28
29
|
redirect_to switchlet.flags_path, alert: "Flag name cannot be empty"
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
|
|
33
|
+
def update
|
|
34
|
+
flag_name = params[:name]
|
|
35
|
+
description = params[:description]
|
|
36
|
+
Switchlet.set_description!(flag_name, description)
|
|
37
|
+
redirect_to switchlet.flags_path, notice: "Description updated for '#{flag_name}'"
|
|
38
|
+
end
|
|
39
|
+
|
|
32
40
|
def destroy
|
|
33
41
|
flag_name = params[:name]
|
|
34
42
|
Switchlet.delete!(flag_name)
|
|
@@ -52,16 +52,26 @@
|
|
|
52
52
|
color: #2c3e50;
|
|
53
53
|
}
|
|
54
54
|
.form-group {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: 10px;
|
|
58
|
+
}
|
|
59
|
+
.form-row {
|
|
55
60
|
display: flex;
|
|
56
61
|
gap: 10px;
|
|
57
62
|
align-items: center;
|
|
58
63
|
}
|
|
59
|
-
input[type="text"] {
|
|
64
|
+
input[type="text"], textarea {
|
|
60
65
|
flex: 1;
|
|
61
66
|
padding: 8px 12px;
|
|
62
67
|
border: 1px solid #ddd;
|
|
63
68
|
border-radius: 4px;
|
|
64
69
|
font-size: 14px;
|
|
70
|
+
font-family: inherit;
|
|
71
|
+
}
|
|
72
|
+
textarea {
|
|
73
|
+
resize: vertical;
|
|
74
|
+
min-height: 40px;
|
|
65
75
|
}
|
|
66
76
|
.btn {
|
|
67
77
|
padding: 8px 16px;
|
|
@@ -136,6 +146,36 @@
|
|
|
136
146
|
padding: 40px;
|
|
137
147
|
color: #7f8c8d;
|
|
138
148
|
}
|
|
149
|
+
.text-muted {
|
|
150
|
+
color: #7f8c8d;
|
|
151
|
+
font-style: italic;
|
|
152
|
+
}
|
|
153
|
+
.description-cell {
|
|
154
|
+
min-width: 200px;
|
|
155
|
+
}
|
|
156
|
+
.description-text {
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
display: block;
|
|
159
|
+
min-height: 20px;
|
|
160
|
+
padding: 4px;
|
|
161
|
+
border-radius: 4px;
|
|
162
|
+
transition: background-color 0.2s;
|
|
163
|
+
}
|
|
164
|
+
.description-text:hover {
|
|
165
|
+
background-color: #f8f9fa;
|
|
166
|
+
}
|
|
167
|
+
.description-input {
|
|
168
|
+
width: 100%;
|
|
169
|
+
margin-bottom: 8px;
|
|
170
|
+
}
|
|
171
|
+
.description-actions {
|
|
172
|
+
display: flex;
|
|
173
|
+
gap: 4px;
|
|
174
|
+
}
|
|
175
|
+
.btn-sm {
|
|
176
|
+
padding: 4px 8px;
|
|
177
|
+
font-size: 12px;
|
|
178
|
+
}
|
|
139
179
|
</style>
|
|
140
180
|
</head>
|
|
141
181
|
<body>
|
|
@@ -154,8 +194,11 @@
|
|
|
154
194
|
<h2>Create New Flag</h2>
|
|
155
195
|
<%= form_with url: switchlet.flags_path, local: true do |form| %>
|
|
156
196
|
<div class="form-group">
|
|
157
|
-
|
|
158
|
-
|
|
197
|
+
<div class="form-row">
|
|
198
|
+
<%= form.text_field :flag_name, placeholder: "Enter flag name...", required: true %>
|
|
199
|
+
<%= form.submit "Create Flag", class: "btn btn-primary" %>
|
|
200
|
+
</div>
|
|
201
|
+
<%= form.text_area :description, placeholder: "Optional description...", rows: 2 %>
|
|
159
202
|
</div>
|
|
160
203
|
<% end %>
|
|
161
204
|
</div>
|
|
@@ -170,6 +213,7 @@
|
|
|
170
213
|
<thead>
|
|
171
214
|
<tr>
|
|
172
215
|
<th>Flag Name</th>
|
|
216
|
+
<th>Description</th>
|
|
173
217
|
<th>Status</th>
|
|
174
218
|
<th>Last Updated</th>
|
|
175
219
|
<th>Actions</th>
|
|
@@ -179,6 +223,28 @@
|
|
|
179
223
|
<% @flags.each do |flag| %>
|
|
180
224
|
<tr>
|
|
181
225
|
<td><strong><%= flag[:name] %></strong></td>
|
|
226
|
+
<td>
|
|
227
|
+
<div class="description-cell" data-flag="<%= flag[:name] %>">
|
|
228
|
+
<span class="description-text" onclick="editDescription('<%= flag[:name] %>')">
|
|
229
|
+
<% if flag[:description].present? %>
|
|
230
|
+
<%= flag[:description] %>
|
|
231
|
+
<% else %>
|
|
232
|
+
<span class="text-muted">Click to add description</span>
|
|
233
|
+
<% end %>
|
|
234
|
+
</span>
|
|
235
|
+
<div class="description-edit" style="display: none;">
|
|
236
|
+
<%= form_with url: switchlet.flag_path(flag[:name]), method: :patch, local: true, class: "description-form" do |form| %>
|
|
237
|
+
<%= form.text_area :description, value: flag[:description], rows: 2,
|
|
238
|
+
class: "description-input",
|
|
239
|
+
placeholder: "Enter description..." %>
|
|
240
|
+
<div class="description-actions">
|
|
241
|
+
<%= form.submit "Save", class: "btn btn-success btn-sm" %>
|
|
242
|
+
<button type="button" class="btn btn-secondary btn-sm" onclick="cancelEdit('<%= flag[:name] %>')">Cancel</button>
|
|
243
|
+
</div>
|
|
244
|
+
<% end %>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
</td>
|
|
182
248
|
<td>
|
|
183
249
|
<span class="<%= flag[:enabled] ? 'status-enabled' : 'status-disabled' %>">
|
|
184
250
|
<%= flag[:enabled] ? 'ENABLED' : 'DISABLED' %>
|
|
@@ -206,5 +272,39 @@
|
|
|
206
272
|
</table>
|
|
207
273
|
<% end %>
|
|
208
274
|
</div>
|
|
275
|
+
|
|
276
|
+
<script>
|
|
277
|
+
function editDescription(flagName) {
|
|
278
|
+
const cell = document.querySelector(`[data-flag="${flagName}"]`);
|
|
279
|
+
const textSpan = cell.querySelector('.description-text');
|
|
280
|
+
const editDiv = cell.querySelector('.description-edit');
|
|
281
|
+
const input = cell.querySelector('.description-input');
|
|
282
|
+
|
|
283
|
+
textSpan.style.display = 'none';
|
|
284
|
+
editDiv.style.display = 'block';
|
|
285
|
+
input.focus();
|
|
286
|
+
input.select();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function cancelEdit(flagName) {
|
|
290
|
+
const cell = document.querySelector(`[data-flag="${flagName}"]`);
|
|
291
|
+
const textSpan = cell.querySelector('.description-text');
|
|
292
|
+
const editDiv = cell.querySelector('.description-edit');
|
|
293
|
+
|
|
294
|
+
textSpan.style.display = 'block';
|
|
295
|
+
editDiv.style.display = 'none';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Escape key to cancel editing
|
|
299
|
+
document.addEventListener('keydown', function(e) {
|
|
300
|
+
if (e.key === 'Escape') {
|
|
301
|
+
const activeEdit = document.querySelector('.description-edit[style*="block"]');
|
|
302
|
+
if (activeEdit) {
|
|
303
|
+
const flagName = activeEdit.closest('[data-flag]').dataset.flag;
|
|
304
|
+
cancelEdit(flagName);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
</script>
|
|
209
309
|
</body>
|
|
210
310
|
</html>
|
data/config/routes.rb
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/migration"
|
|
5
|
+
|
|
6
|
+
module Switchlet
|
|
7
|
+
module Generators
|
|
8
|
+
class AddDescriptionGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
def self.next_migration_number(path)
|
|
14
|
+
ActiveRecord::Generators::Base.next_migration_number(path)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_migration_file
|
|
18
|
+
migration_template "add_description_to_switchlet_flags.rb", "db/migrate/add_description_to_switchlet_flags.rb"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
class CreateSwitchletFlags < ActiveRecord::Migration[6.1]
|
|
4
4
|
def change
|
|
5
5
|
create_table :switchlet_flags do |t|
|
|
6
|
-
t.string :name,
|
|
7
|
-
t.boolean :enabled,
|
|
6
|
+
t.string :name, null: false
|
|
7
|
+
t.boolean :enabled, null: false, default: false
|
|
8
|
+
t.text :description
|
|
8
9
|
t.timestamps
|
|
9
10
|
end
|
|
10
11
|
add_index :switchlet_flags, :name, unique: true
|
data/lib/switchlet/version.rb
CHANGED
data/lib/switchlet.rb
CHANGED
|
@@ -21,15 +21,19 @@ module Switchlet
|
|
|
21
21
|
Flag.find_by(name: name.to_s)&.enabled || false
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def self.enable!(name)
|
|
24
|
+
def self.enable!(name, description: nil)
|
|
25
25
|
flag = Flag.find_or_create_by(name: name.to_s)
|
|
26
|
-
|
|
26
|
+
attrs = { enabled: true }
|
|
27
|
+
attrs[:description] = description if description
|
|
28
|
+
flag.update!(attrs)
|
|
27
29
|
true
|
|
28
30
|
end
|
|
29
31
|
|
|
30
|
-
def self.disable!(name)
|
|
32
|
+
def self.disable!(name, description: nil)
|
|
31
33
|
flag = Flag.find_or_create_by(name: name.to_s)
|
|
32
|
-
|
|
34
|
+
attrs = { enabled: false }
|
|
35
|
+
attrs[:description] = description if description
|
|
36
|
+
flag.update!(attrs)
|
|
33
37
|
false
|
|
34
38
|
end
|
|
35
39
|
|
|
@@ -43,8 +47,15 @@ module Switchlet
|
|
|
43
47
|
{
|
|
44
48
|
name: flag.name,
|
|
45
49
|
enabled: flag.enabled,
|
|
50
|
+
description: flag.description,
|
|
46
51
|
updated_at: flag.updated_at
|
|
47
52
|
}
|
|
48
53
|
end
|
|
49
54
|
end
|
|
55
|
+
|
|
56
|
+
def self.set_description!(name, description)
|
|
57
|
+
flag = Flag.find_or_create_by(name: name.to_s)
|
|
58
|
+
flag.update!(description: description)
|
|
59
|
+
flag.description
|
|
60
|
+
end
|
|
50
61
|
end
|
data/switchlet-0.1.4.gem
ADDED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: switchlet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- komagata
|
|
@@ -65,7 +65,9 @@ files:
|
|
|
65
65
|
- app/controllers/switchlet/flags_controller.rb
|
|
66
66
|
- app/views/switchlet/flags/index.html.erb
|
|
67
67
|
- config/routes.rb
|
|
68
|
+
- lib/generators/switchlet/add_description_generator.rb
|
|
68
69
|
- lib/generators/switchlet/install_generator.rb
|
|
70
|
+
- lib/generators/switchlet/templates/add_description_to_switchlet_flags.rb
|
|
69
71
|
- lib/generators/switchlet/templates/create_switchlet_flags.rb
|
|
70
72
|
- lib/generators/switchlet/templates/switchlet.rb
|
|
71
73
|
- lib/switchlet.rb
|
|
@@ -79,6 +81,7 @@ files:
|
|
|
79
81
|
- switchlet-0.1.1.gem
|
|
80
82
|
- switchlet-0.1.2.gem
|
|
81
83
|
- switchlet-0.1.3.gem
|
|
84
|
+
- switchlet-0.1.4.gem
|
|
82
85
|
homepage: https://github.com/komagata/switchlet
|
|
83
86
|
licenses:
|
|
84
87
|
- MIT
|