dbwatcher 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -2
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +12 -22
- data/app/assets/javascripts/dbwatcher/components/dashboard.js +325 -0
- data/app/assets/stylesheets/dbwatcher/application.css +394 -41
- data/app/assets/stylesheets/dbwatcher/application.scss +4 -0
- data/app/assets/stylesheets/dbwatcher/components/_badges.scss +68 -23
- data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +83 -26
- data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +3 -3
- data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +9 -0
- data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +248 -0
- data/app/assets/stylesheets/dbwatcher/vendor/_tabulator_overrides.scss +37 -0
- data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +180 -0
- data/app/controllers/dbwatcher/dashboard/system_info_controller.rb +64 -0
- data/app/controllers/dbwatcher/dashboard_controller.rb +17 -0
- data/app/controllers/dbwatcher/sessions_controller.rb +3 -19
- data/app/helpers/dbwatcher/application_helper.rb +43 -11
- data/app/helpers/dbwatcher/diagram_helper.rb +0 -88
- data/app/views/dbwatcher/dashboard/_layout.html.erb +27 -0
- data/app/views/dbwatcher/dashboard/_overview.html.erb +188 -0
- data/app/views/dbwatcher/dashboard/_system_info.html.erb +22 -0
- data/app/views/dbwatcher/dashboard/_system_info_content.html.erb +389 -0
- data/app/views/dbwatcher/dashboard/index.html.erb +8 -177
- data/app/views/dbwatcher/sessions/_changes.html.erb +91 -0
- data/app/views/dbwatcher/sessions/_layout.html.erb +23 -0
- data/app/views/dbwatcher/sessions/index.html.erb +107 -87
- data/app/views/dbwatcher/sessions/show.html.erb +10 -4
- data/app/views/dbwatcher/tables/index.html.erb +32 -40
- data/app/views/layouts/dbwatcher/application.html.erb +100 -48
- data/config/routes.rb +23 -6
- data/lib/dbwatcher/configuration.rb +18 -1
- data/lib/dbwatcher/services/base_service.rb +2 -0
- data/lib/dbwatcher/services/system_info/database_info_collector.rb +263 -0
- data/lib/dbwatcher/services/system_info/machine_info_collector.rb +387 -0
- data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +328 -0
- data/lib/dbwatcher/services/system_info/system_info_collector.rb +114 -0
- data/lib/dbwatcher/storage/concerns/error_handler.rb +6 -6
- data/lib/dbwatcher/storage/session.rb +5 -0
- data/lib/dbwatcher/storage/system_info_storage.rb +242 -0
- data/lib/dbwatcher/storage.rb +12 -0
- data/lib/dbwatcher/version.rb +1 -1
- data/lib/dbwatcher.rb +15 -1
- metadata +20 -15
- data/app/helpers/dbwatcher/component_helper.rb +0 -29
- data/app/views/dbwatcher/sessions/_changes_tab.html.erb +0 -265
- data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +0 -12
- data/app/views/dbwatcher/sessions/changes.html.erb +0 -21
- data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +0 -44
- data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +0 -96
- data/app/views/dbwatcher/sessions/diagrams.html.erb +0 -21
- data/app/views/dbwatcher/sessions/shared/_layout.html.erb +0 -8
- data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +0 -35
- data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +0 -25
- data/app/views/dbwatcher/sessions/summary.html.erb +0 -21
- /data/app/views/dbwatcher/sessions/{_diagrams_tab.html.erb → _diagrams.html.erb} +0 -0
- /data/app/views/dbwatcher/sessions/{_summary_tab.html.erb → _summary.html.erb} +0 -0
@@ -29,6 +29,7 @@
|
|
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" %>
|
32
33
|
|
33
34
|
<!-- DBWatcher Services -->
|
34
35
|
<%= javascript_include_tag "dbwatcher/core/alpine_store" %>
|
@@ -56,9 +57,9 @@
|
|
56
57
|
}
|
57
58
|
});
|
58
59
|
|
59
|
-
//
|
60
|
+
// Improved initialization with safety checks
|
60
61
|
document.addEventListener('DOMContentLoaded', function() {
|
61
|
-
//
|
62
|
+
// Prevent immediate DOM access conflicts
|
62
63
|
setTimeout(() => {
|
63
64
|
try {
|
64
65
|
if (window.DBWatcher && !DBWatcher.initialized) {
|
@@ -68,16 +69,22 @@
|
|
68
69
|
} catch (error) {
|
69
70
|
console.error('❌ Error during DBWatcher fallback initialization:', error);
|
70
71
|
}
|
71
|
-
},
|
72
|
+
}, 150);
|
72
73
|
|
73
|
-
// Plugin verification
|
74
|
+
// Plugin verification with safety checks
|
74
75
|
setTimeout(() => {
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
try {
|
77
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
78
|
+
if (!window.Alpine.directive('collapse')) {
|
79
|
+
console.warn('⚠️ Alpine.js Collapse plugin may not be properly loaded');
|
80
|
+
} else {
|
81
|
+
console.log('✅ Alpine.js Collapse plugin verified');
|
82
|
+
}
|
83
|
+
}
|
84
|
+
} catch (error) {
|
85
|
+
console.warn('⚠️ Alpine.js plugin verification failed:', error);
|
79
86
|
}
|
80
|
-
},
|
87
|
+
}, 300);
|
81
88
|
});
|
82
89
|
</script>
|
83
90
|
|
@@ -129,7 +136,7 @@
|
|
129
136
|
|
130
137
|
<!-- Navigation -->
|
131
138
|
<nav class="flex-1 py-2 overflow-y-auto">
|
132
|
-
<%= link_to root_path, class: "sidebar-item #{current_page?(root_path) ? 'active' : ''}" do %>
|
139
|
+
<%= link_to root_path, class: "sidebar-item #{current_page?(root_path) || (params[:tab] == 'system_info') ? 'active' : ''}" do %>
|
133
140
|
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
134
141
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
135
142
|
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 +167,8 @@
|
|
160
167
|
</svg>
|
161
168
|
<span x-show="!sidebarCollapsed">SQL Logs</span>
|
162
169
|
<% end %>
|
170
|
+
|
171
|
+
|
163
172
|
</nav>
|
164
173
|
|
165
174
|
<!-- Actions -->
|
@@ -176,6 +185,42 @@
|
|
176
185
|
<span x-show="!sidebarCollapsed" class="text-xs">Clear All</span>
|
177
186
|
<% end %>
|
178
187
|
</div>
|
188
|
+
|
189
|
+
<!-- Gem Info Section -->
|
190
|
+
<div class="mt-auto pt-4 border-t border-gray-700">
|
191
|
+
<div class="px-3 py-2">
|
192
|
+
<div class="text-xs text-gray-400 mb-2 font-medium" x-show="!sidebarCollapsed">
|
193
|
+
dbwatcher v<%= Dbwatcher::VERSION %>
|
194
|
+
</div>
|
195
|
+
<div class="flex items-center gap-2 text-xs text-gray-400" x-show="!sidebarCollapsed">
|
196
|
+
<a href="https://github.com/patrick204nqh/dbwatcher"
|
197
|
+
target="_blank"
|
198
|
+
rel="noopener noreferrer"
|
199
|
+
class="hover:text-blue-400 transition-colors duration-200 flex items-center gap-1"
|
200
|
+
title="View on GitHub">
|
201
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
|
202
|
+
<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"/>
|
203
|
+
</svg>
|
204
|
+
<span>GitHub</span>
|
205
|
+
</a>
|
206
|
+
<span class="text-gray-600">•</span>
|
207
|
+
<a href="https://rubydoc.info/gems/dbwatcher/<%= Dbwatcher::VERSION %>"
|
208
|
+
target="_blank"
|
209
|
+
rel="noopener noreferrer"
|
210
|
+
class="hover:text-red-400 transition-colors duration-200 flex items-center gap-1"
|
211
|
+
title="View Documentation">
|
212
|
+
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
|
213
|
+
<path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
|
214
|
+
</svg>
|
215
|
+
<span>Docs</span>
|
216
|
+
</a>
|
217
|
+
</div>
|
218
|
+
<!-- Collapsed state - just show version -->
|
219
|
+
<div class="text-xs text-gray-400 text-center" x-show="sidebarCollapsed" title="DBWatcher v<%= Dbwatcher::VERSION %>">
|
220
|
+
v<%= Dbwatcher::VERSION %>
|
221
|
+
</div>
|
222
|
+
</div>
|
223
|
+
</div>
|
179
224
|
</div>
|
180
225
|
</aside>
|
181
226
|
|
@@ -211,50 +256,57 @@
|
|
211
256
|
<!-- Initialize DBWatcher system -->
|
212
257
|
<script>
|
213
258
|
document.addEventListener('DOMContentLoaded', function() {
|
214
|
-
//
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
window.DBWatcher
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
259
|
+
// Add delay to prevent timing conflicts
|
260
|
+
setTimeout(() => {
|
261
|
+
try {
|
262
|
+
// Initialize DBWatcher if available
|
263
|
+
if (window.DBWatcher) {
|
264
|
+
// Ensure BaseComponent is available
|
265
|
+
if (!window.DBWatcher.BaseComponent && typeof DBWatcher.BaseComponent !== 'function') {
|
266
|
+
window.DBWatcher.BaseComponent = function(config = {}) {
|
267
|
+
return {
|
268
|
+
init() {
|
269
|
+
if (this.componentInit) {
|
270
|
+
try {
|
271
|
+
this.componentInit();
|
272
|
+
} catch (error) {
|
273
|
+
console.error("Error during component initialization:", error);
|
274
|
+
}
|
275
|
+
}
|
226
276
|
}
|
277
|
+
};
|
278
|
+
};
|
279
|
+
console.log('Added fallback BaseComponent');
|
280
|
+
}
|
281
|
+
|
282
|
+
// Register legacy components with Alpine directly if ComponentRegistry isn't available
|
283
|
+
if (!window.DBWatcher.ComponentRegistry) {
|
284
|
+
console.log('ComponentRegistry not available, using direct Alpine registration');
|
285
|
+
|
286
|
+
// Ensure Alpine is available with safety checks
|
287
|
+
if (window.Alpine && typeof window.Alpine.data === 'function') {
|
288
|
+
// Direct registration of components with Alpine
|
289
|
+
if (!window.Alpine.data('changesTable') && window.DBWatcher.components && window.DBWatcher.components.changesTable) {
|
290
|
+
window.Alpine.data('changesTable', (config = {}) => {
|
291
|
+
const component = window.DBWatcher.components.changesTable(config);
|
292
|
+
return component;
|
293
|
+
});
|
294
|
+
console.log('Registered changesTable component with Alpine');
|
227
295
|
}
|
228
296
|
}
|
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
297
|
}
|
248
|
-
}
|
249
|
-
}
|
250
298
|
|
251
|
-
|
252
|
-
|
299
|
+
console.log('✅ DBWatcher initialized (fallback)');
|
300
|
+
}
|
253
301
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
302
|
+
// Verify Alpine.js plugins with safety checks
|
303
|
+
if (window.Alpine && typeof window.Alpine.directive === 'function' && window.Alpine.directive('collapse')) {
|
304
|
+
console.log('✅ Alpine.js Collapse plugin verified');
|
305
|
+
}
|
306
|
+
} catch (error) {
|
307
|
+
console.error('❌ Error during DBWatcher system initialization:', error);
|
308
|
+
}
|
309
|
+
}, 200);
|
258
310
|
});
|
259
311
|
</script>
|
260
312
|
</body>
|
data/config/routes.rb
CHANGED
@@ -3,16 +3,20 @@
|
|
3
3
|
Dbwatcher::Engine.routes.draw do
|
4
4
|
root to: "dashboard#index"
|
5
5
|
|
6
|
-
# Dashboard
|
6
|
+
# Dashboard actions
|
7
7
|
delete :clear_all, to: "dashboard#clear_all"
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
# Dashboard system info actions
|
10
|
+
namespace :dashboard do
|
11
|
+
resources :system_info, only: [] do
|
12
|
+
collection do
|
13
|
+
post :refresh
|
14
|
+
delete :clear_cache
|
15
|
+
end
|
14
16
|
end
|
17
|
+
end
|
15
18
|
|
19
|
+
resources :sessions do
|
16
20
|
collection do
|
17
21
|
delete :clear
|
18
22
|
end
|
@@ -32,6 +36,19 @@ Dbwatcher::Engine.routes.draw do
|
|
32
36
|
get :diagram_types
|
33
37
|
end
|
34
38
|
end
|
39
|
+
|
40
|
+
# System information API routes
|
41
|
+
resources :system_info, only: [:index] do
|
42
|
+
collection do
|
43
|
+
post :refresh
|
44
|
+
get :machine
|
45
|
+
get :database
|
46
|
+
get :runtime
|
47
|
+
get :summary
|
48
|
+
delete :clear_cache
|
49
|
+
get :cache_status
|
50
|
+
end
|
51
|
+
end
|
35
52
|
end
|
36
53
|
end
|
37
54
|
|
@@ -21,6 +21,11 @@ module Dbwatcher
|
|
21
21
|
:diagram_preserve_table_case, :diagram_direction, :diagram_cardinality_format,
|
22
22
|
:diagram_show_attribute_count, :diagram_show_method_count
|
23
23
|
|
24
|
+
# System information configuration
|
25
|
+
attr_accessor :collect_system_info, :system_info_refresh_interval,
|
26
|
+
:collect_sensitive_env_vars, :system_info_cache_duration,
|
27
|
+
:system_info_include_performance_metrics
|
28
|
+
|
24
29
|
# Initialize with default values
|
25
30
|
def initialize
|
26
31
|
# Storage configuration defaults
|
@@ -30,7 +35,7 @@ module Dbwatcher
|
|
30
35
|
@auto_clean_after_days = 7
|
31
36
|
|
32
37
|
# Query tracking configuration defaults
|
33
|
-
@track_queries =
|
38
|
+
@track_queries = false
|
34
39
|
@slow_query_threshold = 200 # milliseconds
|
35
40
|
@max_query_logs_per_day = 1000
|
36
41
|
|
@@ -39,6 +44,9 @@ module Dbwatcher
|
|
39
44
|
|
40
45
|
# Initialize diagram configuration with defaults
|
41
46
|
initialize_diagram_config
|
47
|
+
|
48
|
+
# Initialize system information configuration with defaults
|
49
|
+
initialize_system_info_config
|
42
50
|
end
|
43
51
|
|
44
52
|
# Initialize diagram configuration with default values
|
@@ -56,6 +64,15 @@ module Dbwatcher
|
|
56
64
|
@diagram_show_method_count = true
|
57
65
|
end
|
58
66
|
|
67
|
+
# Initialize system information configuration with default values
|
68
|
+
def initialize_system_info_config
|
69
|
+
@collect_system_info = true
|
70
|
+
@system_info_refresh_interval = 5 * 60 # 5 minutes in seconds
|
71
|
+
@collect_sensitive_env_vars = false
|
72
|
+
@system_info_cache_duration = 60 * 60 # 1 hour in seconds
|
73
|
+
@system_info_include_performance_metrics = true
|
74
|
+
end
|
75
|
+
|
59
76
|
# Validate configuration
|
60
77
|
#
|
61
78
|
# @return [Boolean] true if configuration is valid
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../logging"
|
4
|
+
|
5
|
+
module Dbwatcher
|
6
|
+
module Services
|
7
|
+
module SystemInfo
|
8
|
+
# Database information collector service
|
9
|
+
#
|
10
|
+
# Collects database-specific information including adapter, version,
|
11
|
+
# connection pool stats, table counts, and schema information.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# info = SystemInfo::DatabaseInfoCollector.call
|
15
|
+
# puts info[:adapter]
|
16
|
+
# puts info[:version]
|
17
|
+
# puts info[:tables].count
|
18
|
+
#
|
19
|
+
# This class is necessarily complex due to the comprehensive database information
|
20
|
+
# it needs to collect across different database systems.
|
21
|
+
# rubocop:disable Metrics/ClassLength
|
22
|
+
class DatabaseInfoCollector
|
23
|
+
include Dbwatcher::Logging
|
24
|
+
|
25
|
+
# Class method to create instance and call
|
26
|
+
#
|
27
|
+
# @return [Hash] database information
|
28
|
+
def self.call
|
29
|
+
new.call
|
30
|
+
end
|
31
|
+
|
32
|
+
def call
|
33
|
+
log_info "#{self.class.name}: Collecting database information"
|
34
|
+
|
35
|
+
{
|
36
|
+
adapter: collect_adapter_info,
|
37
|
+
version: collect_database_version,
|
38
|
+
connection_pool: collect_connection_pool_info,
|
39
|
+
tables: collect_table_info,
|
40
|
+
schema: collect_schema_info,
|
41
|
+
indexes: collect_index_info,
|
42
|
+
query_stats: collect_query_stats
|
43
|
+
}
|
44
|
+
rescue StandardError => e
|
45
|
+
log_error "Database info collection failed: #{e.message}"
|
46
|
+
{ error: e.message }
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Collect database adapter information
|
52
|
+
#
|
53
|
+
# @return [Hash] adapter information
|
54
|
+
def collect_adapter_info
|
55
|
+
return {} unless active_record_available?
|
56
|
+
|
57
|
+
connection = ActiveRecord::Base.connection
|
58
|
+
adapter_name = connection.adapter_name.downcase
|
59
|
+
|
60
|
+
{
|
61
|
+
name: adapter_name,
|
62
|
+
pool_size: ActiveRecord::Base.connection_pool.size,
|
63
|
+
checkout_timeout: ActiveRecord::Base.connection_pool.checkout_timeout
|
64
|
+
}
|
65
|
+
rescue StandardError => e
|
66
|
+
log_error "Failed to get adapter info: #{e.message}"
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
|
70
|
+
# Collect database version information
|
71
|
+
#
|
72
|
+
# @return [String] database version
|
73
|
+
# rubocop:disable Metrics/MethodLength
|
74
|
+
def collect_database_version
|
75
|
+
return nil unless active_record_available?
|
76
|
+
|
77
|
+
connection = ActiveRecord::Base.connection
|
78
|
+
adapter_name = connection.adapter_name.downcase
|
79
|
+
|
80
|
+
case adapter_name
|
81
|
+
when /mysql/
|
82
|
+
connection.select_value("SELECT VERSION()")
|
83
|
+
when /postgresql/
|
84
|
+
connection.select_value("SELECT version()")
|
85
|
+
when /sqlite/
|
86
|
+
connection.select_value("SELECT sqlite_version()")
|
87
|
+
else
|
88
|
+
"unknown"
|
89
|
+
end
|
90
|
+
rescue StandardError => e
|
91
|
+
log_error "Failed to get database version: #{e.message}"
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
# rubocop:enable Metrics/MethodLength
|
95
|
+
|
96
|
+
# Collect connection pool information
|
97
|
+
#
|
98
|
+
# @return [Hash] connection pool statistics
|
99
|
+
def collect_connection_pool_info
|
100
|
+
return {} unless active_record_available?
|
101
|
+
|
102
|
+
pool = ActiveRecord::Base.connection_pool
|
103
|
+
{
|
104
|
+
size: pool.size,
|
105
|
+
connections: pool.connections.size,
|
106
|
+
active: pool.active_connection?,
|
107
|
+
checkout_timeout: pool.checkout_timeout,
|
108
|
+
reaper_frequency: pool.reaper&.frequency
|
109
|
+
}
|
110
|
+
rescue StandardError => e
|
111
|
+
log_error "Failed to get connection pool info: #{e.message}"
|
112
|
+
{}
|
113
|
+
end
|
114
|
+
|
115
|
+
# Collect table information
|
116
|
+
#
|
117
|
+
# @return [Hash] table statistics
|
118
|
+
# rubocop:disable Metrics/MethodLength
|
119
|
+
def collect_table_info
|
120
|
+
return {} unless active_record_available?
|
121
|
+
|
122
|
+
connection = ActiveRecord::Base.connection
|
123
|
+
tables = connection.tables
|
124
|
+
|
125
|
+
# Skip detailed table info if performance metrics are disabled
|
126
|
+
return { count: tables.size } unless Dbwatcher.configuration.system_info_include_performance_metrics
|
127
|
+
|
128
|
+
table_info = { count: tables.size, tables: [] }
|
129
|
+
|
130
|
+
tables.each do |table|
|
131
|
+
count = connection.select_value("SELECT COUNT(*) FROM #{connection.quote_table_name(table)}").to_i
|
132
|
+
table_info[:tables] << {
|
133
|
+
name: table,
|
134
|
+
count: count,
|
135
|
+
has_primary_key: connection.primary_key(table).present?
|
136
|
+
}
|
137
|
+
rescue StandardError => e
|
138
|
+
log_error "Failed to get info for table #{table}: #{e.message}"
|
139
|
+
table_info[:tables] << { name: table, error: e.message }
|
140
|
+
end
|
141
|
+
|
142
|
+
table_info
|
143
|
+
rescue StandardError => e
|
144
|
+
log_error "Failed to get table info: #{e.message}"
|
145
|
+
{}
|
146
|
+
end
|
147
|
+
# rubocop:enable Metrics/MethodLength
|
148
|
+
|
149
|
+
# Collect schema information
|
150
|
+
#
|
151
|
+
# @return [Hash] schema statistics
|
152
|
+
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
153
|
+
def collect_schema_info
|
154
|
+
return {} unless active_record_available?
|
155
|
+
|
156
|
+
# Skip schema info if performance metrics are disabled
|
157
|
+
return {} unless Dbwatcher.configuration.system_info_include_performance_metrics
|
158
|
+
|
159
|
+
connection = ActiveRecord::Base.connection
|
160
|
+
schema_info = {}
|
161
|
+
|
162
|
+
# Get schema migrations if available
|
163
|
+
if connection.table_exists?("schema_migrations")
|
164
|
+
begin
|
165
|
+
versions = connection.select_values("SELECT version FROM schema_migrations ORDER BY version DESC")
|
166
|
+
schema_info[:migrations] = {
|
167
|
+
count: versions.size,
|
168
|
+
latest: versions.first
|
169
|
+
}
|
170
|
+
rescue StandardError => e
|
171
|
+
log_error "Failed to get schema migrations: #{e.message}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Get schema information if available
|
176
|
+
if defined?(ActiveRecord::InternalMetadata) && connection.table_exists?(ActiveRecord::InternalMetadata.table_name)
|
177
|
+
begin
|
178
|
+
# Split the long line to avoid line length issues
|
179
|
+
query = "SELECT value FROM #{ActiveRecord::InternalMetadata.table_name} " \
|
180
|
+
"WHERE key = 'environment'"
|
181
|
+
environment = connection.select_value(query)
|
182
|
+
schema_info[:environment] = environment
|
183
|
+
rescue StandardError => e
|
184
|
+
log_error "Failed to get schema environment: #{e.message}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
schema_info
|
189
|
+
rescue StandardError => e
|
190
|
+
log_error "Failed to get schema info: #{e.message}"
|
191
|
+
{}
|
192
|
+
end
|
193
|
+
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
194
|
+
|
195
|
+
# Collect index information
|
196
|
+
#
|
197
|
+
# @return [Hash] index statistics
|
198
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
199
|
+
def collect_index_info
|
200
|
+
return {} unless active_record_available?
|
201
|
+
|
202
|
+
# Skip index info if performance metrics are disabled
|
203
|
+
return {} unless Dbwatcher.configuration.system_info_include_performance_metrics
|
204
|
+
|
205
|
+
connection = ActiveRecord::Base.connection
|
206
|
+
tables = connection.tables
|
207
|
+
index_info = { count: 0, tables: [] }
|
208
|
+
|
209
|
+
tables.each do |table|
|
210
|
+
indexes = connection.indexes(table)
|
211
|
+
table_indexes = indexes.map do |index|
|
212
|
+
{
|
213
|
+
name: index.name,
|
214
|
+
columns: index.columns,
|
215
|
+
unique: index.unique
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
index_info[:tables] << {
|
220
|
+
name: table,
|
221
|
+
indexes: table_indexes
|
222
|
+
}
|
223
|
+
index_info[:count] += indexes.size
|
224
|
+
rescue StandardError => e
|
225
|
+
log_error "Failed to get indexes for table #{table}: #{e.message}"
|
226
|
+
index_info[:tables] << { name: table, error: e.message }
|
227
|
+
end
|
228
|
+
|
229
|
+
index_info
|
230
|
+
rescue StandardError => e
|
231
|
+
log_error "Failed to get index info: #{e.message}"
|
232
|
+
{}
|
233
|
+
end
|
234
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
235
|
+
|
236
|
+
# Collect query statistics if available
|
237
|
+
#
|
238
|
+
# @return [Hash] query statistics
|
239
|
+
def collect_query_stats
|
240
|
+
return {} unless active_record_available?
|
241
|
+
|
242
|
+
# This would be implementation-specific and could be expanded
|
243
|
+
# based on the database adapter and monitoring tools available
|
244
|
+
{}
|
245
|
+
rescue StandardError => e
|
246
|
+
log_error "Failed to get query stats: #{e.message}"
|
247
|
+
{}
|
248
|
+
end
|
249
|
+
|
250
|
+
# Check if ActiveRecord is available and connected
|
251
|
+
#
|
252
|
+
# @return [Boolean] true if ActiveRecord is available and connected
|
253
|
+
def active_record_available?
|
254
|
+
defined?(ActiveRecord::Base) && ActiveRecord::Base.connected?
|
255
|
+
rescue StandardError => e
|
256
|
+
log_error "Failed to check ActiveRecord availability: #{e.message}"
|
257
|
+
false
|
258
|
+
end
|
259
|
+
end
|
260
|
+
# rubocop:enable Metrics/ClassLength
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|