subflag-rails 0.6.2 → 0.6.3
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/CHANGELOG.md +8 -0
- data/app/views/subflag/rails/flags/edit.html.erb +73 -13
- data/lib/subflag/rails/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f82250e7b81df25f2937196fdb48473b88fbdf8be78b6ae7179f22cd3bc4c458
|
|
4
|
+
data.tar.gz: 8af66017baa6b4400ce4f8e6c1cff4d808bbb6ebe5957da072c4b94e46b2f88e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e8758f5bcc56db3f8bf6c5af89975ea348d171cc5812e05e4daaa0c0b975776ec49d5e7ea4935d469aa6e7fb99ac1887e2903147795f018508b5bd212d28d70
|
|
7
|
+
data.tar.gz: '087753e17e0234bab20f792ae92c120025d6aa780ab0dee3b42aec8aaea652db43f3e68a3175f7f640d87cba5f51c31f9ae6e0feba835984aae5d478859b67ca'
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.3] - 2026-01-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Full CSP compliance for Admin UI**: Remove all inline event handlers
|
|
10
|
+
- Use event delegation pattern with `data-action` attributes
|
|
11
|
+
- Works with strict `script-src 'nonce-xxx'` policies (no `'unsafe-inline'` required)
|
|
12
|
+
|
|
5
13
|
## [0.6.2] - 2026-01-22
|
|
6
14
|
|
|
7
15
|
### Fixed
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<%= render "form", flag: @flag %>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
<div class="card">
|
|
11
|
+
<div class="card" id="targeting-card">
|
|
12
12
|
<h2>Targeting Rules</h2>
|
|
13
13
|
<p class="text-muted mb-10">Rules are evaluated in order. First match wins. If no rules match, the default value is used.</p>
|
|
14
14
|
|
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
<!-- Rules rendered by JS -->
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
<button type="button" class="btn mt-10"
|
|
19
|
+
<button type="button" class="btn mt-10" data-action="add-rule">+ Add Rule</button>
|
|
20
20
|
|
|
21
21
|
<input type="hidden" name="targeting_rules_json" id="targeting-rules-json" value="<%= @flag.targeting_rules.to_json %>">
|
|
22
22
|
|
|
23
23
|
<div class="mt-10">
|
|
24
|
-
<button type="button" class="btn btn-primary"
|
|
24
|
+
<button type="button" class="btn btn-primary" data-action="save-rules">Save Rules</button>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
|
|
@@ -86,6 +86,66 @@
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
renderRules();
|
|
89
|
+
setupEventDelegation();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function setupEventDelegation() {
|
|
93
|
+
const container = document.getElementById("targeting-card");
|
|
94
|
+
|
|
95
|
+
// Handle clicks (buttons)
|
|
96
|
+
container.addEventListener("click", function(e) {
|
|
97
|
+
const target = e.target.closest("[data-action]");
|
|
98
|
+
if (!target) return;
|
|
99
|
+
|
|
100
|
+
const action = target.dataset.action;
|
|
101
|
+
const ruleIndex = parseInt(target.dataset.ruleIndex, 10);
|
|
102
|
+
const condIndex = parseInt(target.dataset.condIndex, 10);
|
|
103
|
+
|
|
104
|
+
switch (action) {
|
|
105
|
+
case "add-rule":
|
|
106
|
+
addRule();
|
|
107
|
+
break;
|
|
108
|
+
case "remove-rule":
|
|
109
|
+
removeRule(ruleIndex);
|
|
110
|
+
break;
|
|
111
|
+
case "add-condition":
|
|
112
|
+
addCondition(ruleIndex);
|
|
113
|
+
break;
|
|
114
|
+
case "remove-condition":
|
|
115
|
+
removeCondition(ruleIndex, condIndex);
|
|
116
|
+
break;
|
|
117
|
+
case "save-rules":
|
|
118
|
+
saveRules();
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Handle input changes
|
|
124
|
+
container.addEventListener("change", function(e) {
|
|
125
|
+
const target = e.target;
|
|
126
|
+
const action = target.dataset.action;
|
|
127
|
+
if (!action) return;
|
|
128
|
+
|
|
129
|
+
const ruleIndex = parseInt(target.dataset.ruleIndex, 10);
|
|
130
|
+
const condIndex = parseInt(target.dataset.condIndex, 10);
|
|
131
|
+
const field = target.dataset.field;
|
|
132
|
+
const value = target.value;
|
|
133
|
+
|
|
134
|
+
switch (action) {
|
|
135
|
+
case "update-rule-value":
|
|
136
|
+
updateRuleValue(ruleIndex, value);
|
|
137
|
+
break;
|
|
138
|
+
case "update-rule-percentage":
|
|
139
|
+
updateRulePercentage(ruleIndex, value);
|
|
140
|
+
break;
|
|
141
|
+
case "update-logic":
|
|
142
|
+
updateLogic(ruleIndex, value);
|
|
143
|
+
break;
|
|
144
|
+
case "update-condition":
|
|
145
|
+
updateCondition(ruleIndex, condIndex, field, value);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
89
149
|
}
|
|
90
150
|
|
|
91
151
|
function renderRules() {
|
|
@@ -103,26 +163,26 @@
|
|
|
103
163
|
<div class="rule" data-rule-index="${ruleIndex}">
|
|
104
164
|
<div class="rule-header">
|
|
105
165
|
<strong>Rule ${ruleIndex + 1}</strong>
|
|
106
|
-
<button type="button" class="remove-btn"
|
|
166
|
+
<button type="button" class="remove-btn" data-action="remove-rule" data-rule-index="${ruleIndex}">×</button>
|
|
107
167
|
</div>
|
|
108
168
|
<div class="rule-value">
|
|
109
169
|
<label>Return value:</label>
|
|
110
|
-
<input type="text" value="${escapeHtml(rule.value || '')}"
|
|
170
|
+
<input type="text" value="${escapeHtml(rule.value || '')}" data-action="update-rule-value" data-rule-index="${ruleIndex}" placeholder="value when matched">
|
|
111
171
|
</div>
|
|
112
172
|
<div class="rule-percentage">
|
|
113
173
|
<label>Percentage rollout:</label>
|
|
114
|
-
<input type="number" min="0" max="100" value="${percentage ?? ''}"
|
|
174
|
+
<input type="number" min="0" max="100" value="${percentage ?? ''}" data-action="update-rule-percentage" data-rule-index="${ruleIndex}" placeholder="">
|
|
115
175
|
<span>% of users</span>
|
|
116
176
|
<small style="color: #666;">(leave empty to match all)</small>
|
|
117
177
|
</div>
|
|
118
178
|
<div class="rule-logic" ${!hasConditions && percentage ? 'style="opacity: 0.5;"' : ''}>
|
|
119
|
-
<label><input type="radio" name="logic-${ruleIndex}" value="AND" ${logicType === 'AND' ? 'checked' : ''}
|
|
120
|
-
<label><input type="radio" name="logic-${ruleIndex}" value="OR" ${logicType === 'OR' ? 'checked' : ''}
|
|
179
|
+
<label><input type="radio" name="logic-${ruleIndex}" value="AND" ${logicType === 'AND' ? 'checked' : ''} data-action="update-logic" data-rule-index="${ruleIndex}"> ALL match</label>
|
|
180
|
+
<label><input type="radio" name="logic-${ruleIndex}" value="OR" ${logicType === 'OR' ? 'checked' : ''} data-action="update-logic" data-rule-index="${ruleIndex}"> ANY match</label>
|
|
121
181
|
</div>
|
|
122
182
|
<div class="conditions" id="conditions-${ruleIndex}">
|
|
123
183
|
${conditions.map((c, cIndex) => renderCondition(c, ruleIndex, cIndex)).join("")}
|
|
124
184
|
</div>
|
|
125
|
-
<button type="button" class="btn btn-sm"
|
|
185
|
+
<button type="button" class="btn btn-sm" data-action="add-condition" data-rule-index="${ruleIndex}">+ Add Condition</button>
|
|
126
186
|
<small style="display: block; margin-top: 5px; color: #666;">${hasConditions && percentage ? 'User must match conditions AND be in percentage' : ''}</small>
|
|
127
187
|
</div>
|
|
128
188
|
`;
|
|
@@ -137,12 +197,12 @@
|
|
|
137
197
|
|
|
138
198
|
return `
|
|
139
199
|
<div class="condition">
|
|
140
|
-
<input type="text" placeholder="attribute" value="${escapeHtml(condition.attribute || '')}"
|
|
141
|
-
<select
|
|
200
|
+
<input type="text" placeholder="attribute" value="${escapeHtml(condition.attribute || '')}" data-action="update-condition" data-rule-index="${ruleIndex}" data-cond-index="${condIndex}" data-field="attribute">
|
|
201
|
+
<select data-action="update-condition" data-rule-index="${ruleIndex}" data-cond-index="${condIndex}" data-field="operator">
|
|
142
202
|
${operatorOptions}
|
|
143
203
|
</select>
|
|
144
|
-
<input type="text" placeholder="value" value="${escapeHtml(valueDisplay)}"
|
|
145
|
-
<button type="button" class="remove-btn"
|
|
204
|
+
<input type="text" placeholder="value" value="${escapeHtml(valueDisplay)}" data-action="update-condition" data-rule-index="${ruleIndex}" data-cond-index="${condIndex}" data-field="value">
|
|
205
|
+
<button type="button" class="remove-btn" data-action="remove-condition" data-rule-index="${ruleIndex}" data-cond-index="${condIndex}">×</button>
|
|
146
206
|
</div>
|
|
147
207
|
`;
|
|
148
208
|
}
|