dbwatcher 0.1.5 → 1.1.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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -210
  3. data/app/assets/config/dbwatcher_manifest.js +15 -0
  4. data/app/assets/javascripts/dbwatcher/alpine_registrations.js +39 -0
  5. data/app/assets/javascripts/dbwatcher/auto_init.js +23 -0
  6. data/app/assets/javascripts/dbwatcher/components/base.js +141 -0
  7. data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +1008 -0
  8. data/app/assets/javascripts/dbwatcher/components/diagrams.js +449 -0
  9. data/app/assets/javascripts/dbwatcher/components/summary.js +234 -0
  10. data/app/assets/javascripts/dbwatcher/core/alpine_store.js +138 -0
  11. data/app/assets/javascripts/dbwatcher/core/api_client.js +162 -0
  12. data/app/assets/javascripts/dbwatcher/core/component_loader.js +70 -0
  13. data/app/assets/javascripts/dbwatcher/core/component_registry.js +94 -0
  14. data/app/assets/javascripts/dbwatcher/dbwatcher.js +120 -0
  15. data/app/assets/javascripts/dbwatcher/services/mermaid.js +315 -0
  16. data/app/assets/javascripts/dbwatcher/services/mermaid_service.js +199 -0
  17. data/app/assets/javascripts/dbwatcher/vendor/date-fns-browser.js +99 -0
  18. data/app/assets/javascripts/dbwatcher/vendor/lodash.min.js +140 -0
  19. data/app/assets/javascripts/dbwatcher/vendor/tabulator.min.js +3 -0
  20. data/app/assets/stylesheets/dbwatcher/application.css +423 -0
  21. data/app/assets/stylesheets/dbwatcher/application.scss +15 -0
  22. data/app/assets/stylesheets/dbwatcher/components/_badges.scss +38 -0
  23. data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +162 -0
  24. data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +51 -0
  25. data/app/assets/stylesheets/dbwatcher/components/_forms.scss +27 -0
  26. data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +55 -0
  27. data/app/assets/stylesheets/dbwatcher/core/_base.scss +34 -0
  28. data/app/assets/stylesheets/dbwatcher/core/_variables.scss +47 -0
  29. data/app/assets/stylesheets/dbwatcher/vendor/tabulator.min.css +2 -0
  30. data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +64 -0
  31. data/app/controllers/dbwatcher/base_controller.rb +101 -0
  32. data/app/controllers/dbwatcher/dashboard_controller.rb +20 -0
  33. data/app/controllers/dbwatcher/queries_controller.rb +24 -0
  34. data/app/controllers/dbwatcher/sessions_controller.rb +30 -20
  35. data/app/controllers/dbwatcher/tables_controller.rb +38 -0
  36. data/app/helpers/dbwatcher/application_helper.rb +103 -0
  37. data/app/helpers/dbwatcher/component_helper.rb +29 -0
  38. data/app/helpers/dbwatcher/diagram_helper.rb +110 -0
  39. data/app/helpers/dbwatcher/formatting_helper.rb +108 -0
  40. data/app/helpers/dbwatcher/session_helper.rb +28 -0
  41. data/app/views/dbwatcher/dashboard/index.html.erb +177 -0
  42. data/app/views/dbwatcher/queries/index.html.erb +240 -0
  43. data/app/views/dbwatcher/sessions/_changes_tab.html.erb +265 -0
  44. data/app/views/dbwatcher/sessions/_diagrams_tab.html.erb +166 -0
  45. data/app/views/dbwatcher/sessions/_session_header.html.erb +11 -0
  46. data/app/views/dbwatcher/sessions/_summary_tab.html.erb +88 -0
  47. data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +12 -0
  48. data/app/views/dbwatcher/sessions/changes.html.erb +21 -0
  49. data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +44 -0
  50. data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +96 -0
  51. data/app/views/dbwatcher/sessions/diagrams.html.erb +21 -0
  52. data/app/views/dbwatcher/sessions/index.html.erb +124 -27
  53. data/app/views/dbwatcher/sessions/shared/_layout.html.erb +8 -0
  54. data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +35 -0
  55. data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +25 -0
  56. data/app/views/dbwatcher/sessions/show.html.erb +3 -149
  57. data/app/views/dbwatcher/sessions/summary.html.erb +21 -0
  58. data/app/views/dbwatcher/shared/_badge.html.erb +4 -0
  59. data/app/views/dbwatcher/shared/_data_table.html.erb +20 -0
  60. data/app/views/dbwatcher/shared/_header.html.erb +7 -0
  61. data/app/views/dbwatcher/shared/_page_layout.html.erb +20 -0
  62. data/app/views/dbwatcher/shared/_section_panel.html.erb +9 -0
  63. data/app/views/dbwatcher/shared/_stats_card.html.erb +11 -0
  64. data/app/views/dbwatcher/shared/_tab_bar.html.erb +6 -0
  65. data/app/views/dbwatcher/tables/changes.html.erb +225 -0
  66. data/app/views/dbwatcher/tables/index.html.erb +123 -0
  67. data/app/views/dbwatcher/tables/show.html.erb +86 -0
  68. data/app/views/layouts/dbwatcher/application.html.erb +252 -25
  69. data/bin/compile_scss +49 -0
  70. data/config/routes.rb +43 -3
  71. data/lib/dbwatcher/configuration.rb +103 -1
  72. data/lib/dbwatcher/engine.rb +28 -13
  73. data/lib/dbwatcher/logging.rb +72 -0
  74. data/lib/dbwatcher/services/analyzers/session_data_processor.rb +98 -0
  75. data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +202 -0
  76. data/lib/dbwatcher/services/api/base_api_service.rb +100 -0
  77. data/lib/dbwatcher/services/api/changes_data_service.rb +112 -0
  78. data/lib/dbwatcher/services/api/diagram_data_service.rb +145 -0
  79. data/lib/dbwatcher/services/api/summary_data_service.rb +158 -0
  80. data/lib/dbwatcher/services/base_service.rb +64 -0
  81. data/lib/dbwatcher/services/dashboard_data_aggregator.rb +121 -0
  82. data/lib/dbwatcher/services/diagram_analyzers/base_analyzer.rb +162 -0
  83. data/lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb +354 -0
  84. data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +502 -0
  85. data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +564 -0
  86. data/lib/dbwatcher/services/diagram_data/attribute.rb +154 -0
  87. data/lib/dbwatcher/services/diagram_data/dataset.rb +278 -0
  88. data/lib/dbwatcher/services/diagram_data/entity.rb +180 -0
  89. data/lib/dbwatcher/services/diagram_data/relationship.rb +188 -0
  90. data/lib/dbwatcher/services/diagram_data/relationship_params.rb +55 -0
  91. data/lib/dbwatcher/services/diagram_data.rb +65 -0
  92. data/lib/dbwatcher/services/diagram_error_handler.rb +239 -0
  93. data/lib/dbwatcher/services/diagram_generator.rb +154 -0
  94. data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +149 -0
  95. data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +49 -0
  96. data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +52 -0
  97. data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +52 -0
  98. data/lib/dbwatcher/services/diagram_system.rb +69 -0
  99. data/lib/dbwatcher/services/diagram_type_registry.rb +164 -0
  100. data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +127 -0
  101. data/lib/dbwatcher/services/mermaid_syntax/cardinality_mapper.rb +90 -0
  102. data/lib/dbwatcher/services/mermaid_syntax/class_diagram_builder.rb +136 -0
  103. data/lib/dbwatcher/services/mermaid_syntax/class_diagram_helper.rb +46 -0
  104. data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +116 -0
  105. data/lib/dbwatcher/services/mermaid_syntax/flowchart_builder.rb +109 -0
  106. data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +102 -0
  107. data/lib/dbwatcher/services/mermaid_syntax_builder.rb +155 -0
  108. data/lib/dbwatcher/services/query_filter_processor.rb +114 -0
  109. data/lib/dbwatcher/services/table_statistics_collector.rb +119 -0
  110. data/lib/dbwatcher/sql_logger.rb +107 -0
  111. data/lib/dbwatcher/storage/api/base_api.rb +134 -0
  112. data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +59 -0
  113. data/lib/dbwatcher/storage/api/query_api.rb +95 -0
  114. data/lib/dbwatcher/storage/api/session_api.rb +181 -0
  115. data/lib/dbwatcher/storage/api/table_api.rb +86 -0
  116. data/lib/dbwatcher/storage/base_storage.rb +120 -0
  117. data/lib/dbwatcher/storage/change_processor.rb +65 -0
  118. data/lib/dbwatcher/storage/concerns/data_normalizer.rb +134 -0
  119. data/lib/dbwatcher/storage/concerns/error_handler.rb +75 -0
  120. data/lib/dbwatcher/storage/concerns/timestampable.rb +74 -0
  121. data/lib/dbwatcher/storage/concerns/validatable.rb +117 -0
  122. data/lib/dbwatcher/storage/date_helper.rb +21 -0
  123. data/lib/dbwatcher/storage/errors.rb +86 -0
  124. data/lib/dbwatcher/storage/file_manager.rb +122 -0
  125. data/lib/dbwatcher/storage/null_session.rb +39 -0
  126. data/lib/dbwatcher/storage/query_storage.rb +338 -0
  127. data/lib/dbwatcher/storage/query_validator.rb +24 -0
  128. data/lib/dbwatcher/storage/session.rb +58 -0
  129. data/lib/dbwatcher/storage/session_operations.rb +37 -0
  130. data/lib/dbwatcher/storage/session_query.rb +71 -0
  131. data/lib/dbwatcher/storage/session_storage.rb +322 -0
  132. data/lib/dbwatcher/storage/table_storage.rb +237 -0
  133. data/lib/dbwatcher/storage.rb +112 -85
  134. data/lib/dbwatcher/tracker.rb +4 -55
  135. data/lib/dbwatcher/version.rb +1 -1
  136. data/lib/dbwatcher.rb +70 -3
  137. metadata +140 -2
@@ -1,34 +1,261 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
- <head>
4
- <title>DB Watcher</title>
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <%= csrf_meta_tags %>
7
- <script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- </head>
10
- <body class="bg-gray-50">
11
- <div class="min-h-screen">
12
- <nav class="bg-white shadow">
13
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
14
- <div class="flex justify-between h-16">
15
- <div class="flex items-center">
16
- <h1 class="text-xl font-semibold">🔍 DB Watcher</h1>
3
+ <head>
4
+ <title>DB Watcher</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+
8
+ <!-- Alpine.js and Plugins - ensure plugins load BEFORE Alpine.js core -->
9
+ <script defer src="https://unpkg.com/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
10
+ <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
11
+
12
+ <!-- Mermaid.js for database diagrams -->
13
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js" crossorigin="anonymous"></script>
14
+
15
+ <!-- SVG Pan Zoom Library -->
16
+ <script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script>
17
+
18
+ <!-- Tabulator.js for table library -->
19
+ <%= javascript_include_tag "dbwatcher/vendor/tabulator.min" %>
20
+ <%= stylesheet_link_tag "dbwatcher/vendor/tabulator.min", media: "all" %>
21
+
22
+ <!-- DBWatcher Core Architecture -->
23
+ <%= javascript_include_tag "dbwatcher/vendor/lodash.min" %>
24
+ <%= javascript_include_tag "dbwatcher/vendor/date-fns-browser" %>
25
+ <%= javascript_include_tag "dbwatcher/dbwatcher" %>
26
+ <%= javascript_include_tag "dbwatcher/core/component_registry" %>
27
+ <%= javascript_include_tag "dbwatcher/core/component_loader" %>
28
+ <%= javascript_include_tag "dbwatcher/components/base" %>
29
+ <%= javascript_include_tag "dbwatcher/components/changes_table_hybrid" %>
30
+ <%= javascript_include_tag "dbwatcher/components/diagrams" %>
31
+ <%= javascript_include_tag "dbwatcher/components/summary" %>
32
+
33
+ <!-- DBWatcher Services -->
34
+ <%= javascript_include_tag "dbwatcher/core/alpine_store" %>
35
+ <%= javascript_include_tag "dbwatcher/core/api_client" %>
36
+ <%= javascript_include_tag "dbwatcher/services/mermaid" %>
37
+
38
+ <!-- Alpine.js Component Registrations -->
39
+ <%= javascript_include_tag "dbwatcher/alpine_registrations" %>
40
+
41
+ <!-- Auto-initialization -->
42
+ <%= javascript_include_tag "dbwatcher/auto_init" %>
43
+
44
+ <script>
45
+ // Initialize DBWatcher before Alpine.js starts
46
+ document.addEventListener('alpine:init', () => {
47
+ try {
48
+ if (window.DBWatcher) {
49
+ DBWatcher.init();
50
+ console.log('✅ DBWatcher initialized with optimized architecture');
51
+ } else {
52
+ console.error('❌ DBWatcher not loaded');
53
+ }
54
+ } catch (error) {
55
+ console.error('❌ Error during DBWatcher initialization:', error);
56
+ }
57
+ });
58
+
59
+ // Fallback initialization with better error handling
60
+ document.addEventListener('DOMContentLoaded', function() {
61
+ // Small delay to ensure Alpine is available
62
+ setTimeout(() => {
63
+ try {
64
+ if (window.DBWatcher && !DBWatcher.initialized) {
65
+ DBWatcher.init();
66
+ console.log('✅ DBWatcher initialized (fallback)');
67
+ }
68
+ } catch (error) {
69
+ console.error('❌ Error during DBWatcher fallback initialization:', error);
70
+ }
71
+ }, 100);
72
+
73
+ // Plugin verification
74
+ 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');
79
+ }
80
+ }, 200);
81
+ });
82
+ </script>
83
+
84
+ <!-- Tailwind CSS -->
85
+ <script src="https://cdn.tailwindcss.com"></script>
86
+
87
+ <!-- DBWatcher Styles -->
88
+ <%= stylesheet_link_tag 'dbwatcher/application.css', 'data-turbolinks-track': 'reload' %>
89
+
90
+ <script>
91
+ tailwind.config = {
92
+ theme: {
93
+ extend: {
94
+ colors: {
95
+ 'navy-dark': '#00285D',
96
+ 'blue-light': '#96C1E7',
97
+ 'blue-medium': '#6CADDF',
98
+ 'gold-dark': '#D4A11E',
99
+ 'gold-light': '#FFC758',
100
+ }
101
+ }
102
+ }
103
+ }
104
+ </script>
105
+ </head>
106
+
107
+ <body class="bg-gray-50 h-screen overflow-hidden text-gray-800">
108
+ <div class="flex h-full" x-data="{ sidebarWidth: 200, sidebarCollapsed: false }">
109
+ <!-- Compact Sidebar -->
110
+ <aside class="bg-gray-900 text-gray-300 flex-shrink-0 transition-all duration-200"
111
+ :style="{ width: sidebarCollapsed ? '48px' : sidebarWidth + 'px' }">
112
+ <div class="flex flex-col h-full">
113
+ <!-- Logo -->
114
+ <div class="h-10 flex items-center justify-between px-3 border-b border-gray-700">
115
+ <div class="flex items-center gap-2" x-show="!sidebarCollapsed">
116
+ <svg class="w-5 h-5 text-gold-light" fill="none" stroke="currentColor" viewBox="0 0 24 24">
117
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
118
+ </svg>
119
+ <span class="text-sm font-medium text-white">DB Watcher</span>
120
+ </div>
121
+ <button @click="sidebarCollapsed = !sidebarCollapsed"
122
+ class="p-1 hover:bg-gray-800 rounded text-gray-400">
123
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
124
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
125
+ :d="sidebarCollapsed ? 'M13 5l7 7-7 7M5 5l7 7-7 7' : 'M11 19l-7-7 7-7m8 14l-7-7 7-7'"/>
126
+ </svg>
127
+ </button>
17
128
  </div>
18
- <div class="flex items-center space-x-4">
19
- <%= link_to "Sessions", sessions_index_path, class: "text-gray-700 hover:text-gray-900" %>
20
- <%= button_to "Reset All Sessions", destroy_all_sessions_path,
129
+
130
+ <!-- Navigation -->
131
+ <nav class="flex-1 py-2 overflow-y-auto">
132
+ <%= link_to root_path, class: "sidebar-item #{current_page?(root_path) ? 'active' : ''}" do %>
133
+ <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
134
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
135
+ 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"/>
136
+ </svg>
137
+ <span x-show="!sidebarCollapsed">Dashboard</span>
138
+ <% end %>
139
+
140
+ <%= link_to sessions_path, class: "sidebar-item #{current_page?(sessions_path) ? 'active' : ''}" do %>
141
+ <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
142
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
143
+ d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
144
+ </svg>
145
+ <span x-show="!sidebarCollapsed">Sessions</span>
146
+ <% end %>
147
+
148
+ <%= link_to tables_path, class: "sidebar-item #{current_page?(tables_path) ? 'active' : ''}" do %>
149
+ <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
150
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
151
+ d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
152
+ </svg>
153
+ <span x-show="!sidebarCollapsed">Tables</span>
154
+ <% end %>
155
+
156
+ <%= link_to queries_path, class: "sidebar-item #{current_page?(queries_path) ? 'active' : ''}" do %>
157
+ <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
158
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
159
+ d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
160
+ </svg>
161
+ <span x-show="!sidebarCollapsed">SQL Logs</span>
162
+ <% end %>
163
+ </nav>
164
+
165
+ <!-- Actions -->
166
+ <div class="p-2 border-t border-gray-700">
167
+ <!-- Clear All Action -->
168
+ <%= button_to clear_all_path,
21
169
  method: :delete,
22
- data: { confirm: "Are you sure? This will delete all tracking data." },
23
- class: "bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600" %>
170
+ data: { confirm: "Clear all data?" },
171
+ class: "sidebar-item w-full justify-center bg-red-900 hover:bg-red-800 text-red-200" do %>
172
+ <svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
173
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
174
+ d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
175
+ </svg>
176
+ <span x-show="!sidebarCollapsed" class="text-xs">Clear All</span>
177
+ <% end %>
24
178
  </div>
25
179
  </div>
180
+ </aside>
181
+
182
+ <!-- Splitter -->
183
+ <div class="splitter" x-show="!sidebarCollapsed"
184
+ @mousedown="startResize($event)"
185
+ x-data="{
186
+ startResize(e) {
187
+ const startX = e.pageX;
188
+ const startWidth = sidebarWidth;
189
+
190
+ const doDrag = (e) => {
191
+ sidebarWidth = Math.max(150, Math.min(400, startWidth + e.pageX - startX));
192
+ };
193
+
194
+ const stopDrag = () => {
195
+ document.removeEventListener('mousemove', doDrag);
196
+ document.removeEventListener('mouseup', stopDrag);
197
+ };
198
+
199
+ document.addEventListener('mousemove', doDrag);
200
+ document.addEventListener('mouseup', stopDrag);
201
+ }
202
+ }">
26
203
  </div>
27
- </nav>
28
204
 
29
- <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
30
- <%= yield %>
31
- </main>
32
- </div>
33
- </body>
205
+ <!-- Main Content -->
206
+ <main class="flex-1 overflow-hidden bg-white">
207
+ <%= yield %>
208
+ </main>
209
+ </div>
210
+
211
+ <!-- Initialize DBWatcher system -->
212
+ <script>
213
+ 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);
226
+ }
227
+ }
228
+ }
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
+ }
248
+ }
249
+ }
250
+
251
+ console.log('✅ DBWatcher initialized (fallback)');
252
+ }
253
+
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
+ }
258
+ });
259
+ </script>
260
+ </body>
34
261
  </html>
data/bin/compile_scss ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Script to compile SCSS to CSS for DBWatcher
5
+
6
+ require 'rubygems'
7
+ require 'bundler/setup'
8
+ require 'sassc'
9
+ require 'fileutils'
10
+
11
+ # Get the root directory
12
+ root_dir = File.expand_path('..', __dir__)
13
+
14
+ # Path to the main SCSS file
15
+ scss_dir = File.join(root_dir, 'app', 'assets', 'stylesheets', 'dbwatcher')
16
+ scss_file = File.join(scss_dir, 'application.scss')
17
+ css_file = File.join(scss_dir, 'application.css')
18
+
19
+ puts "Compiling DBWatcher SCSS files to CSS..."
20
+ puts "SCSS file: #{scss_file}"
21
+ puts "CSS file: #{css_file}"
22
+
23
+ # Read the SCSS content
24
+ scss_content = File.read(scss_file)
25
+
26
+ # Create a list of import paths
27
+ load_paths = [scss_dir]
28
+
29
+ # Compile SCSS to CSS
30
+ begin
31
+ css_content = SassC::Engine.new(
32
+ scss_content,
33
+ style: :expanded,
34
+ load_paths: load_paths,
35
+ syntax: :scss
36
+ ).render
37
+
38
+ # Add a header comment
39
+ css_content = "/**\n * DBWatcher Application Styles\n * Compiled CSS for all components\n * Generated at #{Time.now}\n */\n\n" + css_content
40
+
41
+ # Write to file
42
+ File.write(css_file, css_content)
43
+
44
+ puts "SCSS compilation complete! CSS written to #{css_file}"
45
+ rescue => e
46
+ puts "Error compiling SCSS: #{e.message}"
47
+ puts e.backtrace.join("\n")
48
+ exit 1
49
+ end
data/config/routes.rb CHANGED
@@ -1,10 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Dbwatcher::Engine.routes.draw do
4
- resources :sessions, only: %i[index show] do
4
+ root to: "dashboard#index"
5
+
6
+ # Dashboard clear all action
7
+ delete :clear_all, to: "dashboard#clear_all"
8
+
9
+ resources :sessions do
10
+ member do
11
+ get :changes
12
+ get :summary
13
+ get :diagrams
14
+ end
15
+
16
+ collection do
17
+ delete :clear
18
+ end
19
+ end
20
+
21
+ # API namespace for JSON-only endpoints
22
+ namespace :api do
23
+ namespace :v1 do
24
+ resources :sessions, only: [] do
25
+ member do
26
+ get :changes_data
27
+ get :summary_data
28
+ get :diagram_data
29
+ end
30
+
31
+ collection do
32
+ get :diagram_types
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ resources :tables, only: %i[index show] do
39
+ member do
40
+ get :changes
41
+ end
42
+ end
43
+
44
+ resources :queries, only: [:index] do
5
45
  collection do
6
- delete :destroy_all
46
+ get :filter
47
+ delete :clear
7
48
  end
8
49
  end
9
- root to: "sessions#index"
10
50
  end
@@ -1,18 +1,78 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dbwatcher
4
+ # Configuration class for DBWatcher
5
+ #
6
+ # This class manages all configuration options for DBWatcher, including
7
+ # storage, tracking, and diagram visualization settings.
4
8
  class Configuration
9
+ # Storage configuration
5
10
  attr_accessor :storage_path, :enabled, :max_sessions, :auto_clean_after_days
6
11
 
12
+ # Query tracking configuration
13
+ attr_accessor :track_queries, :slow_query_threshold, :max_query_logs_per_day
14
+
15
+ # Routing configuration
16
+ attr_accessor :mount_path
17
+
18
+ # Diagram configuration
19
+ attr_accessor :diagram_show_attributes, :diagram_show_methods, :diagram_show_cardinality,
20
+ :diagram_max_attributes, :diagram_attribute_types, :diagram_relationship_labels,
21
+ :diagram_preserve_table_case, :diagram_direction, :diagram_cardinality_format,
22
+ :diagram_show_attribute_count, :diagram_show_method_count
23
+
24
+ # Initialize with default values
7
25
  def initialize
26
+ # Storage configuration defaults
8
27
  @storage_path = default_storage_path
9
28
  @enabled = true
10
- @max_sessions = 100
29
+ @max_sessions = 50
11
30
  @auto_clean_after_days = 7
31
+
32
+ # Query tracking configuration defaults
33
+ @track_queries = true
34
+ @slow_query_threshold = 200 # milliseconds
35
+ @max_query_logs_per_day = 1000
36
+
37
+ # Routing configuration defaults
38
+ @mount_path = "/dbwatcher"
39
+
40
+ # Initialize diagram configuration with defaults
41
+ initialize_diagram_config
42
+ end
43
+
44
+ # Initialize diagram configuration with default values
45
+ def initialize_diagram_config
46
+ @diagram_show_attributes = true
47
+ @diagram_show_methods = false # Hide methods by default
48
+ @diagram_show_cardinality = true
49
+ @diagram_max_attributes = 10
50
+ @diagram_attribute_types = true # Changed from array to boolean
51
+ @diagram_relationship_labels = true # Changed from symbol to boolean
52
+ @diagram_preserve_table_case = false # Changed from true to false
53
+ @diagram_direction = "LR" # Left to right by default
54
+ @diagram_cardinality_format = :simple # Use simpler 1:N format
55
+ @diagram_show_attribute_count = true
56
+ @diagram_show_method_count = true
57
+ end
58
+
59
+ # Validate configuration
60
+ #
61
+ # @return [Boolean] true if configuration is valid
62
+ def valid?
63
+ validate_storage_path
64
+ validate_max_sessions
65
+ validate_auto_clean_after_days
66
+ validate_slow_query_threshold
67
+ validate_max_query_logs_per_day
68
+ true
12
69
  end
13
70
 
14
71
  private
15
72
 
73
+ # Default storage path based on Rails or current directory
74
+ #
75
+ # @return [String] default storage path
16
76
  def default_storage_path
17
77
  if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
18
78
  Rails.root.join("tmp", "dbwatcher").to_s
@@ -20,5 +80,47 @@ module Dbwatcher
20
80
  File.join(Dir.pwd, "tmp", "dbwatcher")
21
81
  end
22
82
  end
83
+
84
+ # Validate storage path
85
+ def validate_storage_path
86
+ return if storage_path.nil? || Dir.exist?(storage_path)
87
+
88
+ begin
89
+ FileUtils.mkdir_p(storage_path)
90
+ rescue StandardError => e
91
+ raise ConfigurationError, "Failed to create storage path: #{e.message}"
92
+ end
93
+ end
94
+
95
+ # Validate max sessions
96
+ def validate_max_sessions
97
+ return if max_sessions.is_a?(Integer) && max_sessions.positive?
98
+
99
+ raise ConfigurationError, "max_sessions must be a positive integer"
100
+ end
101
+
102
+ # Validate auto clean after days
103
+ def validate_auto_clean_after_days
104
+ return if auto_clean_after_days.is_a?(Integer) && auto_clean_after_days.positive?
105
+
106
+ raise ConfigurationError, "auto_clean_after_days must be a positive integer"
107
+ end
108
+
109
+ # Validate slow query threshold
110
+ def validate_slow_query_threshold
111
+ return if slow_query_threshold.is_a?(Integer) && slow_query_threshold.positive?
112
+
113
+ raise ConfigurationError, "slow_query_threshold must be a positive integer"
114
+ end
115
+
116
+ # Validate max query logs per day
117
+ def validate_max_query_logs_per_day
118
+ return if max_query_logs_per_day.is_a?(Integer) && max_query_logs_per_day.positive?
119
+
120
+ raise ConfigurationError, "max_query_logs_per_day must be a positive integer"
121
+ end
23
122
  end
123
+
124
+ # Configuration error class
125
+ class ConfigurationError < StandardError; end
24
126
  end
@@ -4,27 +4,42 @@ module Dbwatcher
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace Dbwatcher
6
6
 
7
+ # Configure autoload paths
8
+ config.autoload_paths += %W[
9
+ #{root}/lib
10
+ ]
11
+
12
+ # Serve static assets
13
+ initializer "dbwatcher.assets", before: :add_to_load_path do |app|
14
+ # Define asset paths
15
+ app.config.assets.paths << root.join("app", "assets", "stylesheets")
16
+ app.config.assets.paths << root.join("app", "assets", "javascripts")
17
+ app.config.assets.paths << root.join("app", "assets", "config")
18
+
19
+ # Load engine's manifest file
20
+ app.config.assets.precompile << "dbwatcher_manifest.js"
21
+ end
22
+
7
23
  initializer "dbwatcher.setup" do |app|
8
- # Auto-include in all models
9
- ActiveSupport.on_load(:active_record) do
10
- include Dbwatcher::ModelExtension
11
- end
24
+ if Dbwatcher.configuration.enabled
25
+ # Auto-include in all models
26
+ ActiveSupport.on_load(:active_record) do
27
+ include Dbwatcher::ModelExtension
28
+ end
12
29
 
13
- # Add middleware
14
- app.middleware.use Dbwatcher::Middleware
30
+ # Add middleware
31
+ app.middleware.use Dbwatcher::Middleware
32
+
33
+ # Setup SQL logging if enabled
34
+ Dbwatcher::SqlLogger.instance if Dbwatcher.configuration.track_queries
35
+ end
15
36
  end
16
37
 
17
38
  # Mount the engine routes automatically
18
39
  initializer "dbwatcher.routes", after: :add_routing_paths do |app|
19
- app.routes.append do
40
+ app.routes.prepend do
20
41
  mount Dbwatcher::Engine => "/dbwatcher", as: :dbwatcher
21
42
  end
22
43
  end
23
-
24
- # Serve static assets
25
- initializer "dbwatcher.assets" do |app|
26
- app.config.assets.paths << root.join("app", "assets", "stylesheets")
27
- app.config.assets.paths << root.join("app", "assets", "javascripts")
28
- end
29
44
  end
30
45
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ # Logging module for DBWatcher components
5
+ # Provides consistent logging interface across all services and components
6
+ module Logging
7
+ extend ActiveSupport::Concern if defined?(ActiveSupport)
8
+
9
+ # Log an informational message with optional context
10
+ # @param message [String] the log message
11
+ # @param context [Hash] additional context data
12
+ def log_info(message, context = {})
13
+ log_with_level(:info, message, context)
14
+ end
15
+
16
+ # Log a debug message with optional context
17
+ # @param message [String] the log message
18
+ # @param context [Hash] additional context data
19
+ def log_debug(message, context = {})
20
+ log_with_level(:debug, message, context)
21
+ end
22
+
23
+ # Log a warning message with optional context
24
+ # @param message [String] the log message
25
+ # @param context [Hash] additional context data
26
+ def log_warn(message, context = {})
27
+ log_with_level(:warn, message, context)
28
+ end
29
+
30
+ # Log an error message with optional context
31
+ # @param message [String] the log message
32
+ # @param context [Hash] additional context data
33
+ def log_error(message, context = {})
34
+ log_with_level(:error, message, context)
35
+ end
36
+
37
+ private
38
+
39
+ def log_with_level(level, message, context)
40
+ logger = rails_logger || fallback_logger
41
+ formatted_message = format_log_message(message, context)
42
+ logger.public_send(level, formatted_message)
43
+ end
44
+
45
+ def format_log_message(message, context)
46
+ base_message = "[DBWatcher:#{component_name}] #{message}"
47
+ return base_message if context.empty?
48
+
49
+ context_string = context.map { |k, v| "#{k}=#{v}" }.join(" ")
50
+ "#{base_message} | #{context_string}"
51
+ end
52
+
53
+ def component_name
54
+ self.class.name.split("::").last
55
+ end
56
+
57
+ def rails_logger
58
+ return nil unless defined?(Rails)
59
+
60
+ Rails.logger
61
+ end
62
+
63
+ def fallback_logger
64
+ @fallback_logger ||= Logger.new($stdout).tap do |logger|
65
+ logger.level = Logger::INFO
66
+ logger.formatter = proc do |severity, datetime, _progname, msg|
67
+ "#{datetime.strftime("%Y-%m-%d %H:%M:%S")} [#{severity}] #{msg}\n"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end