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.
- checksums.yaml +4 -4
- data/README.md +81 -210
- data/app/assets/config/dbwatcher_manifest.js +15 -0
- data/app/assets/javascripts/dbwatcher/alpine_registrations.js +39 -0
- data/app/assets/javascripts/dbwatcher/auto_init.js +23 -0
- data/app/assets/javascripts/dbwatcher/components/base.js +141 -0
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +1008 -0
- data/app/assets/javascripts/dbwatcher/components/diagrams.js +449 -0
- data/app/assets/javascripts/dbwatcher/components/summary.js +234 -0
- data/app/assets/javascripts/dbwatcher/core/alpine_store.js +138 -0
- data/app/assets/javascripts/dbwatcher/core/api_client.js +162 -0
- data/app/assets/javascripts/dbwatcher/core/component_loader.js +70 -0
- data/app/assets/javascripts/dbwatcher/core/component_registry.js +94 -0
- data/app/assets/javascripts/dbwatcher/dbwatcher.js +120 -0
- data/app/assets/javascripts/dbwatcher/services/mermaid.js +315 -0
- data/app/assets/javascripts/dbwatcher/services/mermaid_service.js +199 -0
- data/app/assets/javascripts/dbwatcher/vendor/date-fns-browser.js +99 -0
- data/app/assets/javascripts/dbwatcher/vendor/lodash.min.js +140 -0
- data/app/assets/javascripts/dbwatcher/vendor/tabulator.min.js +3 -0
- data/app/assets/stylesheets/dbwatcher/application.css +423 -0
- data/app/assets/stylesheets/dbwatcher/application.scss +15 -0
- data/app/assets/stylesheets/dbwatcher/components/_badges.scss +38 -0
- data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +162 -0
- data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +51 -0
- data/app/assets/stylesheets/dbwatcher/components/_forms.scss +27 -0
- data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +55 -0
- data/app/assets/stylesheets/dbwatcher/core/_base.scss +34 -0
- data/app/assets/stylesheets/dbwatcher/core/_variables.scss +47 -0
- data/app/assets/stylesheets/dbwatcher/vendor/tabulator.min.css +2 -0
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +64 -0
- data/app/controllers/dbwatcher/base_controller.rb +101 -0
- data/app/controllers/dbwatcher/dashboard_controller.rb +20 -0
- data/app/controllers/dbwatcher/queries_controller.rb +24 -0
- data/app/controllers/dbwatcher/sessions_controller.rb +30 -20
- data/app/controllers/dbwatcher/tables_controller.rb +38 -0
- data/app/helpers/dbwatcher/application_helper.rb +103 -0
- data/app/helpers/dbwatcher/component_helper.rb +29 -0
- data/app/helpers/dbwatcher/diagram_helper.rb +110 -0
- data/app/helpers/dbwatcher/formatting_helper.rb +108 -0
- data/app/helpers/dbwatcher/session_helper.rb +28 -0
- data/app/views/dbwatcher/dashboard/index.html.erb +177 -0
- data/app/views/dbwatcher/queries/index.html.erb +240 -0
- data/app/views/dbwatcher/sessions/_changes_tab.html.erb +265 -0
- data/app/views/dbwatcher/sessions/_diagrams_tab.html.erb +166 -0
- data/app/views/dbwatcher/sessions/_session_header.html.erb +11 -0
- data/app/views/dbwatcher/sessions/_summary_tab.html.erb +88 -0
- data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +12 -0
- data/app/views/dbwatcher/sessions/changes.html.erb +21 -0
- data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +44 -0
- data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +96 -0
- data/app/views/dbwatcher/sessions/diagrams.html.erb +21 -0
- data/app/views/dbwatcher/sessions/index.html.erb +124 -27
- data/app/views/dbwatcher/sessions/shared/_layout.html.erb +8 -0
- data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +35 -0
- data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +25 -0
- data/app/views/dbwatcher/sessions/show.html.erb +3 -149
- data/app/views/dbwatcher/sessions/summary.html.erb +21 -0
- data/app/views/dbwatcher/shared/_badge.html.erb +4 -0
- data/app/views/dbwatcher/shared/_data_table.html.erb +20 -0
- data/app/views/dbwatcher/shared/_header.html.erb +7 -0
- data/app/views/dbwatcher/shared/_page_layout.html.erb +20 -0
- data/app/views/dbwatcher/shared/_section_panel.html.erb +9 -0
- data/app/views/dbwatcher/shared/_stats_card.html.erb +11 -0
- data/app/views/dbwatcher/shared/_tab_bar.html.erb +6 -0
- data/app/views/dbwatcher/tables/changes.html.erb +225 -0
- data/app/views/dbwatcher/tables/index.html.erb +123 -0
- data/app/views/dbwatcher/tables/show.html.erb +86 -0
- data/app/views/layouts/dbwatcher/application.html.erb +252 -25
- data/bin/compile_scss +49 -0
- data/config/routes.rb +43 -3
- data/lib/dbwatcher/configuration.rb +103 -1
- data/lib/dbwatcher/engine.rb +28 -13
- data/lib/dbwatcher/logging.rb +72 -0
- data/lib/dbwatcher/services/analyzers/session_data_processor.rb +98 -0
- data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +202 -0
- data/lib/dbwatcher/services/api/base_api_service.rb +100 -0
- data/lib/dbwatcher/services/api/changes_data_service.rb +112 -0
- data/lib/dbwatcher/services/api/diagram_data_service.rb +145 -0
- data/lib/dbwatcher/services/api/summary_data_service.rb +158 -0
- data/lib/dbwatcher/services/base_service.rb +64 -0
- data/lib/dbwatcher/services/dashboard_data_aggregator.rb +121 -0
- data/lib/dbwatcher/services/diagram_analyzers/base_analyzer.rb +162 -0
- data/lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb +354 -0
- data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +502 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +564 -0
- data/lib/dbwatcher/services/diagram_data/attribute.rb +154 -0
- data/lib/dbwatcher/services/diagram_data/dataset.rb +278 -0
- data/lib/dbwatcher/services/diagram_data/entity.rb +180 -0
- data/lib/dbwatcher/services/diagram_data/relationship.rb +188 -0
- data/lib/dbwatcher/services/diagram_data/relationship_params.rb +55 -0
- data/lib/dbwatcher/services/diagram_data.rb +65 -0
- data/lib/dbwatcher/services/diagram_error_handler.rb +239 -0
- data/lib/dbwatcher/services/diagram_generator.rb +154 -0
- data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +149 -0
- data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +49 -0
- data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +52 -0
- data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +52 -0
- data/lib/dbwatcher/services/diagram_system.rb +69 -0
- data/lib/dbwatcher/services/diagram_type_registry.rb +164 -0
- data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +127 -0
- data/lib/dbwatcher/services/mermaid_syntax/cardinality_mapper.rb +90 -0
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_builder.rb +136 -0
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_helper.rb +46 -0
- data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +116 -0
- data/lib/dbwatcher/services/mermaid_syntax/flowchart_builder.rb +109 -0
- data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +102 -0
- data/lib/dbwatcher/services/mermaid_syntax_builder.rb +155 -0
- data/lib/dbwatcher/services/query_filter_processor.rb +114 -0
- data/lib/dbwatcher/services/table_statistics_collector.rb +119 -0
- data/lib/dbwatcher/sql_logger.rb +107 -0
- data/lib/dbwatcher/storage/api/base_api.rb +134 -0
- data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +59 -0
- data/lib/dbwatcher/storage/api/query_api.rb +95 -0
- data/lib/dbwatcher/storage/api/session_api.rb +181 -0
- data/lib/dbwatcher/storage/api/table_api.rb +86 -0
- data/lib/dbwatcher/storage/base_storage.rb +120 -0
- data/lib/dbwatcher/storage/change_processor.rb +65 -0
- data/lib/dbwatcher/storage/concerns/data_normalizer.rb +134 -0
- data/lib/dbwatcher/storage/concerns/error_handler.rb +75 -0
- data/lib/dbwatcher/storage/concerns/timestampable.rb +74 -0
- data/lib/dbwatcher/storage/concerns/validatable.rb +117 -0
- data/lib/dbwatcher/storage/date_helper.rb +21 -0
- data/lib/dbwatcher/storage/errors.rb +86 -0
- data/lib/dbwatcher/storage/file_manager.rb +122 -0
- data/lib/dbwatcher/storage/null_session.rb +39 -0
- data/lib/dbwatcher/storage/query_storage.rb +338 -0
- data/lib/dbwatcher/storage/query_validator.rb +24 -0
- data/lib/dbwatcher/storage/session.rb +58 -0
- data/lib/dbwatcher/storage/session_operations.rb +37 -0
- data/lib/dbwatcher/storage/session_query.rb +71 -0
- data/lib/dbwatcher/storage/session_storage.rb +322 -0
- data/lib/dbwatcher/storage/table_storage.rb +237 -0
- data/lib/dbwatcher/storage.rb +112 -85
- data/lib/dbwatcher/tracker.rb +4 -55
- data/lib/dbwatcher/version.rb +1 -1
- data/lib/dbwatcher.rb +70 -3
- metadata +140 -2
@@ -1,34 +1,261 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html>
|
3
|
-
<head>
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
<
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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: "
|
23
|
-
class: "
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
</
|
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
|
-
|
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
|
-
|
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 =
|
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
|
data/lib/dbwatcher/engine.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
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.
|
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
|