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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -2
  3. data/app/assets/config/dbwatcher_manifest.js +1 -0
  4. data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +196 -119
  5. data/app/assets/javascripts/dbwatcher/components/dashboard.js +325 -0
  6. data/app/assets/javascripts/dbwatcher/components/timeline.js +211 -0
  7. data/app/assets/javascripts/dbwatcher/dbwatcher.js +5 -0
  8. data/app/assets/stylesheets/dbwatcher/application.css +691 -41
  9. data/app/assets/stylesheets/dbwatcher/application.scss +5 -0
  10. data/app/assets/stylesheets/dbwatcher/components/_badges.scss +68 -23
  11. data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +83 -26
  12. data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +3 -3
  13. data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +9 -0
  14. data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +248 -0
  15. data/app/assets/stylesheets/dbwatcher/components/_timeline.scss +326 -0
  16. data/app/assets/stylesheets/dbwatcher/vendor/_tabulator_overrides.scss +37 -0
  17. data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +18 -4
  18. data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +180 -0
  19. data/app/controllers/dbwatcher/dashboard/system_info_controller.rb +64 -0
  20. data/app/controllers/dbwatcher/dashboard_controller.rb +17 -0
  21. data/app/controllers/dbwatcher/sessions_controller.rb +3 -19
  22. data/app/helpers/dbwatcher/application_helper.rb +43 -11
  23. data/app/helpers/dbwatcher/diagram_helper.rb +0 -88
  24. data/app/views/dbwatcher/dashboard/_layout.html.erb +27 -0
  25. data/app/views/dbwatcher/dashboard/_overview.html.erb +188 -0
  26. data/app/views/dbwatcher/dashboard/_system_info.html.erb +22 -0
  27. data/app/views/dbwatcher/dashboard/_system_info_content.html.erb +389 -0
  28. data/app/views/dbwatcher/dashboard/index.html.erb +8 -177
  29. data/app/views/dbwatcher/sessions/_layout.html.erb +26 -0
  30. data/app/views/dbwatcher/sessions/{_summary_tab.html.erb → _summary.html.erb} +1 -1
  31. data/app/views/dbwatcher/sessions/_tables.html.erb +170 -0
  32. data/app/views/dbwatcher/sessions/_timeline.html.erb +260 -0
  33. data/app/views/dbwatcher/sessions/index.html.erb +107 -87
  34. data/app/views/dbwatcher/sessions/show.html.erb +12 -4
  35. data/app/views/dbwatcher/tables/index.html.erb +32 -40
  36. data/app/views/layouts/dbwatcher/application.html.erb +101 -48
  37. data/config/routes.rb +25 -7
  38. data/lib/dbwatcher/configuration.rb +18 -1
  39. data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +102 -1
  40. data/lib/dbwatcher/services/api/{changes_data_service.rb → tables_data_service.rb} +6 -6
  41. data/lib/dbwatcher/services/base_service.rb +2 -0
  42. data/lib/dbwatcher/services/system_info/database_info_collector.rb +263 -0
  43. data/lib/dbwatcher/services/system_info/machine_info_collector.rb +387 -0
  44. data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +328 -0
  45. data/lib/dbwatcher/services/system_info/system_info_collector.rb +114 -0
  46. data/lib/dbwatcher/services/timeline_data_service/enhancement_utilities.rb +100 -0
  47. data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +125 -0
  48. data/lib/dbwatcher/services/timeline_data_service/metadata_builder.rb +93 -0
  49. data/lib/dbwatcher/services/timeline_data_service.rb +130 -0
  50. data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +1 -1
  51. data/lib/dbwatcher/storage/concerns/error_handler.rb +6 -6
  52. data/lib/dbwatcher/storage/session.rb +5 -0
  53. data/lib/dbwatcher/storage/system_info_storage.rb +242 -0
  54. data/lib/dbwatcher/storage.rb +12 -0
  55. data/lib/dbwatcher/version.rb +1 -1
  56. data/lib/dbwatcher.rb +16 -2
  57. metadata +28 -16
  58. data/app/helpers/dbwatcher/component_helper.rb +0 -29
  59. data/app/views/dbwatcher/sessions/_changes_tab.html.erb +0 -265
  60. data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +0 -12
  61. data/app/views/dbwatcher/sessions/changes.html.erb +0 -21
  62. data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +0 -44
  63. data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +0 -96
  64. data/app/views/dbwatcher/sessions/diagrams.html.erb +0 -21
  65. data/app/views/dbwatcher/sessions/shared/_layout.html.erb +0 -8
  66. data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +0 -35
  67. data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +0 -25
  68. data/app/views/dbwatcher/sessions/summary.html.erb +0 -21
  69. /data/app/views/dbwatcher/sessions/{_diagrams_tab.html.erb → _diagrams.html.erb} +0 -0
@@ -1,4 +1,12 @@
1
- <!-- This view is only used for redirects now -->
2
- <div>
3
- <p>Redirecting to changes...</p>
4
- </div>
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-data="" x-model="searchTerm"
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
- <%= link_to table[:name], table_path(table[:name]), class: "text-navy-dark hover:text-blue-medium" %>
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
- <%= Time.parse(table[:last_change].to_s).strftime("%m/%d %H:%M") rescue table[:last_change] %>
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-1 justify-center">
67
- <span class="badge badge-insert text-xs" title="Inserts">I</span>
68
- <span class="badge badge-update text-xs" title="Updates">U</span>
69
- <span class="badge badge-delete text-xs" title="Deletes">D</span>
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
- // Fallback initialization with better error handling
61
+ // Improved initialization with safety checks
60
62
  document.addEventListener('DOMContentLoaded', function() {
61
- // Small delay to ensure Alpine is available
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
- }, 100);
73
+ }, 150);
72
74
 
73
- // Plugin verification
75
+ // Plugin verification with safety checks
74
76
  setTimeout(() => {
75
- if (window.Alpine && !window.Alpine.directive('collapse')) {
76
- console.warn('⚠️ Alpine.js Collapse plugin may not be properly loaded');
77
- } else if (window.Alpine && window.Alpine.directive('collapse')) {
78
- console.log(' Alpine.js Collapse plugin verified');
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
- }, 200);
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
- // Initialize DBWatcher if available
215
- if (window.DBWatcher) {
216
- // Ensure BaseComponent is available
217
- if (!window.DBWatcher.BaseComponent && typeof DBWatcher.BaseComponent !== 'function') {
218
- window.DBWatcher.BaseComponent = function(config = {}) {
219
- return {
220
- init() {
221
- if (this.componentInit) {
222
- try {
223
- this.componentInit();
224
- } catch (error) {
225
- console.error("Error during component initialization:", error);
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
- console.log('✅ DBWatcher initialized (fallback)');
252
- }
300
+ console.log('✅ DBWatcher initialized (fallback)');
301
+ }
253
302
 
254
- // Verify Alpine.js plugins
255
- if (window.Alpine && window.Alpine.directive && window.Alpine.directive('collapse')) {
256
- console.log('✅ Alpine.js Collapse plugin verified');
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 clear all action
6
+ # Dashboard actions
7
7
  delete :clear_all, to: "dashboard#clear_all"
8
8
 
9
- resources :sessions do
10
- member do
11
- get :changes
12
- get :summary
13
- get :diagrams
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 :changes_data
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 = true
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 changes data
6
+ # Service for handling filtered tables data
7
7
  #
8
- # Provides changes data for the sessions changes view and API endpoints
8
+ # Provides tables data for the sessions tables view and API endpoints
9
9
  # with filtering and caching support.
10
- class ChangesDataService < BaseApiService
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 changes data for session #{session.id}")
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
- build_changes_response
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 build_changes_response
36
+ def build_tables_response
37
37
  {
38
38
  tables_summary: build_filtered_summary,
39
39
  filters: filter_params || {},
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../logging"
4
+
3
5
  module Dbwatcher
4
6
  module Services
5
7
  # Base class for all service objects