subflag-rails 0.6.2 → 0.6.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4da7fbf4afd2e81121d78a29b650556e3c70ae35b80bee1f3edc484b25e67151
4
- data.tar.gz: 2a8bf9240dfc0875fbb6bf7e3dde7ea608c606125aaeb00affbf84b1cf6e9851
3
+ metadata.gz: e50478370bd25d61af6b4ba0e0d6c67544e5d0ae2403b50495584f16f549e3e6
4
+ data.tar.gz: 54e5cc2ef88f6053e71aa23fa7c380350f2cddc93fd3f92e75b5e6a13e6934bb
5
5
  SHA512:
6
- metadata.gz: a9d2171cb2ef2a919701dfc5a4e2a89bd64f498bca92dc5a77779982c5357f427f8894bd368250c94c6ff26db2b67ccc4f9f9aa787c5bccd9a4b304946413c50
7
- data.tar.gz: 9476c50e529d665d30f5bda1355f5a21c4009756124f3ddaf1efb5e16ccae668b572c0e30b310a438f8a6cf3a0c34040e1992304370d428e29a71e1ec027fe0c
6
+ metadata.gz: 96240398a0e33351f4a977e02fa0259637c37b5f1b576504fea0486ff96ecc62af1da1f874f1139c400ce082b796661c8e1bdea4ec357e328bc7c9021289733d
7
+ data.tar.gz: e42adc498fa3dffe58a0945a1c3c240a2d90509e02aef9d21c4c91f117a4358ffb620a4b0fc0e31c9827e7826275b5b7233d5dee3f6904c3284a39e809250620
data/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.6.4] - 2026-01-23
6
+
7
+ ### Changed
8
+
9
+ - Updated documentation to recommend `subflag-openfeature-provider >= 0.3.2`
10
+ - v0.3.2 fixes provider initialization error with OpenFeature SDK
11
+
12
+ ## [0.6.3] - 2026-01-22
13
+
14
+ ### Fixed
15
+
16
+ - **Full CSP compliance for Admin UI**: Remove all inline event handlers
17
+ - Use event delegation pattern with `data-action` attributes
18
+ - Works with strict `script-src 'nonce-xxx'` policies (no `'unsafe-inline'` required)
19
+
5
20
  ## [0.6.2] - 2026-01-22
6
21
 
7
22
  ### Fixed
data/README.md CHANGED
@@ -64,7 +64,7 @@ Dashboard, environments, percentage rollouts, and user targeting.
64
64
 
65
65
  ```ruby
66
66
  gem "subflag-rails"
67
- gem "subflag-openfeature-provider"
67
+ gem "subflag-openfeature-provider", ">= 0.3.2"
68
68
  ```
69
69
 
70
70
  ```bash
@@ -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" onclick="addRule()">+ Add Rule</button>
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" onclick="saveRules()">Save Rules</button>
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" onclick="removeRule(${ruleIndex})">&times;</button>
166
+ <button type="button" class="remove-btn" data-action="remove-rule" data-rule-index="${ruleIndex}">&times;</button>
107
167
  </div>
108
168
  <div class="rule-value">
109
169
  <label>Return value:</label>
110
- <input type="text" value="${escapeHtml(rule.value || '')}" onchange="updateRuleValue(${ruleIndex}, this.value)" placeholder="value when matched">
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 ?? ''}" onchange="updateRulePercentage(${ruleIndex}, this.value)" placeholder="">
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' : ''} onchange="updateLogic(${ruleIndex}, 'AND')"> ALL match</label>
120
- <label><input type="radio" name="logic-${ruleIndex}" value="OR" ${logicType === 'OR' ? 'checked' : ''} onchange="updateLogic(${ruleIndex}, 'OR')"> ANY match</label>
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" onclick="addCondition(${ruleIndex})">+ Add Condition</button>
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 || '')}" onchange="updateCondition(${ruleIndex}, ${condIndex}, 'attribute', this.value)">
141
- <select onchange="updateCondition(${ruleIndex}, ${condIndex}, 'operator', this.value)">
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)}" onchange="updateCondition(${ruleIndex}, ${condIndex}, 'value', this.value)">
145
- <button type="button" class="remove-btn" onclick="removeCondition(${ruleIndex}, ${condIndex})">&times;</button>
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}">&times;</button>
146
206
  </div>
147
207
  `;
148
208
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Subflag
4
4
  module Rails
5
- VERSION = "0.6.2"
5
+ VERSION = "0.6.4"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subflag-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Subflag
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-22 00:00:00.000000000 Z
11
+ date: 2026-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: openfeature-sdk