dbwatcher 1.1.1 → 1.1.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/README.md +24 -2
- data/app/assets/config/dbwatcher_manifest.js +1 -0
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +196 -119
- data/app/assets/javascripts/dbwatcher/components/dashboard.js +325 -0
- data/app/assets/javascripts/dbwatcher/components/timeline.js +211 -0
- data/app/assets/javascripts/dbwatcher/dbwatcher.js +5 -0
- data/app/assets/stylesheets/dbwatcher/application.css +691 -41
- data/app/assets/stylesheets/dbwatcher/application.scss +5 -0
- data/app/assets/stylesheets/dbwatcher/components/_badges.scss +68 -23
- data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +83 -26
- data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +3 -3
- data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +9 -0
- data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +248 -0
- data/app/assets/stylesheets/dbwatcher/components/_timeline.scss +326 -0
- data/app/assets/stylesheets/dbwatcher/vendor/_tabulator_overrides.scss +37 -0
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +18 -4
- data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +180 -0
- data/app/controllers/dbwatcher/dashboard/system_info_controller.rb +64 -0
- data/app/controllers/dbwatcher/dashboard_controller.rb +17 -0
- data/app/controllers/dbwatcher/sessions_controller.rb +3 -19
- data/app/helpers/dbwatcher/application_helper.rb +43 -11
- data/app/helpers/dbwatcher/diagram_helper.rb +0 -88
- data/app/views/dbwatcher/dashboard/_layout.html.erb +27 -0
- data/app/views/dbwatcher/dashboard/_overview.html.erb +188 -0
- data/app/views/dbwatcher/dashboard/_system_info.html.erb +22 -0
- data/app/views/dbwatcher/dashboard/_system_info_content.html.erb +389 -0
- data/app/views/dbwatcher/dashboard/index.html.erb +8 -177
- data/app/views/dbwatcher/sessions/_layout.html.erb +26 -0
- data/app/views/dbwatcher/sessions/{_summary_tab.html.erb → _summary.html.erb} +1 -1
- data/app/views/dbwatcher/sessions/_tables.html.erb +170 -0
- data/app/views/dbwatcher/sessions/_timeline.html.erb +260 -0
- data/app/views/dbwatcher/sessions/index.html.erb +107 -87
- data/app/views/dbwatcher/sessions/show.html.erb +12 -4
- data/app/views/dbwatcher/tables/index.html.erb +32 -40
- data/app/views/layouts/dbwatcher/application.html.erb +101 -48
- data/config/routes.rb +25 -7
- data/lib/dbwatcher/configuration.rb +18 -1
- data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +102 -1
- data/lib/dbwatcher/services/api/{changes_data_service.rb → tables_data_service.rb} +6 -6
- data/lib/dbwatcher/services/base_service.rb +2 -0
- data/lib/dbwatcher/services/system_info/database_info_collector.rb +263 -0
- data/lib/dbwatcher/services/system_info/machine_info_collector.rb +387 -0
- data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +328 -0
- data/lib/dbwatcher/services/system_info/system_info_collector.rb +114 -0
- data/lib/dbwatcher/services/timeline_data_service/enhancement_utilities.rb +100 -0
- data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +125 -0
- data/lib/dbwatcher/services/timeline_data_service/metadata_builder.rb +93 -0
- data/lib/dbwatcher/services/timeline_data_service.rb +130 -0
- data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +1 -1
- data/lib/dbwatcher/storage/concerns/error_handler.rb +6 -6
- data/lib/dbwatcher/storage/session.rb +5 -0
- data/lib/dbwatcher/storage/system_info_storage.rb +242 -0
- data/lib/dbwatcher/storage.rb +12 -0
- data/lib/dbwatcher/version.rb +1 -1
- data/lib/dbwatcher.rb +16 -2
- metadata +28 -16
- data/app/helpers/dbwatcher/component_helper.rb +0 -29
- data/app/views/dbwatcher/sessions/_changes_tab.html.erb +0 -265
- data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +0 -12
- data/app/views/dbwatcher/sessions/changes.html.erb +0 -21
- data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +0 -44
- data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +0 -96
- data/app/views/dbwatcher/sessions/diagrams.html.erb +0 -21
- data/app/views/dbwatcher/sessions/shared/_layout.html.erb +0 -8
- data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +0 -35
- data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +0 -25
- data/app/views/dbwatcher/sessions/summary.html.erb +0 -21
- /data/app/views/dbwatcher/sessions/{_diagrams_tab.html.erb → _diagrams.html.erb} +0 -0
@@ -1,4 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
<%= render layout: 'layout', locals: { active_tab: @active_tab, session: @session } do %>
|
2
|
+
<% case @active_tab %>
|
3
|
+
<% when 'timeline' %>
|
4
|
+
<%= render partial: 'timeline' %>
|
5
|
+
<% when 'summary' %>
|
6
|
+
<%= render partial: 'summary' %>
|
7
|
+
<% when 'diagrams' %>
|
8
|
+
<%= render partial: 'diagrams' %>
|
9
|
+
<% else %>
|
10
|
+
<%= render partial: 'tables' %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
@@ -1,51 +1,43 @@
|
|
1
1
|
<%# Tables Index Page %>
|
2
|
-
<div class="h-full flex flex-col">
|
2
|
+
<div class="h-full flex flex-col" x-data="{ searchTerm: '' }">
|
3
3
|
<%= render 'dbwatcher/shared/header', title: 'Database Tables', subtitle: "#{@tables.count} tables" %>
|
4
|
-
|
4
|
+
|
5
5
|
<%= render 'dbwatcher/shared/tab_bar', tabs: [
|
6
|
-
{ name: 'All Tables', active: true }
|
7
|
-
{ name: 'Modified', active: false },
|
8
|
-
{ name: 'Recent', active: false }
|
6
|
+
{ name: 'All Tables', active: true }
|
9
7
|
] %>
|
10
|
-
|
8
|
+
|
11
9
|
<!-- Toolbar -->
|
12
10
|
<div class="h-8 bg-gray-100 border-b border-gray-300 flex items-center px-4 gap-2">
|
13
|
-
<input type="text" placeholder="Filter tables..."
|
11
|
+
<input type="text" placeholder="Filter tables..."
|
14
12
|
class="compact-input flex-1 max-w-xs"
|
15
|
-
x-
|
13
|
+
x-model="searchTerm"
|
16
14
|
@input="filterTables()">
|
17
|
-
<select class="compact-select">
|
18
|
-
<option>All Types</option>
|
19
|
-
<option>User Tables</option>
|
20
|
-
<option>System Tables</option>
|
21
|
-
</select>
|
22
|
-
<button class="compact-button bg-blue-medium text-white hover:bg-blue-dark">
|
23
|
-
Refresh
|
24
|
-
</button>
|
25
15
|
</div>
|
26
|
-
|
16
|
+
|
27
17
|
<!-- Content Area -->
|
28
18
|
<div class="flex-1 overflow-auto">
|
29
19
|
<table class="compact-table w-full">
|
30
20
|
<thead>
|
31
21
|
<tr>
|
32
|
-
<th class="text-left">Table Name</th>
|
33
|
-
<th class="text-center">Changes</th>
|
34
|
-
<th class="text-center">Last Modified</th>
|
35
|
-
<th class="text-center">Operations</th>
|
36
|
-
<th class="text-right">Actions</th>
|
22
|
+
<th class="text-left" style="min-width: 250px; width: 40%">Table Name</th>
|
23
|
+
<th class="text-center" style="width: 100px">Changes</th>
|
24
|
+
<th class="text-center" style="width: 180px">Last Modified</th>
|
25
|
+
<th class="text-center" style="width: 120px">Operations</th>
|
26
|
+
<th class="text-right" style="width: 100px">Actions</th>
|
37
27
|
</tr>
|
38
28
|
</thead>
|
39
29
|
<tbody>
|
40
30
|
<% @tables.each do |table| %>
|
41
31
|
<% next if table[:name].nil? || table[:name].empty? %>
|
42
32
|
<tr class="hover:bg-gray-50">
|
43
|
-
<td class="font-medium text-navy-dark">
|
33
|
+
<td class="font-medium text-navy-dark" style="max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
44
34
|
<div class="flex items-center gap-2">
|
45
|
-
<svg class="w-3 h-3 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
35
|
+
<svg class="w-3 h-3 text-gray-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
46
36
|
<path fill-rule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11 4a1 1 0 10-2 0v4a1 1 0 102 0V7zm-3 1a1 1 0 10-2 0v3a1 1 0 102 0V8zM8 9a1 1 0 00-2 0v2a1 1 0 102 0V9z" clip-rule="evenodd"/>
|
47
37
|
</svg>
|
48
|
-
|
38
|
+
<span class="truncate" title="<%= table[:name] %>">
|
39
|
+
<%= link_to table[:name], table_path(table[:name]), class: "text-navy-dark hover:text-blue-medium" %>
|
40
|
+
</span>
|
49
41
|
</div>
|
50
42
|
</td>
|
51
43
|
<td class="text-center">
|
@@ -57,35 +49,35 @@
|
|
57
49
|
</td>
|
58
50
|
<td class="text-center text-gray-600">
|
59
51
|
<% if table[:last_change] %>
|
60
|
-
<%=
|
52
|
+
<%= format_timestamp(table[:last_change]) %>
|
61
53
|
<% else %>
|
62
54
|
<span class="text-gray-400">No changes</span>
|
63
55
|
<% end %>
|
64
56
|
</td>
|
65
57
|
<td class="text-center">
|
66
|
-
<div class="flex gap-
|
67
|
-
<span class="badge badge-insert text-xs" title="Inserts">
|
68
|
-
<span class="badge badge-update text-xs" title="Updates">
|
69
|
-
<span class="badge badge-delete text-xs" title="Deletes">
|
58
|
+
<div class="flex gap-2 justify-center">
|
59
|
+
<span class="badge badge-insert text-xs px-2" title="Inserts">Insert</span>
|
60
|
+
<span class="badge badge-update text-xs px-2" title="Updates">Update</span>
|
61
|
+
<span class="badge badge-delete text-xs px-2" title="Deletes">Delete</span>
|
70
62
|
</div>
|
71
63
|
</td>
|
72
64
|
<td class="text-right">
|
73
65
|
<div class="flex gap-1 justify-end">
|
74
|
-
<%= link_to changes_table_path(table[:name]),
|
75
|
-
class: "compact-button bg-gray-500 text-white hover:bg-gray-600",
|
66
|
+
<%= link_to changes_table_path(table[:name]),
|
67
|
+
class: "compact-button bg-gray-500 text-white hover:bg-gray-600",
|
76
68
|
title: "View Changes" do %>
|
77
69
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
78
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
70
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
79
71
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
80
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
72
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
81
73
|
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
82
74
|
</svg>
|
83
75
|
<% end %>
|
84
|
-
<%= link_to table_path(table[:name]),
|
85
|
-
class: "compact-button bg-navy-dark text-white hover:bg-blue-medium",
|
76
|
+
<%= link_to table_path(table[:name]),
|
77
|
+
class: "compact-button bg-navy-dark text-white hover:bg-blue-medium",
|
86
78
|
title: "Table Details" do %>
|
87
79
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
88
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
80
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
89
81
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
90
82
|
</svg>
|
91
83
|
<% end %>
|
@@ -96,10 +88,10 @@
|
|
96
88
|
</tbody>
|
97
89
|
</table>
|
98
90
|
</div>
|
99
|
-
|
91
|
+
|
100
92
|
<!-- Status Bar -->
|
101
93
|
<div class="h-6 bg-gray-100 border-t border-gray-300 flex items-center px-4 text-xs text-gray-600">
|
102
|
-
<%= @tables.count %> tables total •
|
94
|
+
<%= @tables.count %> tables total •
|
103
95
|
<%= @tables.count { |t| t[:change_count] > 0 } %> with changes •
|
104
96
|
Last updated: <%= Time.current.strftime("%H:%M:%S") %>
|
105
97
|
</div>
|
@@ -110,7 +102,7 @@ function filterTables() {
|
|
110
102
|
// Simple table filtering functionality
|
111
103
|
const searchTerm = document.querySelector('input[x-model="searchTerm"]').value.toLowerCase();
|
112
104
|
const rows = document.querySelectorAll('tbody tr');
|
113
|
-
|
105
|
+
|
114
106
|
rows.forEach(row => {
|
115
107
|
const tableName = row.querySelector('td').textContent.toLowerCase();
|
116
108
|
if (tableName.includes(searchTerm)) {
|
@@ -29,6 +29,8 @@
|
|
29
29
|
<%= javascript_include_tag "dbwatcher/components/changes_table_hybrid" %>
|
30
30
|
<%= javascript_include_tag "dbwatcher/components/diagrams" %>
|
31
31
|
<%= javascript_include_tag "dbwatcher/components/summary" %>
|
32
|
+
<%= javascript_include_tag "dbwatcher/components/dashboard" %>
|
33
|
+
<%= javascript_include_tag "dbwatcher/components/timeline" %>
|
32
34
|
|
33
35
|
<!-- DBWatcher Services -->
|
34
36
|
<%= javascript_include_tag "dbwatcher/core/alpine_store" %>
|
@@ -56,9 +58,9 @@
|
|
56
58
|
}
|
57
59
|
});
|
58
60
|
|
59
|
-
//
|
61
|
+
// Improved initialization with safety checks
|
60
62
|
document.addEventListener('DOMContentLoaded', function() {
|
61
|
-
//
|
63
|
+
// Prevent immediate DOM access conflicts
|
62
64
|
setTimeout(() => {
|
63
65
|
try {
|
64
66
|
if (window.DBWatcher && !DBWatcher.initialized) {
|
@@ -68,16 +70,22 @@
|
|
68
70
|
} catch (error) {
|
69
71
|
console.error('❌ Error during DBWatcher fallback initialization:', error);
|
70
72
|
}
|
71
|
-
},
|
73
|
+
}, 150);
|
72
74
|
|
73
|
-
// Plugin verification
|
75
|
+
// Plugin verification with safety checks
|
74
76
|
setTimeout(() => {
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
77
|
+
try {
|
78
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
79
|
+
if (!window.Alpine.directive('collapse')) {
|
80
|
+
console.warn('⚠️ Alpine.js Collapse plugin may not be properly loaded');
|
81
|
+
} else {
|
82
|
+
console.log('✅ Alpine.js Collapse plugin verified');
|
83
|
+
}
|
84
|
+
}
|
85
|
+
} catch (error) {
|
86
|
+
console.warn('⚠️ Alpine.js plugin verification failed:', error);
|
79
87
|
}
|
80
|
-
},
|
88
|
+
}, 300);
|
81
89
|
});
|
82
90
|
</script>
|
83
91
|
|
@@ -129,7 +137,7 @@
|
|
129
137
|
|
130
138
|
<!-- Navigation -->
|
131
139
|
<nav class="flex-1 py-2 overflow-y-auto">
|
132
|
-
<%= link_to root_path, class: "sidebar-item #{current_page?(root_path) ? 'active' : ''}" do %>
|
140
|
+
<%= link_to root_path, class: "sidebar-item #{current_page?(root_path) || (params[:tab] == 'system_info') ? 'active' : ''}" do %>
|
133
141
|
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
134
142
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
135
143
|
d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z"/>
|
@@ -160,6 +168,8 @@
|
|
160
168
|
</svg>
|
161
169
|
<span x-show="!sidebarCollapsed">SQL Logs</span>
|
162
170
|
<% end %>
|
171
|
+
|
172
|
+
|
163
173
|
</nav>
|
164
174
|
|
165
175
|
<!-- Actions -->
|
@@ -176,6 +186,42 @@
|
|
176
186
|
<span x-show="!sidebarCollapsed" class="text-xs">Clear All</span>
|
177
187
|
<% end %>
|
178
188
|
</div>
|
189
|
+
|
190
|
+
<!-- Gem Info Section -->
|
191
|
+
<div class="mt-auto pt-4 border-t border-gray-700">
|
192
|
+
<div class="px-3 py-2">
|
193
|
+
<div class="text-xs text-gray-400 mb-2 font-medium" x-show="!sidebarCollapsed">
|
194
|
+
dbwatcher v<%= Dbwatcher::VERSION %>
|
195
|
+
</div>
|
196
|
+
<div class="flex items-center gap-2 text-xs text-gray-400" x-show="!sidebarCollapsed">
|
197
|
+
<a href="https://github.com/patrick204nqh/dbwatcher"
|
198
|
+
target="_blank"
|
199
|
+
rel="noopener noreferrer"
|
200
|
+
class="hover:text-blue-400 transition-colors duration-200 flex items-center gap-1"
|
201
|
+
title="View on GitHub">
|
202
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
|
203
|
+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
204
|
+
</svg>
|
205
|
+
<span>GitHub</span>
|
206
|
+
</a>
|
207
|
+
<span class="text-gray-600">•</span>
|
208
|
+
<a href="https://rubydoc.info/gems/dbwatcher/<%= Dbwatcher::VERSION %>"
|
209
|
+
target="_blank"
|
210
|
+
rel="noopener noreferrer"
|
211
|
+
class="hover:text-red-400 transition-colors duration-200 flex items-center gap-1"
|
212
|
+
title="View Documentation">
|
213
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
|
214
|
+
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
|
215
|
+
</svg>
|
216
|
+
<span>Docs</span>
|
217
|
+
</a>
|
218
|
+
</div>
|
219
|
+
<!-- Collapsed state - just show version -->
|
220
|
+
<div class="text-xs text-gray-400 text-center" x-show="sidebarCollapsed" title="DBWatcher v<%= Dbwatcher::VERSION %>">
|
221
|
+
v<%= Dbwatcher::VERSION %>
|
222
|
+
</div>
|
223
|
+
</div>
|
224
|
+
</div>
|
179
225
|
</div>
|
180
226
|
</aside>
|
181
227
|
|
@@ -211,50 +257,57 @@
|
|
211
257
|
<!-- Initialize DBWatcher system -->
|
212
258
|
<script>
|
213
259
|
document.addEventListener('DOMContentLoaded', function() {
|
214
|
-
//
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
window.DBWatcher
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
260
|
+
// Add delay to prevent timing conflicts
|
261
|
+
setTimeout(() => {
|
262
|
+
try {
|
263
|
+
// Initialize DBWatcher if available
|
264
|
+
if (window.DBWatcher) {
|
265
|
+
// Ensure BaseComponent is available
|
266
|
+
if (!window.DBWatcher.BaseComponent && typeof DBWatcher.BaseComponent !== 'function') {
|
267
|
+
window.DBWatcher.BaseComponent = function(config = {}) {
|
268
|
+
return {
|
269
|
+
init() {
|
270
|
+
if (this.componentInit) {
|
271
|
+
try {
|
272
|
+
this.componentInit();
|
273
|
+
} catch (error) {
|
274
|
+
console.error("Error during component initialization:", error);
|
275
|
+
}
|
276
|
+
}
|
226
277
|
}
|
278
|
+
};
|
279
|
+
};
|
280
|
+
console.log('Added fallback BaseComponent');
|
281
|
+
}
|
282
|
+
|
283
|
+
// Register legacy components with Alpine directly if ComponentRegistry isn't available
|
284
|
+
if (!window.DBWatcher.ComponentRegistry) {
|
285
|
+
console.log('ComponentRegistry not available, using direct Alpine registration');
|
286
|
+
|
287
|
+
// Ensure Alpine is available with safety checks
|
288
|
+
if (window.Alpine && typeof window.Alpine.data === 'function') {
|
289
|
+
// Direct registration of components with Alpine
|
290
|
+
if (!window.Alpine.data('changesTable') && window.DBWatcher.components && window.DBWatcher.components.changesTable) {
|
291
|
+
window.Alpine.data('changesTable', (config = {}) => {
|
292
|
+
const component = window.DBWatcher.components.changesTable(config);
|
293
|
+
return component;
|
294
|
+
});
|
295
|
+
console.log('Registered changesTable component with Alpine');
|
227
296
|
}
|
228
297
|
}
|
229
|
-
};
|
230
|
-
};
|
231
|
-
console.log('Added fallback BaseComponent');
|
232
|
-
}
|
233
|
-
|
234
|
-
// Register legacy components with Alpine directly if ComponentRegistry isn't available
|
235
|
-
if (!window.DBWatcher.ComponentRegistry) {
|
236
|
-
console.log('ComponentRegistry not available, using direct Alpine registration');
|
237
|
-
|
238
|
-
// Ensure Alpine is available
|
239
|
-
if (window.Alpine) {
|
240
|
-
// Direct registration of components with Alpine
|
241
|
-
if (!window.Alpine.data('changesTable') && window.DBWatcher.components && window.DBWatcher.components.changesTable) {
|
242
|
-
window.Alpine.data('changesTable', (config = {}) => {
|
243
|
-
const component = window.DBWatcher.components.changesTable(config);
|
244
|
-
return component;
|
245
|
-
});
|
246
|
-
console.log('Registered changesTable component with Alpine');
|
247
298
|
}
|
248
|
-
}
|
249
|
-
}
|
250
299
|
|
251
|
-
|
252
|
-
|
300
|
+
console.log('✅ DBWatcher initialized (fallback)');
|
301
|
+
}
|
253
302
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
303
|
+
// Verify Alpine.js plugins with safety checks
|
304
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function' && window.Alpine.directive('collapse')) {
|
305
|
+
console.log('✅ Alpine.js Collapse plugin verified');
|
306
|
+
}
|
307
|
+
} catch (error) {
|
308
|
+
console.error('❌ Error during DBWatcher system initialization:', error);
|
309
|
+
}
|
310
|
+
}, 200);
|
258
311
|
});
|
259
312
|
</script>
|
260
313
|
</body>
|
data/config/routes.rb
CHANGED
@@ -3,16 +3,20 @@
|
|
3
3
|
Dbwatcher::Engine.routes.draw do
|
4
4
|
root to: "dashboard#index"
|
5
5
|
|
6
|
-
# Dashboard
|
6
|
+
# Dashboard actions
|
7
7
|
delete :clear_all, to: "dashboard#clear_all"
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
# Dashboard system info actions
|
10
|
+
namespace :dashboard do
|
11
|
+
resources :system_info, only: [] do
|
12
|
+
collection do
|
13
|
+
post :refresh
|
14
|
+
delete :clear_cache
|
15
|
+
end
|
14
16
|
end
|
17
|
+
end
|
15
18
|
|
19
|
+
resources :sessions do
|
16
20
|
collection do
|
17
21
|
delete :clear
|
18
22
|
end
|
@@ -23,15 +27,29 @@ Dbwatcher::Engine.routes.draw do
|
|
23
27
|
namespace :v1 do
|
24
28
|
resources :sessions, only: [] do
|
25
29
|
member do
|
26
|
-
get :
|
30
|
+
get :tables_data
|
27
31
|
get :summary_data
|
28
32
|
get :diagram_data
|
33
|
+
get :timeline_data
|
29
34
|
end
|
30
35
|
|
31
36
|
collection do
|
32
37
|
get :diagram_types
|
33
38
|
end
|
34
39
|
end
|
40
|
+
|
41
|
+
# System information API routes
|
42
|
+
resources :system_info, only: [:index] do
|
43
|
+
collection do
|
44
|
+
post :refresh
|
45
|
+
get :machine
|
46
|
+
get :database
|
47
|
+
get :runtime
|
48
|
+
get :summary
|
49
|
+
delete :clear_cache
|
50
|
+
get :cache_status
|
51
|
+
end
|
52
|
+
end
|
35
53
|
end
|
36
54
|
end
|
37
55
|
|
@@ -21,6 +21,11 @@ module Dbwatcher
|
|
21
21
|
:diagram_preserve_table_case, :diagram_direction, :diagram_cardinality_format,
|
22
22
|
:diagram_show_attribute_count, :diagram_show_method_count
|
23
23
|
|
24
|
+
# System information configuration
|
25
|
+
attr_accessor :collect_system_info, :system_info_refresh_interval,
|
26
|
+
:collect_sensitive_env_vars, :system_info_cache_duration,
|
27
|
+
:system_info_include_performance_metrics
|
28
|
+
|
24
29
|
# Initialize with default values
|
25
30
|
def initialize
|
26
31
|
# Storage configuration defaults
|
@@ -30,7 +35,7 @@ module Dbwatcher
|
|
30
35
|
@auto_clean_after_days = 7
|
31
36
|
|
32
37
|
# Query tracking configuration defaults
|
33
|
-
@track_queries =
|
38
|
+
@track_queries = false
|
34
39
|
@slow_query_threshold = 200 # milliseconds
|
35
40
|
@max_query_logs_per_day = 1000
|
36
41
|
|
@@ -39,6 +44,9 @@ module Dbwatcher
|
|
39
44
|
|
40
45
|
# Initialize diagram configuration with defaults
|
41
46
|
initialize_diagram_config
|
47
|
+
|
48
|
+
# Initialize system information configuration with defaults
|
49
|
+
initialize_system_info_config
|
42
50
|
end
|
43
51
|
|
44
52
|
# Initialize diagram configuration with default values
|
@@ -56,6 +64,15 @@ module Dbwatcher
|
|
56
64
|
@diagram_show_method_count = true
|
57
65
|
end
|
58
66
|
|
67
|
+
# Initialize system information configuration with default values
|
68
|
+
def initialize_system_info_config
|
69
|
+
@collect_system_info = true
|
70
|
+
@system_info_refresh_interval = 5 * 60 # 5 minutes in seconds
|
71
|
+
@collect_sensitive_env_vars = false
|
72
|
+
@system_info_cache_duration = 60 * 60 # 1 hour in seconds
|
73
|
+
@system_info_include_performance_metrics = true
|
74
|
+
end
|
75
|
+
|
59
76
|
# Validate configuration
|
60
77
|
#
|
61
78
|
# @return [Boolean] true if configuration is valid
|
@@ -51,7 +51,8 @@ module Dbwatcher
|
|
51
51
|
sample_record: nil,
|
52
52
|
total_operations: 0,
|
53
53
|
operations: { insert: 0, update: 0, delete: 0 },
|
54
|
-
changes: []
|
54
|
+
changes: [],
|
55
|
+
model_class: find_model_class(table_name)
|
55
56
|
}
|
56
57
|
end
|
57
58
|
|
@@ -196,6 +197,106 @@ module Dbwatcher
|
|
196
197
|
total_operations: tables.values.sum { |t| t[:total_operations] }
|
197
198
|
}
|
198
199
|
end
|
200
|
+
|
201
|
+
# Find the actual Rails model class for a table name
|
202
|
+
#
|
203
|
+
# @param table_name [String] database table name
|
204
|
+
# @return [String, nil] model class name or nil if not found
|
205
|
+
def find_model_class(table_name)
|
206
|
+
return nil unless table_name.is_a?(String)
|
207
|
+
|
208
|
+
Rails.logger.debug "Finding model class for table: #{table_name}"
|
209
|
+
ensure_models_loaded
|
210
|
+
|
211
|
+
# Try conventional naming first
|
212
|
+
model_name = find_by_convention(table_name)
|
213
|
+
return model_name if model_name
|
214
|
+
|
215
|
+
# Fallback to searching all ActiveRecord descendants
|
216
|
+
find_by_table_name_search(table_name)
|
217
|
+
rescue StandardError => e
|
218
|
+
log_model_finding_error(table_name, e)
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
|
222
|
+
# Ensure all models are loaded in development
|
223
|
+
def ensure_models_loaded
|
224
|
+
Rails.application.eager_load! if Rails.env.development?
|
225
|
+
end
|
226
|
+
|
227
|
+
# Find model by conventional naming (table_name.classify)
|
228
|
+
#
|
229
|
+
# @param table_name [String] database table name
|
230
|
+
# @return [String, nil] model class name or nil
|
231
|
+
def find_by_convention(table_name)
|
232
|
+
model_name = table_name.classify
|
233
|
+
Rails.logger.debug "Expected model name: #{model_name}"
|
234
|
+
|
235
|
+
return nil unless Object.const_defined?(model_name)
|
236
|
+
|
237
|
+
model_class = Object.const_get(model_name)
|
238
|
+
Rails.logger.debug "Found model class: #{model_class}"
|
239
|
+
|
240
|
+
validate_and_return_model(model_class, table_name)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Validate model class and return name if valid
|
244
|
+
#
|
245
|
+
# @param model_class [Class] the model class to validate
|
246
|
+
# @param table_name [String] expected table name
|
247
|
+
# @return [String, nil] model name or nil
|
248
|
+
def validate_and_return_model(model_class, table_name)
|
249
|
+
unless active_record_model?(model_class)
|
250
|
+
Rails.logger.debug "#{model_class} is not an ActiveRecord model"
|
251
|
+
return nil
|
252
|
+
end
|
253
|
+
|
254
|
+
if model_class.table_name == table_name
|
255
|
+
Rails.logger.debug "Model #{model_class.name} matches table #{table_name}"
|
256
|
+
model_class.name
|
257
|
+
else
|
258
|
+
log_table_name_mismatch(model_class, table_name)
|
259
|
+
nil
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Check if class is an ActiveRecord model
|
264
|
+
#
|
265
|
+
# @param model_class [Class] class to check
|
266
|
+
# @return [Boolean] true if ActiveRecord model
|
267
|
+
def active_record_model?(model_class)
|
268
|
+
model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Find model by searching all ActiveRecord descendants
|
272
|
+
#
|
273
|
+
# @param table_name [String] database table name
|
274
|
+
# @return [String, nil] model class name or nil
|
275
|
+
def find_by_table_name_search(table_name)
|
276
|
+
Rails.logger.debug "Checking all ActiveRecord descendants..."
|
277
|
+
|
278
|
+
ActiveRecord::Base.descendants.each do |model|
|
279
|
+
if model.table_name == table_name
|
280
|
+
Rails.logger.debug "Found matching model: #{model.name} for table #{table_name}"
|
281
|
+
return model.name
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
Rails.logger.debug "No model found for table: #{table_name}"
|
286
|
+
nil
|
287
|
+
end
|
288
|
+
|
289
|
+
# Log table name mismatch
|
290
|
+
def log_table_name_mismatch(model_class, table_name)
|
291
|
+
message = "Model #{model_class.name} table_name (#{model_class.table_name}) doesn't match #{table_name}"
|
292
|
+
Rails.logger.debug message
|
293
|
+
end
|
294
|
+
|
295
|
+
# Log model finding error
|
296
|
+
def log_model_finding_error(table_name, error)
|
297
|
+
Rails.logger.debug "Error finding model class for table #{table_name}: #{error.message}"
|
298
|
+
Rails.logger.debug error.backtrace.first(5).join("\n")
|
299
|
+
end
|
199
300
|
end
|
200
301
|
end
|
201
302
|
end
|
@@ -3,25 +3,25 @@
|
|
3
3
|
module Dbwatcher
|
4
4
|
module Services
|
5
5
|
module Api
|
6
|
-
# Service for handling filtered
|
6
|
+
# Service for handling filtered tables data
|
7
7
|
#
|
8
|
-
# Provides
|
8
|
+
# Provides tables data for the sessions tables view and API endpoints
|
9
9
|
# with filtering and caching support.
|
10
|
-
class
|
10
|
+
class TablesDataService < BaseApiService
|
11
11
|
def call
|
12
12
|
start_time = Time.now
|
13
13
|
|
14
14
|
# Check for nil session first
|
15
15
|
return { error: "Session not found" } unless session
|
16
16
|
|
17
|
-
log_service_start("Getting
|
17
|
+
log_service_start("Getting tables data for session #{session.id}")
|
18
18
|
|
19
19
|
validation_error = validate_session
|
20
20
|
return validation_error if validation_error
|
21
21
|
|
22
22
|
begin
|
23
23
|
result = with_cache(cache_suffix) do
|
24
|
-
|
24
|
+
build_tables_response
|
25
25
|
end
|
26
26
|
|
27
27
|
log_service_completion(start_time, session_id: session.id, filters: filter_params)
|
@@ -33,7 +33,7 @@ module Dbwatcher
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
-
def
|
36
|
+
def build_tables_response
|
37
37
|
{
|
38
38
|
tables_summary: build_filtered_summary,
|
39
39
|
filters: filter_params || {},
|