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,155 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.lyra-visualization { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1400px; margin: 0 auto; padding: 20px; }
|
|
3
|
+
.lyra-visualization h1 { color: #1a1a2e; border-bottom: 2px solid #4a4e69; padding-bottom: 10px; }
|
|
4
|
+
.lyra-visualization h2 { color: #4a4e69; margin-top: 30px; }
|
|
5
|
+
.lyra-visualization .breadcrumb { margin-bottom: 20px; font-size: 14px; }
|
|
6
|
+
.lyra-visualization .breadcrumb a { color: #4a4e69; text-decoration: none; }
|
|
7
|
+
.lyra-visualization .breadcrumb a:hover { text-decoration: underline; }
|
|
8
|
+
.lyra-visualization .controls { background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px; display: flex; gap: 15px; align-items: center; }
|
|
9
|
+
.lyra-visualization .controls label { font-weight: 500; color: #495057; }
|
|
10
|
+
.lyra-visualization .controls select { padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; }
|
|
11
|
+
.lyra-visualization .controls button { padding: 8px 16px; background: #4a4e69; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
|
|
12
|
+
.lyra-visualization .controls button:hover { background: #22223b; }
|
|
13
|
+
.lyra-visualization .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; margin-bottom: 30px; }
|
|
14
|
+
.lyra-visualization .stat-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; }
|
|
15
|
+
.lyra-visualization .stat-card .value { font-size: 32px; font-weight: 700; color: #4a4e69; }
|
|
16
|
+
.lyra-visualization .stat-card .label { font-size: 14px; color: #6c757d; margin-top: 5px; }
|
|
17
|
+
.lyra-visualization .heatmap-grid { display: grid; grid-template-columns: 80px repeat(24, 1fr); gap: 2px; margin: 20px 0; }
|
|
18
|
+
.lyra-visualization .heatmap-cell { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; font-size: 11px; border-radius: 3px; }
|
|
19
|
+
.lyra-visualization .heatmap-header { background: #f8f9fa; font-weight: 600; font-size: 10px; color: #495057; }
|
|
20
|
+
.lyra-visualization .heatmap-row-label { background: #f8f9fa; font-weight: 500; font-size: 12px; color: #495057; display: flex; align-items: center; justify-content: flex-end; padding-right: 8px; }
|
|
21
|
+
.lyra-visualization .breakdown-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
|
|
22
|
+
.lyra-visualization .breakdown-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; }
|
|
23
|
+
.lyra-visualization .breakdown-card h3 { margin: 0 0 15px 0; color: #1a1a2e; font-size: 16px; }
|
|
24
|
+
.lyra-visualization .bar-chart { display: flex; flex-direction: column; gap: 8px; }
|
|
25
|
+
.lyra-visualization .bar-row { display: flex; align-items: center; gap: 10px; }
|
|
26
|
+
.lyra-visualization .bar-label { width: 60px; font-size: 13px; color: #495057; }
|
|
27
|
+
.lyra-visualization .bar-container { flex: 1; height: 24px; background: #e9ecef; border-radius: 4px; overflow: hidden; }
|
|
28
|
+
.lyra-visualization .bar-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); border-radius: 4px; transition: width 0.3s; }
|
|
29
|
+
.lyra-visualization .bar-value { width: 50px; text-align: right; font-size: 13px; color: #495057; font-weight: 500; }
|
|
30
|
+
.lyra-visualization .op-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }
|
|
31
|
+
.lyra-visualization .op-card { background: #f8f9fa; padding: 15px; border-radius: 6px; text-align: center; }
|
|
32
|
+
.lyra-visualization .op-card .op-name { font-weight: 600; text-transform: uppercase; margin-bottom: 5px; }
|
|
33
|
+
.lyra-visualization .op-card .op-count { font-size: 24px; font-weight: 700; color: #4a4e69; }
|
|
34
|
+
.lyra-visualization .op-created { color: #28a745; }
|
|
35
|
+
.lyra-visualization .op-updated { color: #ffc107; }
|
|
36
|
+
.lyra-visualization .op-destroyed { color: #dc3545; }
|
|
37
|
+
</style>
|
|
38
|
+
|
|
39
|
+
<div class="lyra-visualization">
|
|
40
|
+
<div class="breadcrumb">
|
|
41
|
+
<%= link_to "Dashboard", lyra.dashboard_path %> › Activity Heatmap
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<h1>Activity Heatmap</h1>
|
|
45
|
+
<p style="color: #6c757d;">
|
|
46
|
+
Visualize event activity patterns across time. Identify peak hours and days for database operations.
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<div class="controls">
|
|
50
|
+
<%= form_tag(heatmap_view_path, method: :get, style: "display: flex; gap: 15px; align-items: center;") do %>
|
|
51
|
+
<label for="days">Time period:</label>
|
|
52
|
+
<%= select_tag :days, options_for_select([["Last 7 days", 7], ["Last 14 days", 14], ["Last 30 days", 30], ["Last 90 days", 90]], @days), id: "days" %>
|
|
53
|
+
<button type="submit">Update</button>
|
|
54
|
+
<% end %>
|
|
55
|
+
<a href="<%= heatmap_data_path(days: @days, format: :json) %>" style="color: #4a4e69; text-decoration: none; font-size: 14px;">Download JSON</a>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<% metadata = @heatmap_data[:metadata] || {} %>
|
|
59
|
+
<div class="stats-grid">
|
|
60
|
+
<div class="stat-card">
|
|
61
|
+
<div class="value"><%= metadata[:total_events] || 0 %></div>
|
|
62
|
+
<div class="label">Total Events</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="stat-card">
|
|
65
|
+
<% peak_hour = metadata[:peak_hour] %>
|
|
66
|
+
<div class="value"><%= peak_hour ? peak_hour[:label] : "N/A" %></div>
|
|
67
|
+
<div class="label">Peak Hour</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="stat-card">
|
|
70
|
+
<% peak_day = metadata[:peak_day] %>
|
|
71
|
+
<div class="value"><%= peak_day ? peak_day[:label] : "N/A" %></div>
|
|
72
|
+
<div class="label">Peak Day</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="stat-card">
|
|
75
|
+
<% busiest = metadata[:busiest_slot] %>
|
|
76
|
+
<div class="value"><%= busiest ? "#{busiest[:day_label][0..2]} #{busiest[:hour_label]}" : "N/A" %></div>
|
|
77
|
+
<div class="label">Busiest Time Slot</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<% operations = metadata[:operations_breakdown] || {} %>
|
|
82
|
+
<% if operations.any? %>
|
|
83
|
+
<h2>Operations Summary</h2>
|
|
84
|
+
<div class="op-grid">
|
|
85
|
+
<% operations.each do |op, count| %>
|
|
86
|
+
<div class="op-card">
|
|
87
|
+
<div class="op-name op-<%= op %>"><%= op %></div>
|
|
88
|
+
<div class="op-count"><%= count %></div>
|
|
89
|
+
</div>
|
|
90
|
+
<% end %>
|
|
91
|
+
</div>
|
|
92
|
+
<% end %>
|
|
93
|
+
|
|
94
|
+
<h2>Weekly Heatmap (Hour × Day)</h2>
|
|
95
|
+
<% days = %w[Sun Mon Tue Wed Thu Fri Sat] %>
|
|
96
|
+
<% heatmap_data = @heatmap_data[:data] || [] %>
|
|
97
|
+
<% max_count = heatmap_data.map { |d| d[:count] }.max || 1 %>
|
|
98
|
+
|
|
99
|
+
<div class="heatmap-grid">
|
|
100
|
+
<div class="heatmap-cell"></div>
|
|
101
|
+
<% (0..23).each do |hour| %>
|
|
102
|
+
<div class="heatmap-cell heatmap-header"><%= hour %></div>
|
|
103
|
+
<% end %>
|
|
104
|
+
|
|
105
|
+
<% days.each_with_index do |day, day_idx| %>
|
|
106
|
+
<div class="heatmap-cell heatmap-row-label"><%= day %></div>
|
|
107
|
+
<% (0..23).each do |hour| %>
|
|
108
|
+
<% cell = heatmap_data.find { |d| d[:day] == day_idx && d[:hour] == hour } %>
|
|
109
|
+
<% count = cell ? cell[:count] : 0 %>
|
|
110
|
+
<% intensity = max_count > 0 ? (count.to_f / max_count * 100).round : 0 %>
|
|
111
|
+
<% bg_color = intensity == 0 ? "#f8f9fa" : "rgba(102, 126, 234, #{intensity / 100.0})" %>
|
|
112
|
+
<div class="heatmap-cell" style="background: <%= bg_color %>; color: <%= intensity > 50 ? '#fff' : '#495057' %>;">
|
|
113
|
+
<%= count > 0 ? count : "" %>
|
|
114
|
+
</div>
|
|
115
|
+
<% end %>
|
|
116
|
+
<% end %>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<h2>Breakdowns</h2>
|
|
120
|
+
<div class="breakdown-grid">
|
|
121
|
+
<div class="breakdown-card">
|
|
122
|
+
<h3>Hourly Distribution</h3>
|
|
123
|
+
<div class="bar-chart">
|
|
124
|
+
<% max_hourly = (@hourly || []).map { |h| h[:count] }.max || 1 %>
|
|
125
|
+
<% (@hourly || []).each do |h| %>
|
|
126
|
+
<% pct = max_hourly > 0 ? (h[:count].to_f / max_hourly * 100).round : 0 %>
|
|
127
|
+
<div class="bar-row">
|
|
128
|
+
<div class="bar-label"><%= h[:label] %></div>
|
|
129
|
+
<div class="bar-container">
|
|
130
|
+
<div class="bar-fill" style="width: <%= pct %>%;"></div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="bar-value"><%= h[:count] %></div>
|
|
133
|
+
</div>
|
|
134
|
+
<% end %>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="breakdown-card">
|
|
139
|
+
<h3>Daily Distribution</h3>
|
|
140
|
+
<div class="bar-chart">
|
|
141
|
+
<% max_daily = (@daily || []).map { |d| d[:count] }.max || 1 %>
|
|
142
|
+
<% (@daily || []).each do |d| %>
|
|
143
|
+
<% pct = max_daily > 0 ? (d[:count].to_f / max_daily * 100).round : 0 %>
|
|
144
|
+
<div class="bar-row">
|
|
145
|
+
<div class="bar-label"><%= d[:day][0..2] %></div>
|
|
146
|
+
<div class="bar-container">
|
|
147
|
+
<div class="bar-fill" style="width: <%= pct %>%;"></div>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="bar-value"><%= d[:count] %></div>
|
|
150
|
+
</div>
|
|
151
|
+
<% end %>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.lyra-dashboard { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
3
|
+
.lyra-dashboard h1 { color: #1a1a2e; border-bottom: 2px solid #4a4e69; padding-bottom: 10px; }
|
|
4
|
+
.lyra-dashboard h2 { color: #4a4e69; margin-top: 30px; }
|
|
5
|
+
.lyra-dashboard .mode-badge { display: inline-block; padding: 4px 12px; border-radius: 4px; font-weight: 600; }
|
|
6
|
+
.lyra-dashboard .mode-monitor { background: #d4edda; color: #155724; }
|
|
7
|
+
.lyra-dashboard .mode-hijack { background: #fff3cd; color: #856404; }
|
|
8
|
+
.lyra-dashboard .mode-event_sourcing, .lyra-dashboard .mode-es_sync, .lyra-dashboard .mode-es_async, .lyra-dashboard .mode-es_disabled { background: #cce5ff; color: #004085; }
|
|
9
|
+
.lyra-dashboard .mode-disabled { background: #f8d7da; color: #721c24; }
|
|
10
|
+
.lyra-dashboard .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; margin-top: 20px; }
|
|
11
|
+
.lyra-dashboard .card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
|
12
|
+
.lyra-dashboard .card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
|
13
|
+
.lyra-dashboard .card h3 { margin: 0 0 10px 0; color: #1a1a2e; }
|
|
14
|
+
.lyra-dashboard .card p { color: #6c757d; margin: 0 0 15px 0; font-size: 14px; }
|
|
15
|
+
.lyra-dashboard .card a { display: inline-block; padding: 8px 16px; background: #4a4e69; color: #fff; text-decoration: none; border-radius: 4px; font-size: 14px; }
|
|
16
|
+
.lyra-dashboard .card a:hover { background: #22223b; }
|
|
17
|
+
.lyra-dashboard .model-list { list-style: none; padding: 0; margin: 0; }
|
|
18
|
+
.lyra-dashboard .model-list li { padding: 12px 15px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
|
|
19
|
+
.lyra-dashboard .model-list li:last-child { border-bottom: none; }
|
|
20
|
+
.lyra-dashboard .model-list .model-name { font-weight: 500; color: #1a1a2e; }
|
|
21
|
+
.lyra-dashboard .model-list .model-links a { margin-left: 10px; padding: 4px 10px; background: #e9ecef; color: #495057; text-decoration: none; border-radius: 4px; font-size: 12px; }
|
|
22
|
+
.lyra-dashboard .model-list .model-links a:hover { background: #4a4e69; color: #fff; }
|
|
23
|
+
.lyra-dashboard .section-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; }
|
|
24
|
+
.lyra-dashboard .section-grid a { display: block; padding: 15px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 6px; text-decoration: none; color: #495057; text-align: center; }
|
|
25
|
+
.lyra-dashboard .section-grid a:hover { background: #e9ecef; border-color: #4a4e69; }
|
|
26
|
+
</style>
|
|
27
|
+
|
|
28
|
+
<div class="lyra-dashboard">
|
|
29
|
+
<h1>Lyra Dashboard</h1>
|
|
30
|
+
<p style="color: #6c757d; margin-bottom: 20px;">
|
|
31
|
+
Lyra provides event sourcing and audit trail capabilities for your Rails models.
|
|
32
|
+
<% if pam_dsl_available? %>
|
|
33
|
+
Monitor data changes, track CRUD operations, and ensure GDPR compliance with PII detection.
|
|
34
|
+
<% else %>
|
|
35
|
+
Monitor data changes and track CRUD operations.
|
|
36
|
+
<% end %>
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<p>
|
|
40
|
+
Mode: <span class="mode-badge mode-<%= @mode %>"><%= @mode.to_s.titleize %></span>
|
|
41
|
+
<span style="color: #6c757d; font-size: 13px; margin-left: 10px;">
|
|
42
|
+
<% case @mode.to_sym
|
|
43
|
+
when :monitor %>
|
|
44
|
+
(Events recorded after DB save - audit mode)
|
|
45
|
+
<% when :hijack %>
|
|
46
|
+
(Events recorded before DB save)
|
|
47
|
+
<% when :es_sync, :es_async, :event_sourcing %>
|
|
48
|
+
(Events are source of truth)
|
|
49
|
+
<% when :disabled %>
|
|
50
|
+
(Event tracking disabled)
|
|
51
|
+
<% end %>
|
|
52
|
+
</span>
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<h2>Monitored Models</h2>
|
|
56
|
+
<p style="color: #6c757d; font-size: 14px; margin-bottom: 15px;">
|
|
57
|
+
Models with <code>monitor_with_lyra</code> are tracked for all CRUD operations.
|
|
58
|
+
</p>
|
|
59
|
+
<% if @monitored_models.any? %>
|
|
60
|
+
<div class="card">
|
|
61
|
+
<ul class="model-list">
|
|
62
|
+
<% @monitored_models.each do |model| %>
|
|
63
|
+
<li>
|
|
64
|
+
<span class="model-name"><%= model.name %></span>
|
|
65
|
+
<span class="model-links">
|
|
66
|
+
<%= link_to "Overview", model_overview_path(model_class: model.name) %>
|
|
67
|
+
<%= link_to "Discrepancies", discrepancies_path(model_class: model.name) %>
|
|
68
|
+
</span>
|
|
69
|
+
</li>
|
|
70
|
+
<% end %>
|
|
71
|
+
</ul>
|
|
72
|
+
</div>
|
|
73
|
+
<% else %>
|
|
74
|
+
<p>No models are currently being monitored. Add <code>monitor_with_lyra</code> to your models.</p>
|
|
75
|
+
<% end %>
|
|
76
|
+
|
|
77
|
+
<h2>Event Flow & Audit</h2>
|
|
78
|
+
<p style="color: #6c757d; font-size: 14px; margin-bottom: 15px;">
|
|
79
|
+
Visualize events, track changes, and view the complete audit history of any record.
|
|
80
|
+
</p>
|
|
81
|
+
<div class="section-grid">
|
|
82
|
+
<%= link_to "Event Timeline", timeline_path %>
|
|
83
|
+
<%= link_to "CRUD Mapping", crud_mapping_path %>
|
|
84
|
+
<%= link_to "Audit Trail", audit_trail_path %>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<h2>Visualizations</h2>
|
|
88
|
+
<p style="color: #6c757d; font-size: 14px; margin-bottom: 15px;">
|
|
89
|
+
Interactive visualizations of event patterns and relationships.
|
|
90
|
+
</p>
|
|
91
|
+
<div class="section-grid">
|
|
92
|
+
<%= link_to "Event Graph", event_graph_view_path %>
|
|
93
|
+
<%= link_to "Activity Heatmap", heatmap_view_path %>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<% if pam_dsl_available? %>
|
|
97
|
+
<h2>Privacy & GDPR</h2>
|
|
98
|
+
<p style="color: #6c757d; font-size: 14px; margin-bottom: 15px;">
|
|
99
|
+
Tools for GDPR compliance, PII detection, and data lineage tracking.
|
|
100
|
+
</p>
|
|
101
|
+
<div class="section-grid">
|
|
102
|
+
<%= link_to "PII Detection", pii_detection_path %>
|
|
103
|
+
<%= link_to "Privacy Policy (PAM)", privacy_policy_path %>
|
|
104
|
+
</div>
|
|
105
|
+
<% end %>
|
|
106
|
+
|
|
107
|
+
<h2>Configuration</h2>
|
|
108
|
+
<p style="color: #6c757d; font-size: 14px; margin-bottom: 15px;">
|
|
109
|
+
View current Lyra settings, projection modes, and per-model event metrics.
|
|
110
|
+
</p>
|
|
111
|
+
<div class="section-grid">
|
|
112
|
+
<%= link_to "Projections & Config", projections_path %>
|
|
113
|
+
<%= link_to "Schema Registry", schema_path %>
|
|
114
|
+
<% if petri_flow_available? %>
|
|
115
|
+
<%= link_to "Formal Verification", verification_path %>
|
|
116
|
+
<% end %>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
</div>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.lyra-model-overview { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
3
|
+
.lyra-model-overview h1 { color: #1a1a2e; border-bottom: 2px solid #4a4e69; padding-bottom: 10px; }
|
|
4
|
+
.lyra-model-overview h2 { color: #4a4e69; margin-top: 30px; }
|
|
5
|
+
.lyra-model-overview .back-link { display: inline-block; margin-bottom: 20px; color: #4a4e69; text-decoration: none; }
|
|
6
|
+
.lyra-model-overview .back-link:hover { text-decoration: underline; }
|
|
7
|
+
.lyra-model-overview .summary-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
|
|
8
|
+
.lyra-model-overview .summary-card { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
|
9
|
+
.lyra-model-overview .summary-card .value { font-size: 36px; font-weight: 700; color: #1a1a2e; }
|
|
10
|
+
.lyra-model-overview .summary-card .label { color: #6c757d; font-size: 14px; margin-top: 5px; }
|
|
11
|
+
.lyra-model-overview .info-table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #dee2e6; border-radius: 8px; overflow: hidden; }
|
|
12
|
+
.lyra-model-overview .info-table th { background: #f8f9fa; padding: 12px 15px; text-align: left; border-bottom: 1px solid #dee2e6; font-weight: 600; color: #495057; }
|
|
13
|
+
.lyra-model-overview .info-table td { padding: 12px 15px; border-bottom: 1px solid #eee; }
|
|
14
|
+
.lyra-model-overview .info-table tr:last-child td { border-bottom: none; }
|
|
15
|
+
.lyra-model-overview .badge { display: inline-block; padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 600; }
|
|
16
|
+
.lyra-model-overview .badge-success { background: #d4edda; color: #155724; }
|
|
17
|
+
.lyra-model-overview .badge-warning { background: #fff3cd; color: #856404; }
|
|
18
|
+
.lyra-model-overview .badge-info { background: #cce5ff; color: #004085; }
|
|
19
|
+
.lyra-model-overview .recent-events { margin-top: 20px; }
|
|
20
|
+
.lyra-model-overview .event-item { background: #fff; border: 1px solid #dee2e6; border-radius: 6px; padding: 12px 15px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; }
|
|
21
|
+
.lyra-model-overview .event-item .event-info { display: flex; align-items: center; gap: 10px; }
|
|
22
|
+
.lyra-model-overview .event-item .timestamp { color: #6c757d; font-size: 12px; }
|
|
23
|
+
.lyra-model-overview .action-links { margin-top: 20px; display: flex; gap: 10px; }
|
|
24
|
+
.lyra-model-overview .action-links a { padding: 10px 20px; background: #4a4e69; color: #fff; text-decoration: none; border-radius: 6px; font-size: 14px; }
|
|
25
|
+
.lyra-model-overview .action-links a:hover { background: #22223b; }
|
|
26
|
+
.lyra-model-overview .action-links a.secondary { background: #e9ecef; color: #495057; }
|
|
27
|
+
.lyra-model-overview .action-links a.secondary:hover { background: #dee2e6; }
|
|
28
|
+
</style>
|
|
29
|
+
|
|
30
|
+
<div class="lyra-model-overview">
|
|
31
|
+
<%= link_to "← Back to Dashboard".html_safe, dashboard_path, class: "back-link" %>
|
|
32
|
+
|
|
33
|
+
<h1>Model Overview: <%= @model_class.name %></h1>
|
|
34
|
+
<p style="color: #6c757d; margin-bottom: 20px;">
|
|
35
|
+
Detailed view of the <%= @model_class.name %> model, including record counts, event statistics, and recent activity.
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<h2 style="margin-top: 0;">Statistics</h2>
|
|
39
|
+
<p style="color: #6c757d; font-size: 13px; margin-bottom: 15px;">
|
|
40
|
+
Overview of records and events for this model.
|
|
41
|
+
</p>
|
|
42
|
+
<div class="summary-cards">
|
|
43
|
+
<div class="summary-card">
|
|
44
|
+
<div class="value"><%= @records_count %></div>
|
|
45
|
+
<div class="label">Total Records</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="summary-card">
|
|
48
|
+
<div class="value"><%= @events_count %></div>
|
|
49
|
+
<div class="label">Total Events</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="summary-card">
|
|
52
|
+
<div class="value"><%= @records_count > 0 ? (@events_count.to_f / @records_count).round(1) : 0 %></div>
|
|
53
|
+
<div class="label">Avg Events/Record</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<h2>Model Information</h2>
|
|
58
|
+
<p style="color: #6c757d; font-size: 13px; margin-bottom: 15px;">
|
|
59
|
+
Schema and configuration details for this model.
|
|
60
|
+
</p>
|
|
61
|
+
<table class="info-table">
|
|
62
|
+
<tbody>
|
|
63
|
+
<tr>
|
|
64
|
+
<th style="width: 200px;">Table Name</th>
|
|
65
|
+
<td><code><%= @model_class.table_name %></code></td>
|
|
66
|
+
</tr>
|
|
67
|
+
<tr>
|
|
68
|
+
<th>Primary Key</th>
|
|
69
|
+
<td><code><%= @model_class.primary_key %></code></td>
|
|
70
|
+
</tr>
|
|
71
|
+
<tr>
|
|
72
|
+
<th>Columns</th>
|
|
73
|
+
<td>
|
|
74
|
+
<% @model_class.column_names.each do |col| %>
|
|
75
|
+
<span class="badge badge-info" style="margin-right: 5px; margin-bottom: 5px;"><%= col %></span>
|
|
76
|
+
<% end %>
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<th>Lyra Stream Pattern</th>
|
|
81
|
+
<td><code><%= @model_class.name %>$:id</code></td>
|
|
82
|
+
</tr>
|
|
83
|
+
</tbody>
|
|
84
|
+
</table>
|
|
85
|
+
|
|
86
|
+
<h2>Quick Actions</h2>
|
|
87
|
+
<p style="color: #6c757d; font-size: 13px; margin-bottom: 15px;">
|
|
88
|
+
Common operations for this model.
|
|
89
|
+
</p>
|
|
90
|
+
<div class="action-links">
|
|
91
|
+
<%= link_to "View Discrepancies", discrepancies_path(model_class: @model_class.name) %>
|
|
92
|
+
<%= link_to "Audit Trail Lookup", audit_trail_path(model_class: @model_class.name), class: "secondary" %>
|
|
93
|
+
<%= link_to "Event Timeline", timeline_path(model: @model_class.name), class: "secondary" %>
|
|
94
|
+
<%= link_to "CRUD Mapping", crud_mapping_path, class: "secondary" %>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<h2>Recent Records</h2>
|
|
98
|
+
<p style="color: #6c757d; font-size: 13px; margin-bottom: 15px;">
|
|
99
|
+
Most recently updated records for this model (showing up to 10). Click "Audit" to view the complete change history.
|
|
100
|
+
</p>
|
|
101
|
+
<div class="recent-events">
|
|
102
|
+
<% @model_class.order(updated_at: :desc).limit(10).each do |record| %>
|
|
103
|
+
<div class="event-item">
|
|
104
|
+
<div class="event-info">
|
|
105
|
+
<span class="badge badge-info">#<%= record.id %></span>
|
|
106
|
+
<span><%= record.try(:name) || record.try(:title) || record.try(:email) || "Record ##{record.id}" %></span>
|
|
107
|
+
</div>
|
|
108
|
+
<div style="display: flex; align-items: center; gap: 15px;">
|
|
109
|
+
<%= link_to "Audit", audit_trail_for_record_path(model_class: @model_class.name, id: record.id), style: "font-size: 12px; color: #4a4e69; text-decoration: none; padding: 4px 10px; background: #e9ecef; border-radius: 4px;" %>
|
|
110
|
+
<span class="timestamp">Updated: <%= record.updated_at.strftime("%Y-%m-%d %H:%M") %></span>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<% end %>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|