decision_agent 0.1.3 → 0.1.6
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 +84 -233
- data/lib/decision_agent/ab_testing/ab_test.rb +197 -0
- data/lib/decision_agent/ab_testing/ab_test_assignment.rb +76 -0
- data/lib/decision_agent/ab_testing/ab_test_manager.rb +317 -0
- data/lib/decision_agent/ab_testing/ab_testing_agent.rb +188 -0
- data/lib/decision_agent/ab_testing/storage/activerecord_adapter.rb +155 -0
- data/lib/decision_agent/ab_testing/storage/adapter.rb +67 -0
- data/lib/decision_agent/ab_testing/storage/memory_adapter.rb +116 -0
- data/lib/decision_agent/agent.rb +5 -3
- data/lib/decision_agent/auth/access_audit_logger.rb +122 -0
- data/lib/decision_agent/auth/authenticator.rb +127 -0
- data/lib/decision_agent/auth/password_reset_manager.rb +57 -0
- data/lib/decision_agent/auth/password_reset_token.rb +33 -0
- data/lib/decision_agent/auth/permission.rb +29 -0
- data/lib/decision_agent/auth/permission_checker.rb +43 -0
- data/lib/decision_agent/auth/rbac_adapter.rb +278 -0
- data/lib/decision_agent/auth/rbac_config.rb +51 -0
- data/lib/decision_agent/auth/role.rb +56 -0
- data/lib/decision_agent/auth/session.rb +33 -0
- data/lib/decision_agent/auth/session_manager.rb +57 -0
- data/lib/decision_agent/auth/user.rb +70 -0
- data/lib/decision_agent/context.rb +24 -4
- data/lib/decision_agent/decision.rb +10 -3
- data/lib/decision_agent/dsl/condition_evaluator.rb +378 -1
- data/lib/decision_agent/dsl/schema_validator.rb +8 -1
- data/lib/decision_agent/errors.rb +38 -0
- data/lib/decision_agent/evaluation.rb +10 -3
- data/lib/decision_agent/evaluation_validator.rb +8 -13
- data/lib/decision_agent/monitoring/dashboard_server.rb +1 -0
- data/lib/decision_agent/monitoring/metrics_collector.rb +164 -7
- data/lib/decision_agent/monitoring/storage/activerecord_adapter.rb +253 -0
- data/lib/decision_agent/monitoring/storage/base_adapter.rb +90 -0
- data/lib/decision_agent/monitoring/storage/memory_adapter.rb +222 -0
- data/lib/decision_agent/testing/batch_test_importer.rb +373 -0
- data/lib/decision_agent/testing/batch_test_runner.rb +244 -0
- data/lib/decision_agent/testing/test_coverage_analyzer.rb +191 -0
- data/lib/decision_agent/testing/test_result_comparator.rb +235 -0
- data/lib/decision_agent/testing/test_scenario.rb +42 -0
- data/lib/decision_agent/version.rb +10 -1
- data/lib/decision_agent/versioning/activerecord_adapter.rb +1 -1
- data/lib/decision_agent/versioning/file_storage_adapter.rb +96 -28
- data/lib/decision_agent/web/middleware/auth_middleware.rb +45 -0
- data/lib/decision_agent/web/middleware/permission_middleware.rb +94 -0
- data/lib/decision_agent/web/public/app.js +184 -29
- data/lib/decision_agent/web/public/batch_testing.html +640 -0
- data/lib/decision_agent/web/public/index.html +37 -9
- data/lib/decision_agent/web/public/login.html +298 -0
- data/lib/decision_agent/web/public/users.html +679 -0
- data/lib/decision_agent/web/server.rb +873 -7
- data/lib/decision_agent.rb +59 -0
- data/lib/generators/decision_agent/install/install_generator.rb +37 -0
- data/lib/generators/decision_agent/install/templates/ab_test_assignment_model.rb +45 -0
- data/lib/generators/decision_agent/install/templates/ab_test_model.rb +54 -0
- data/lib/generators/decision_agent/install/templates/ab_testing_migration.rb +43 -0
- data/lib/generators/decision_agent/install/templates/ab_testing_tasks.rake +189 -0
- data/lib/generators/decision_agent/install/templates/decision_agent_tasks.rake +114 -0
- data/lib/generators/decision_agent/install/templates/decision_log.rb +57 -0
- data/lib/generators/decision_agent/install/templates/error_metric.rb +53 -0
- data/lib/generators/decision_agent/install/templates/evaluation_metric.rb +43 -0
- data/lib/generators/decision_agent/install/templates/monitoring_migration.rb +109 -0
- data/lib/generators/decision_agent/install/templates/performance_metric.rb +76 -0
- data/lib/generators/decision_agent/install/templates/rule_version.rb +1 -1
- data/spec/ab_testing/ab_test_assignment_spec.rb +253 -0
- data/spec/ab_testing/ab_test_manager_spec.rb +612 -0
- data/spec/ab_testing/ab_test_spec.rb +270 -0
- data/spec/ab_testing/ab_testing_agent_spec.rb +481 -0
- data/spec/ab_testing/storage/adapter_spec.rb +64 -0
- data/spec/ab_testing/storage/memory_adapter_spec.rb +485 -0
- data/spec/advanced_operators_spec.rb +1003 -0
- data/spec/agent_spec.rb +40 -0
- data/spec/audit_adapters_spec.rb +18 -0
- data/spec/auth/access_audit_logger_spec.rb +394 -0
- data/spec/auth/authenticator_spec.rb +112 -0
- data/spec/auth/password_reset_spec.rb +294 -0
- data/spec/auth/permission_checker_spec.rb +207 -0
- data/spec/auth/permission_spec.rb +73 -0
- data/spec/auth/rbac_adapter_spec.rb +550 -0
- data/spec/auth/rbac_config_spec.rb +82 -0
- data/spec/auth/role_spec.rb +51 -0
- data/spec/auth/session_manager_spec.rb +172 -0
- data/spec/auth/session_spec.rb +112 -0
- data/spec/auth/user_spec.rb +130 -0
- data/spec/context_spec.rb +43 -0
- data/spec/decision_agent_spec.rb +96 -0
- data/spec/decision_spec.rb +423 -0
- data/spec/dsl/condition_evaluator_spec.rb +774 -0
- data/spec/evaluation_spec.rb +364 -0
- data/spec/evaluation_validator_spec.rb +165 -0
- data/spec/examples.txt +1542 -548
- data/spec/issue_verification_spec.rb +95 -21
- data/spec/monitoring/metrics_collector_spec.rb +221 -3
- data/spec/monitoring/monitored_agent_spec.rb +1 -1
- data/spec/monitoring/prometheus_exporter_spec.rb +1 -1
- data/spec/monitoring/storage/activerecord_adapter_spec.rb +498 -0
- data/spec/monitoring/storage/base_adapter_spec.rb +61 -0
- data/spec/monitoring/storage/memory_adapter_spec.rb +247 -0
- data/spec/performance_optimizations_spec.rb +486 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/testing/batch_test_importer_spec.rb +693 -0
- data/spec/testing/batch_test_runner_spec.rb +307 -0
- data/spec/testing/test_coverage_analyzer_spec.rb +292 -0
- data/spec/testing/test_result_comparator_spec.rb +392 -0
- data/spec/testing/test_scenario_spec.rb +113 -0
- data/spec/versioning/adapter_spec.rb +156 -0
- data/spec/versioning_spec.rb +253 -0
- data/spec/web/middleware/auth_middleware_spec.rb +133 -0
- data/spec/web/middleware/permission_middleware_spec.rb +247 -0
- data/spec/web_ui_rack_spec.rb +1705 -0
- metadata +123 -6
|
@@ -160,15 +160,43 @@
|
|
|
160
160
|
<div class="field-condition-inputs">
|
|
161
161
|
<input type="text" class="field-path input-sm" placeholder="field.path">
|
|
162
162
|
<select class="operator-select input-sm">
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
<optgroup label="Basic Comparison">
|
|
164
|
+
<option value="eq">equals (==)</option>
|
|
165
|
+
<option value="neq">not equals (!=)</option>
|
|
166
|
+
<option value="gt">greater than (>)</option>
|
|
167
|
+
<option value="gte">greater or equal (>=)</option>
|
|
168
|
+
<option value="lt">less than (<)</option>
|
|
169
|
+
<option value="lte">less or equal (<=)</option>
|
|
170
|
+
<option value="in">in array</option>
|
|
171
|
+
<option value="present">is present</option>
|
|
172
|
+
<option value="blank">is blank</option>
|
|
173
|
+
</optgroup>
|
|
174
|
+
<optgroup label="String Operators">
|
|
175
|
+
<option value="contains">contains substring</option>
|
|
176
|
+
<option value="starts_with">starts with</option>
|
|
177
|
+
<option value="ends_with">ends with</option>
|
|
178
|
+
<option value="matches">matches regex</option>
|
|
179
|
+
</optgroup>
|
|
180
|
+
<optgroup label="Numeric Operators">
|
|
181
|
+
<option value="between">between (range)</option>
|
|
182
|
+
<option value="modulo">modulo equals</option>
|
|
183
|
+
</optgroup>
|
|
184
|
+
<optgroup label="Date/Time Operators">
|
|
185
|
+
<option value="before_date">before date</option>
|
|
186
|
+
<option value="after_date">after date</option>
|
|
187
|
+
<option value="within_days">within N days</option>
|
|
188
|
+
<option value="day_of_week">day of week</option>
|
|
189
|
+
</optgroup>
|
|
190
|
+
<optgroup label="Collection Operators">
|
|
191
|
+
<option value="contains_all">contains all</option>
|
|
192
|
+
<option value="contains_any">contains any</option>
|
|
193
|
+
<option value="intersects">intersects with</option>
|
|
194
|
+
<option value="subset_of">is subset of</option>
|
|
195
|
+
</optgroup>
|
|
196
|
+
<optgroup label="Geospatial Operators">
|
|
197
|
+
<option value="within_radius">within radius</option>
|
|
198
|
+
<option value="in_polygon">in polygon</option>
|
|
199
|
+
</optgroup>
|
|
172
200
|
</select>
|
|
173
201
|
<input type="text" class="field-value input-sm" placeholder="value">
|
|
174
202
|
</div>
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Login - DecisionAgent</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css">
|
|
8
|
+
<style>
|
|
9
|
+
.login-container {
|
|
10
|
+
max-width: 400px;
|
|
11
|
+
margin: 100px auto;
|
|
12
|
+
padding: 40px;
|
|
13
|
+
background: var(--panel-bg);
|
|
14
|
+
border-radius: 10px;
|
|
15
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.login-header {
|
|
19
|
+
text-align: center;
|
|
20
|
+
margin-bottom: 30px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.login-header h1 {
|
|
24
|
+
font-size: 2rem;
|
|
25
|
+
color: var(--primary-color);
|
|
26
|
+
margin-bottom: 10px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.form-group {
|
|
30
|
+
margin-bottom: 20px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.form-group label {
|
|
34
|
+
display: block;
|
|
35
|
+
margin-bottom: 8px;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
color: var(--text-color);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.form-group input {
|
|
41
|
+
width: 100%;
|
|
42
|
+
padding: 12px;
|
|
43
|
+
border: 2px solid var(--border-color);
|
|
44
|
+
border-radius: 6px;
|
|
45
|
+
font-size: 1rem;
|
|
46
|
+
transition: border-color 0.3s;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.form-group input:focus {
|
|
50
|
+
outline: none;
|
|
51
|
+
border-color: var(--primary-color);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.btn-login {
|
|
55
|
+
width: 100%;
|
|
56
|
+
padding: 12px;
|
|
57
|
+
background: var(--primary-color);
|
|
58
|
+
color: white;
|
|
59
|
+
border: none;
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
font-size: 1rem;
|
|
62
|
+
font-weight: 600;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
transition: background 0.3s;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.btn-login:hover {
|
|
68
|
+
background: #4338ca;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.btn-login:disabled {
|
|
72
|
+
background: var(--secondary-color);
|
|
73
|
+
cursor: not-allowed;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.error-message {
|
|
77
|
+
background: #fee2e2;
|
|
78
|
+
color: var(--danger-color);
|
|
79
|
+
padding: 12px;
|
|
80
|
+
border-radius: 6px;
|
|
81
|
+
margin-bottom: 20px;
|
|
82
|
+
display: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.error-message.show {
|
|
86
|
+
display: block;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.success-message {
|
|
90
|
+
background: #d1fae5;
|
|
91
|
+
color: var(--success-color);
|
|
92
|
+
padding: 12px;
|
|
93
|
+
border-radius: 6px;
|
|
94
|
+
margin-bottom: 20px;
|
|
95
|
+
display: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.success-message.show {
|
|
99
|
+
display: block;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.links {
|
|
103
|
+
text-align: center;
|
|
104
|
+
margin-top: 20px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.links a {
|
|
108
|
+
color: var(--primary-color);
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.links a:hover {
|
|
113
|
+
text-decoration: underline;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.loading {
|
|
117
|
+
display: inline-block;
|
|
118
|
+
width: 16px;
|
|
119
|
+
height: 16px;
|
|
120
|
+
border: 2px solid #ffffff;
|
|
121
|
+
border-radius: 50%;
|
|
122
|
+
border-top-color: transparent;
|
|
123
|
+
animation: spin 0.8s linear infinite;
|
|
124
|
+
margin-right: 8px;
|
|
125
|
+
vertical-align: middle;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes spin {
|
|
129
|
+
to { transform: rotate(360deg); }
|
|
130
|
+
}
|
|
131
|
+
</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<div class="login-container">
|
|
135
|
+
<div class="login-header">
|
|
136
|
+
<h1>🔐 Login</h1>
|
|
137
|
+
<p>Sign in to DecisionAgent</p>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div id="errorMessage" class="error-message"></div>
|
|
141
|
+
<div id="successMessage" class="success-message"></div>
|
|
142
|
+
|
|
143
|
+
<form id="loginForm">
|
|
144
|
+
<div class="form-group">
|
|
145
|
+
<label for="email">Email</label>
|
|
146
|
+
<input type="email" id="email" name="email" required autocomplete="email" placeholder="user@example.com">
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="form-group">
|
|
150
|
+
<label for="password">Password</label>
|
|
151
|
+
<input type="password" id="password" name="password" required autocomplete="current-password" placeholder="Enter your password">
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<button type="submit" class="btn-login" id="loginBtn">
|
|
155
|
+
Sign In
|
|
156
|
+
</button>
|
|
157
|
+
</form>
|
|
158
|
+
|
|
159
|
+
<div class="links">
|
|
160
|
+
<a href="/">← Back to Home</a>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<script>
|
|
165
|
+
// Helper function to get the base path for API calls
|
|
166
|
+
function getBasePath() {
|
|
167
|
+
const baseTag = document.querySelector('base');
|
|
168
|
+
if (baseTag && baseTag.href) {
|
|
169
|
+
try {
|
|
170
|
+
const baseUrl = new URL(baseTag.href, window.location.href);
|
|
171
|
+
let path = baseUrl.pathname;
|
|
172
|
+
if (path && !path.endsWith('/')) {
|
|
173
|
+
path += '/';
|
|
174
|
+
}
|
|
175
|
+
return path;
|
|
176
|
+
} catch (e) {
|
|
177
|
+
if (baseTag.href.startsWith('/')) {
|
|
178
|
+
const match = baseTag.href.match(/^(https?:\/\/[^\/]+)?(\/.*?)\/?$/);
|
|
179
|
+
if (match && match[2]) {
|
|
180
|
+
return match[2].endsWith('/') ? match[2] : match[2] + '/';
|
|
181
|
+
}
|
|
182
|
+
} else if (baseTag.href.startsWith('./')) {
|
|
183
|
+
const pathname = window.location.pathname;
|
|
184
|
+
return pathname.substring(0, pathname.lastIndexOf('/') + 1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const pathname = window.location.pathname;
|
|
189
|
+
if (pathname.includes('/decision_agent')) {
|
|
190
|
+
const match = pathname.match(/^(\/.*?\/decision_agent)\/?/);
|
|
191
|
+
if (match) {
|
|
192
|
+
return match[1].endsWith('/') ? match[1] : match[1] + '/';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const dirPath = pathname.substring(0, pathname.lastIndexOf('/') + 1);
|
|
196
|
+
return dirPath || '/';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const basePath = getBasePath();
|
|
200
|
+
const loginForm = document.getElementById('loginForm');
|
|
201
|
+
const loginBtn = document.getElementById('loginBtn');
|
|
202
|
+
const errorMessage = document.getElementById('errorMessage');
|
|
203
|
+
const successMessage = document.getElementById('successMessage');
|
|
204
|
+
|
|
205
|
+
// Check if user is already logged in
|
|
206
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
207
|
+
const token = localStorage.getItem('auth_token');
|
|
208
|
+
if (token) {
|
|
209
|
+
checkAuth(token);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
async function checkAuth(token) {
|
|
214
|
+
try {
|
|
215
|
+
const response = await fetch(`${basePath}api/auth/me`, {
|
|
216
|
+
headers: {
|
|
217
|
+
'Authorization': `Bearer ${token}`
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
if (response.ok) {
|
|
222
|
+
// Already logged in, redirect to home
|
|
223
|
+
window.location.href = '/';
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// Not authenticated, clear token
|
|
227
|
+
localStorage.removeItem('auth_token');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
loginForm.addEventListener('submit', async (e) => {
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
|
|
234
|
+
const email = document.getElementById('email').value;
|
|
235
|
+
const password = document.getElementById('password').value;
|
|
236
|
+
|
|
237
|
+
// Clear previous messages
|
|
238
|
+
hideMessages();
|
|
239
|
+
|
|
240
|
+
// Disable button and show loading
|
|
241
|
+
loginBtn.disabled = true;
|
|
242
|
+
loginBtn.innerHTML = '<span class="loading"></span>Signing in...';
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const response = await fetch(`${basePath}api/auth/login`, {
|
|
246
|
+
method: 'POST',
|
|
247
|
+
headers: {
|
|
248
|
+
'Content-Type': 'application/json'
|
|
249
|
+
},
|
|
250
|
+
body: JSON.stringify({ email, password })
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const data = await response.json();
|
|
254
|
+
|
|
255
|
+
if (response.ok) {
|
|
256
|
+
// Store token
|
|
257
|
+
localStorage.setItem('auth_token', data.token);
|
|
258
|
+
localStorage.setItem('user', JSON.stringify(data.user));
|
|
259
|
+
|
|
260
|
+
// Show success message
|
|
261
|
+
showSuccess('Login successful! Redirecting...');
|
|
262
|
+
|
|
263
|
+
// Redirect after short delay
|
|
264
|
+
setTimeout(() => {
|
|
265
|
+
window.location.href = '/';
|
|
266
|
+
}, 1000);
|
|
267
|
+
} else {
|
|
268
|
+
showError(data.error || 'Login failed. Please check your credentials.');
|
|
269
|
+
loginBtn.disabled = false;
|
|
270
|
+
loginBtn.innerHTML = 'Sign In';
|
|
271
|
+
}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
showError('Network error. Please try again.');
|
|
274
|
+
loginBtn.disabled = false;
|
|
275
|
+
loginBtn.innerHTML = 'Sign In';
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
function showError(message) {
|
|
280
|
+
errorMessage.textContent = message;
|
|
281
|
+
errorMessage.classList.add('show');
|
|
282
|
+
successMessage.classList.remove('show');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function showSuccess(message) {
|
|
286
|
+
successMessage.textContent = message;
|
|
287
|
+
successMessage.classList.add('show');
|
|
288
|
+
errorMessage.classList.remove('show');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function hideMessages() {
|
|
292
|
+
errorMessage.classList.remove('show');
|
|
293
|
+
successMessage.classList.remove('show');
|
|
294
|
+
}
|
|
295
|
+
</script>
|
|
296
|
+
</body>
|
|
297
|
+
</html>
|
|
298
|
+
|