orfeas_lyra 0.6.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 +7 -0
- data/CHANGELOG.md +222 -0
- data/LICENSE +21 -0
- data/README.md +1165 -0
- data/Rakefile +728 -0
- data/app/controllers/lyra/application_controller.rb +23 -0
- data/app/controllers/lyra/dashboard_controller.rb +624 -0
- data/app/controllers/lyra/flow_controller.rb +224 -0
- data/app/controllers/lyra/privacy_controller.rb +182 -0
- data/app/views/lyra/dashboard/audit_trail.html.erb +324 -0
- data/app/views/lyra/dashboard/discrepancies.html.erb +125 -0
- data/app/views/lyra/dashboard/event_graph_view.html.erb +525 -0
- data/app/views/lyra/dashboard/heatmap_view.html.erb +155 -0
- data/app/views/lyra/dashboard/index.html.erb +119 -0
- data/app/views/lyra/dashboard/model_overview.html.erb +115 -0
- data/app/views/lyra/dashboard/projections.html.erb +302 -0
- data/app/views/lyra/dashboard/schema.html.erb +283 -0
- data/app/views/lyra/dashboard/schema_history.html.erb +78 -0
- data/app/views/lyra/dashboard/schema_version.html.erb +340 -0
- data/app/views/lyra/dashboard/verification.html.erb +370 -0
- data/app/views/lyra/flow/crud_mapping.html.erb +125 -0
- data/app/views/lyra/flow/timeline.html.erb +260 -0
- data/app/views/lyra/privacy/pii_detection.html.erb +148 -0
- data/app/views/lyra/privacy/policy.html.erb +188 -0
- data/app/workflows/es_async_mode_workflow.rb +80 -0
- data/app/workflows/es_sync_mode_workflow.rb +64 -0
- data/app/workflows/hijack_mode_workflow.rb +54 -0
- data/app/workflows/lifecycle_workflow.rb +43 -0
- data/app/workflows/monitor_mode_workflow.rb +39 -0
- data/config/privacy_policies.rb +273 -0
- data/config/routes.rb +48 -0
- data/lib/lyra/aggregate.rb +131 -0
- data/lib/lyra/associations/event_aware.rb +225 -0
- data/lib/lyra/command.rb +81 -0
- data/lib/lyra/command_handler.rb +155 -0
- data/lib/lyra/configuration.rb +124 -0
- data/lib/lyra/consistency/read_your_writes.rb +91 -0
- data/lib/lyra/correlation.rb +144 -0
- data/lib/lyra/dual_view.rb +231 -0
- data/lib/lyra/engine.rb +67 -0
- data/lib/lyra/event.rb +71 -0
- data/lib/lyra/event_analyzer.rb +135 -0
- data/lib/lyra/event_flow.rb +449 -0
- data/lib/lyra/event_mapper.rb +106 -0
- data/lib/lyra/event_store_adapter.rb +72 -0
- data/lib/lyra/id_generator.rb +137 -0
- data/lib/lyra/interceptors/association_interceptor.rb +169 -0
- data/lib/lyra/interceptors/crud_interceptor.rb +543 -0
- data/lib/lyra/privacy/gdpr_compliance.rb +161 -0
- data/lib/lyra/privacy/pii_detector.rb +85 -0
- data/lib/lyra/privacy/pii_masker.rb +66 -0
- data/lib/lyra/privacy/policy_integration.rb +253 -0
- data/lib/lyra/projection.rb +94 -0
- data/lib/lyra/projections/async_projection_job.rb +63 -0
- data/lib/lyra/projections/cached_projection.rb +322 -0
- data/lib/lyra/projections/cached_relation.rb +757 -0
- data/lib/lyra/projections/event_store_reader.rb +127 -0
- data/lib/lyra/projections/model_projection.rb +143 -0
- data/lib/lyra/schema/diff.rb +331 -0
- data/lib/lyra/schema/event_class_registrar.rb +63 -0
- data/lib/lyra/schema/generator.rb +190 -0
- data/lib/lyra/schema/reporter.rb +188 -0
- data/lib/lyra/schema/store.rb +156 -0
- data/lib/lyra/schema/validator.rb +100 -0
- data/lib/lyra/strict_data_access.rb +363 -0
- data/lib/lyra/verification/crud_lifecycle_workflow.rb +456 -0
- data/lib/lyra/verification/workflow_generator.rb +540 -0
- data/lib/lyra/version.rb +3 -0
- data/lib/lyra/visualization/activity_heatmap.rb +215 -0
- data/lib/lyra/visualization/event_graph.rb +310 -0
- data/lib/lyra/visualization/timeline.rb +398 -0
- data/lib/lyra.rb +150 -0
- data/lib/tasks/dist.rake +391 -0
- data/lib/tasks/gems.rake +185 -0
- data/lib/tasks/lyra_schema.rake +231 -0
- data/lib/tasks/lyra_workflows.rake +452 -0
- data/lib/tasks/public_release.rake +351 -0
- data/lib/tasks/stats.rake +175 -0
- data/lib/tasks/testbed.rake +479 -0
- data/lib/tasks/version.rake +159 -0
- metadata +221 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.lyra-schema-version { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
3
|
+
.lyra-schema-version h1 { color: #1a1a2e; border-bottom: 2px solid #4a4e69; padding-bottom: 10px; display: flex; justify-content: space-between; align-items: center; }
|
|
4
|
+
.lyra-schema-version h2 { color: #4a4e69; margin-top: 30px; }
|
|
5
|
+
.lyra-schema-version h3 { color: #495057; margin-top: 20px; font-size: 16px; }
|
|
6
|
+
.lyra-schema-version .back-link { display: inline-block; margin-bottom: 20px; color: #4a4e69; text-decoration: none; }
|
|
7
|
+
.lyra-schema-version .back-link:hover { text-decoration: underline; }
|
|
8
|
+
.lyra-schema-version .section-desc { color: #6c757d; font-size: 13px; margin-bottom: 15px; }
|
|
9
|
+
.lyra-schema-version .nav-buttons { display: flex; gap: 10px; }
|
|
10
|
+
.lyra-schema-version .nav-btn { padding: 6px 12px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; color: #495057; text-decoration: none; font-size: 13px; }
|
|
11
|
+
.lyra-schema-version .nav-btn:hover { background: #e9ecef; }
|
|
12
|
+
.lyra-schema-version .nav-btn.disabled { opacity: 0.5; pointer-events: none; }
|
|
13
|
+
.lyra-schema-version .config-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; margin-bottom: 20px; overflow: hidden; }
|
|
14
|
+
.lyra-schema-version .config-header { background: #f8f9fa; padding: 15px 20px; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; }
|
|
15
|
+
.lyra-schema-version .config-header h4 { margin: 0; font-size: 16px; color: #1a1a2e; }
|
|
16
|
+
.lyra-schema-version .config-body { padding: 20px; }
|
|
17
|
+
.lyra-schema-version .config-table { width: 100%; border-collapse: collapse; }
|
|
18
|
+
.lyra-schema-version .config-table th { text-align: left; padding: 10px 15px; background: #f8f9fa; border-bottom: 1px solid #dee2e6; font-weight: 600; color: #495057; width: 180px; }
|
|
19
|
+
.lyra-schema-version .config-table td { padding: 10px 15px; border-bottom: 1px solid #eee; }
|
|
20
|
+
.lyra-schema-version .config-table tr:last-child td { border-bottom: none; }
|
|
21
|
+
.lyra-schema-version .badge { display: inline-block; padding: 4px 12px; border-radius: 4px; font-size: 12px; font-weight: 600; }
|
|
22
|
+
.lyra-schema-version .badge-success { background: #d4edda; color: #155724; }
|
|
23
|
+
.lyra-schema-version .badge-warning { background: #fff3cd; color: #856404; }
|
|
24
|
+
.lyra-schema-version .badge-info { background: #cce5ff; color: #004085; }
|
|
25
|
+
.lyra-schema-version .badge-secondary { background: #e2e3e5; color: #383d41; }
|
|
26
|
+
.lyra-schema-version .badge-danger { background: #f8d7da; color: #721c24; }
|
|
27
|
+
.lyra-schema-version .summary-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; margin: 20px 0; }
|
|
28
|
+
.lyra-schema-version .summary-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px; text-align: center; }
|
|
29
|
+
.lyra-schema-version .summary-card .value { font-size: 28px; font-weight: 700; color: #1a1a2e; }
|
|
30
|
+
.lyra-schema-version .summary-card .label { color: #6c757d; font-size: 12px; margin-top: 5px; }
|
|
31
|
+
.lyra-schema-version .model-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; margin-bottom: 20px; overflow: hidden; }
|
|
32
|
+
.lyra-schema-version .model-header { background: #f8f9fa; padding: 15px 20px; border-bottom: 1px solid #dee2e6; }
|
|
33
|
+
.lyra-schema-version .model-header h4 { margin: 0 0 5px 0; font-size: 18px; color: #1a1a2e; }
|
|
34
|
+
.lyra-schema-version .model-header .model-meta { font-size: 12px; color: #6c757d; }
|
|
35
|
+
.lyra-schema-version .model-body { padding: 20px; }
|
|
36
|
+
.lyra-schema-version .column-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
37
|
+
.lyra-schema-version .column-table th { text-align: left; padding: 8px 12px; background: #f8f9fa; border-bottom: 1px solid #dee2e6; font-weight: 600; color: #495057; }
|
|
38
|
+
.lyra-schema-version .column-table td { padding: 8px 12px; border-bottom: 1px solid #eee; }
|
|
39
|
+
.lyra-schema-version .column-table tr:last-child td { border-bottom: none; }
|
|
40
|
+
.lyra-schema-version .column-table code { background: #f8f9fa; padding: 2px 6px; border-radius: 3px; font-size: 12px; }
|
|
41
|
+
.lyra-schema-version .pii-badge { background: #f8d7da; color: #721c24; font-size: 10px; padding: 2px 6px; border-radius: 3px; }
|
|
42
|
+
.lyra-schema-version .pk-badge { background: #cce5ff; color: #004085; font-size: 10px; padding: 2px 6px; border-radius: 3px; }
|
|
43
|
+
.lyra-schema-version .events-list { margin: 15px 0 0 0; padding: 0; list-style: none; }
|
|
44
|
+
.lyra-schema-version .events-list li { padding: 10px 15px; background: #f8f9fa; border-radius: 6px; margin-bottom: 8px; }
|
|
45
|
+
.lyra-schema-version .events-list .event-name { font-weight: 600; color: #1a1a2e; font-family: monospace; }
|
|
46
|
+
.lyra-schema-version .events-list .event-operation { font-size: 12px; color: #6c757d; margin-left: 10px; }
|
|
47
|
+
.lyra-schema-version .events-list .event-fields { font-size: 11px; color: #6c757d; margin-top: 5px; }
|
|
48
|
+
.lyra-schema-version .change-list { margin: 15px 0; padding: 0; list-style: none; }
|
|
49
|
+
.lyra-schema-version .change-item { padding: 12px 15px; border-left: 4px solid #ffc107; background: #fffbea; margin-bottom: 10px; border-radius: 0 4px 4px 0; }
|
|
50
|
+
.lyra-schema-version .change-item.added { border-left-color: #28a745; background: #f0fff4; }
|
|
51
|
+
.lyra-schema-version .change-item.removed { border-left-color: #dc3545; background: #fff5f5; }
|
|
52
|
+
.lyra-schema-version .change-item.modified { border-left-color: #007bff; background: #f0f7ff; }
|
|
53
|
+
.lyra-schema-version .change-type { font-weight: 600; font-size: 12px; text-transform: uppercase; margin-bottom: 4px; }
|
|
54
|
+
.lyra-schema-version .change-detail { font-size: 13px; color: #495057; }
|
|
55
|
+
.lyra-schema-version .no-changes { color: #28a745; font-style: italic; padding: 20px; text-align: center; background: #f0fff4; border-radius: 6px; }
|
|
56
|
+
.lyra-schema-version code { background: #f8f9fa; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
|
57
|
+
.lyra-schema-version pre.json-block code { background: transparent; color: #f8f9fa; padding: 0; }
|
|
58
|
+
.lyra-schema-version .subsection { margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; }
|
|
59
|
+
.lyra-schema-version .subsection-title { font-size: 14px; font-weight: 600; color: #495057; margin-bottom: 10px; }
|
|
60
|
+
</style>
|
|
61
|
+
|
|
62
|
+
<div class="lyra-schema-version">
|
|
63
|
+
<%= link_to "← Back to Schema Registry".html_safe, schema_path, class: "back-link" %>
|
|
64
|
+
|
|
65
|
+
<h1>
|
|
66
|
+
<span>Schema Version <%= @version %></span>
|
|
67
|
+
<span class="nav-buttons">
|
|
68
|
+
<% if @prev_version %>
|
|
69
|
+
<%= link_to "← v#{@prev_version}".html_safe, schema_version_path(version: @prev_version), class: "nav-btn" %>
|
|
70
|
+
<% else %>
|
|
71
|
+
<span class="nav-btn disabled">← Previous</span>
|
|
72
|
+
<% end %>
|
|
73
|
+
<% if @next_version %>
|
|
74
|
+
<%= link_to "v#{@next_version} →".html_safe, schema_version_path(version: @next_version), class: "nav-btn" %>
|
|
75
|
+
<% else %>
|
|
76
|
+
<span class="nav-btn disabled">Next →</span>
|
|
77
|
+
<% end %>
|
|
78
|
+
</span>
|
|
79
|
+
</h1>
|
|
80
|
+
|
|
81
|
+
<p class="section-desc">
|
|
82
|
+
Complete schema snapshot including all model definitions, column types, event mappings, and PII classifications.
|
|
83
|
+
</p>
|
|
84
|
+
|
|
85
|
+
<!-- Metadata Section -->
|
|
86
|
+
<div class="config-card">
|
|
87
|
+
<div class="config-header">
|
|
88
|
+
<h4>Version Metadata</h4>
|
|
89
|
+
<span class="badge badge-info">v<%= @version %></span>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="config-body">
|
|
92
|
+
<table class="config-table">
|
|
93
|
+
<tr>
|
|
94
|
+
<th>Version</th>
|
|
95
|
+
<td><strong><%= @version %></strong></td>
|
|
96
|
+
</tr>
|
|
97
|
+
<tr>
|
|
98
|
+
<th>Fingerprint</th>
|
|
99
|
+
<td><code style="font-size: 11px;"><%= @fingerprint %></code></td>
|
|
100
|
+
</tr>
|
|
101
|
+
<tr>
|
|
102
|
+
<th>Created At</th>
|
|
103
|
+
<td><%= @created_at.is_a?(String) ? @created_at : @created_at&.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
|
|
104
|
+
</tr>
|
|
105
|
+
<tr>
|
|
106
|
+
<th>Lyra Version</th>
|
|
107
|
+
<td><%= @lyra_version %></td>
|
|
108
|
+
</tr>
|
|
109
|
+
<tr>
|
|
110
|
+
<th>Rails Version</th>
|
|
111
|
+
<td><%= @rails_version || "unknown" %></td>
|
|
112
|
+
</tr>
|
|
113
|
+
</table>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<!-- Summary Statistics -->
|
|
118
|
+
<h2 style="margin-top: 0;">Summary</h2>
|
|
119
|
+
<div class="summary-cards">
|
|
120
|
+
<div class="summary-card">
|
|
121
|
+
<div class="value"><%= @summary[:models_count] || @models.size %></div>
|
|
122
|
+
<div class="label">Models</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="summary-card">
|
|
125
|
+
<div class="value"><%= @summary[:total_columns] || 0 %></div>
|
|
126
|
+
<div class="label">Total Columns</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="summary-card">
|
|
129
|
+
<div class="value" style="color: #dc3545;"><%= @summary[:pii_fields_count] || 0 %></div>
|
|
130
|
+
<div class="label">PII Fields</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="summary-card">
|
|
133
|
+
<div class="value" style="color: #007bff;"><%= @summary[:events_count] || 0 %></div>
|
|
134
|
+
<div class="label">Event Types</div>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Configuration -->
|
|
139
|
+
<h2>Configuration</h2>
|
|
140
|
+
<p class="section-desc">Lyra configuration settings captured in this schema version.</p>
|
|
141
|
+
|
|
142
|
+
<div class="config-card">
|
|
143
|
+
<div class="config-body">
|
|
144
|
+
<table class="config-table">
|
|
145
|
+
<tr>
|
|
146
|
+
<th>Operating Mode</th>
|
|
147
|
+
<td>
|
|
148
|
+
<span class="badge badge-<%= @configuration[:mode] == 'event_sourcing' ? 'info' : (@configuration[:mode] == 'hijack' ? 'warning' : 'success') %>">
|
|
149
|
+
<%= @configuration[:mode]&.to_s&.titleize || "Not set" %>
|
|
150
|
+
</span>
|
|
151
|
+
</td>
|
|
152
|
+
</tr>
|
|
153
|
+
<tr>
|
|
154
|
+
<th>Projection Mode</th>
|
|
155
|
+
<td><%= @configuration[:projection_mode]&.to_s&.titleize || "Not set" %></td>
|
|
156
|
+
</tr>
|
|
157
|
+
<tr>
|
|
158
|
+
<th>Strict Schema</th>
|
|
159
|
+
<td><%= @configuration[:strict_schema] ? "Enabled" : "Disabled" %></td>
|
|
160
|
+
</tr>
|
|
161
|
+
<tr>
|
|
162
|
+
<th>Retention Policy</th>
|
|
163
|
+
<td><%= @configuration[:retention_policy] || "Not configured" %></td>
|
|
164
|
+
</tr>
|
|
165
|
+
</table>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<% if @diff_from_previous.present? && @diff_from_previous.any? %>
|
|
170
|
+
<h2>Changes from v<%= @prev_version %></h2>
|
|
171
|
+
<p class="section-desc">
|
|
172
|
+
Differences between this version and the previous version (v<%= @prev_version %>).
|
|
173
|
+
</p>
|
|
174
|
+
|
|
175
|
+
<ul class="change-list">
|
|
176
|
+
<% @diff_from_previous.each do |change| %>
|
|
177
|
+
<li class="change-item <%= change[:type] || change['type'] %>">
|
|
178
|
+
<div class="change-type"><%= change[:type] || change['type'] %></div>
|
|
179
|
+
<div class="change-detail">
|
|
180
|
+
<% if change[:model] || change['model'] %>
|
|
181
|
+
<strong>Model:</strong> <%= change[:model] || change['model'] %><br>
|
|
182
|
+
<% end %>
|
|
183
|
+
<% if change[:field] || change['field'] %>
|
|
184
|
+
<strong>Field:</strong> <%= change[:field] || change['field'] %><br>
|
|
185
|
+
<% end %>
|
|
186
|
+
<% if change[:description] || change['description'] %>
|
|
187
|
+
<%= change[:description] || change['description'] %>
|
|
188
|
+
<% end %>
|
|
189
|
+
<% if change[:from] || change['from'] || change[:to] || change['to'] %>
|
|
190
|
+
<strong>From:</strong> <%= change[:from] || change['from'] || "(none)" %> →
|
|
191
|
+
<strong>To:</strong> <%= change[:to] || change['to'] || "(none)" %>
|
|
192
|
+
<% end %>
|
|
193
|
+
</div>
|
|
194
|
+
</li>
|
|
195
|
+
<% end %>
|
|
196
|
+
</ul>
|
|
197
|
+
<% elsif @prev_version %>
|
|
198
|
+
<h2>Changes from v<%= @prev_version %></h2>
|
|
199
|
+
<p class="no-changes">No structural changes from the previous version.</p>
|
|
200
|
+
<% end %>
|
|
201
|
+
|
|
202
|
+
<!-- Models -->
|
|
203
|
+
<h2>Model Definitions</h2>
|
|
204
|
+
<p class="section-desc">
|
|
205
|
+
Complete schema for each monitored model including columns, types, constraints, and event mappings.
|
|
206
|
+
</p>
|
|
207
|
+
|
|
208
|
+
<% @models.each do |model_name, model_data| %>
|
|
209
|
+
<div class="model-card">
|
|
210
|
+
<div class="model-header">
|
|
211
|
+
<h4><%= model_name %></h4>
|
|
212
|
+
<div class="model-meta">
|
|
213
|
+
Table: <code><%= model_data[:table_name] || model_data["table_name"] || "unknown" %></code>
|
|
214
|
+
|
|
|
215
|
+
Event Prefix: <code><%= model_data[:event_prefix] || model_data["event_prefix"] || model_name %></code>
|
|
216
|
+
<% if model_data[:aggregate_class] || model_data["aggregate_class"] %>
|
|
217
|
+
|
|
|
218
|
+
Aggregate: <code><%= model_data[:aggregate_class] || model_data["aggregate_class"] %></code>
|
|
219
|
+
<% end %>
|
|
220
|
+
<% if model_data[:privacy_policy] || model_data["privacy_policy"] %>
|
|
221
|
+
|
|
|
222
|
+
Privacy: <code><%= model_data[:privacy_policy] || model_data["privacy_policy"] %></code>
|
|
223
|
+
<% end %>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="model-body">
|
|
227
|
+
<!-- Columns -->
|
|
228
|
+
<div class="subsection-title">Columns</div>
|
|
229
|
+
<% columns = model_data[:columns] || model_data["columns"] || {} %>
|
|
230
|
+
<% if columns.any? %>
|
|
231
|
+
<table class="column-table">
|
|
232
|
+
<thead>
|
|
233
|
+
<tr>
|
|
234
|
+
<th style="width: 200px;">Column</th>
|
|
235
|
+
<th style="width: 100px;">Type</th>
|
|
236
|
+
<th style="width: 80px;">Nullable</th>
|
|
237
|
+
<th style="width: 80px;">Limit</th>
|
|
238
|
+
<th>Default</th>
|
|
239
|
+
<th style="width: 80px;">Flags</th>
|
|
240
|
+
</tr>
|
|
241
|
+
</thead>
|
|
242
|
+
<tbody>
|
|
243
|
+
<% columns.each do |col_name, col_info| %>
|
|
244
|
+
<tr>
|
|
245
|
+
<td><code><%= col_name %></code></td>
|
|
246
|
+
<td><%= col_info[:type] || col_info["type"] || "unknown" %></td>
|
|
247
|
+
<td><%= (col_info[:nullable] || col_info["nullable"]) ? "Yes" : "No" %></td>
|
|
248
|
+
<td><%= col_info[:limit] || col_info["limit"] || "-" %></td>
|
|
249
|
+
<td><code><%= col_info[:default] || col_info["default"] || "NULL" %></code></td>
|
|
250
|
+
<td>
|
|
251
|
+
<% if col_info[:primary_key] || col_info["primary_key"] %>
|
|
252
|
+
<span class="pk-badge">PK</span>
|
|
253
|
+
<% end %>
|
|
254
|
+
<% if col_info[:pii] || col_info["pii"] %>
|
|
255
|
+
<span class="pii-badge"><%= col_info[:pii_type] || col_info["pii_type"] || "PII" %></span>
|
|
256
|
+
<% end %>
|
|
257
|
+
</td>
|
|
258
|
+
</tr>
|
|
259
|
+
<% end %>
|
|
260
|
+
</tbody>
|
|
261
|
+
</table>
|
|
262
|
+
<% else %>
|
|
263
|
+
<p style="color: #6c757d; font-style: italic;">No columns captured</p>
|
|
264
|
+
<% end %>
|
|
265
|
+
|
|
266
|
+
<!-- Events -->
|
|
267
|
+
<div class="subsection">
|
|
268
|
+
<div class="subsection-title">Events</div>
|
|
269
|
+
<% events = model_data[:events] || model_data["events"] || {} %>
|
|
270
|
+
<% if events.any? %>
|
|
271
|
+
<ul class="events-list">
|
|
272
|
+
<% events.each do |event_name, event_info| %>
|
|
273
|
+
<li>
|
|
274
|
+
<span class="event-name"><%= event_name %></span>
|
|
275
|
+
<span class="event-operation">(<%= event_info[:operation] || event_info["operation"] %>)</span>
|
|
276
|
+
<div class="event-fields">
|
|
277
|
+
<strong>Data:</strong> <%= (event_info[:data_fields] || event_info["data_fields"] || []).join(", ") %>
|
|
278
|
+
<% pii = event_info[:pii_fields] || event_info["pii_fields"] || [] %>
|
|
279
|
+
<% if pii.any? %>
|
|
280
|
+
<br><strong style="color: #dc3545;">PII Fields:</strong> <%= pii.join(", ") %>
|
|
281
|
+
<% end %>
|
|
282
|
+
</div>
|
|
283
|
+
</li>
|
|
284
|
+
<% end %>
|
|
285
|
+
</ul>
|
|
286
|
+
<% else %>
|
|
287
|
+
<p style="color: #6c757d; font-style: italic;">No events defined</p>
|
|
288
|
+
<% end %>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<!-- Custom Events -->
|
|
292
|
+
<% custom_events = model_data[:custom_events] || model_data["custom_events"] || {} %>
|
|
293
|
+
<% if custom_events.any? %>
|
|
294
|
+
<div class="subsection">
|
|
295
|
+
<div class="subsection-title">Custom Event Mappings</div>
|
|
296
|
+
<table class="column-table">
|
|
297
|
+
<thead>
|
|
298
|
+
<tr>
|
|
299
|
+
<th>Method</th>
|
|
300
|
+
<th>Event Name</th>
|
|
301
|
+
</tr>
|
|
302
|
+
</thead>
|
|
303
|
+
<tbody>
|
|
304
|
+
<% custom_events.each do |method, event_name| %>
|
|
305
|
+
<tr>
|
|
306
|
+
<td><code><%= method %></code></td>
|
|
307
|
+
<td><code><%= event_name %></code></td>
|
|
308
|
+
</tr>
|
|
309
|
+
<% end %>
|
|
310
|
+
</tbody>
|
|
311
|
+
</table>
|
|
312
|
+
</div>
|
|
313
|
+
<% end %>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
<% end %>
|
|
317
|
+
|
|
318
|
+
<% if @models.empty? %>
|
|
319
|
+
<div class="config-card">
|
|
320
|
+
<div class="config-body" style="text-align: center; padding: 40px; color: #6c757d;">
|
|
321
|
+
<p>No models were captured in this schema version.</p>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
<% end %>
|
|
325
|
+
|
|
326
|
+
<!-- Raw JSON -->
|
|
327
|
+
<h2>Raw Schema (JSON)</h2>
|
|
328
|
+
<p class="section-desc">
|
|
329
|
+
The complete schema data in JSON format. Click to expand.
|
|
330
|
+
</p>
|
|
331
|
+
|
|
332
|
+
<details class="config-card">
|
|
333
|
+
<summary style="padding: 15px 20px; cursor: pointer; background: #f8f9fa; border-bottom: 1px solid #dee2e6;">
|
|
334
|
+
<strong>View Raw JSON</strong>
|
|
335
|
+
</summary>
|
|
336
|
+
<div class="config-body">
|
|
337
|
+
<pre class="json-block" style="background: #1a1a2e; color: #f8f9fa; padding: 20px; border-radius: 6px; overflow-x: auto; font-size: 12px;"><code><%= JSON.pretty_generate(@schema) %></code></pre>
|
|
338
|
+
</div>
|
|
339
|
+
</details>
|
|
340
|
+
</div>
|