dbwatcher 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -2
- data/app/assets/config/dbwatcher_manifest.js +1 -0
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +196 -119
- data/app/assets/javascripts/dbwatcher/components/dashboard.js +325 -0
- data/app/assets/javascripts/dbwatcher/components/timeline.js +211 -0
- data/app/assets/javascripts/dbwatcher/dbwatcher.js +5 -0
- data/app/assets/stylesheets/dbwatcher/application.css +691 -41
- data/app/assets/stylesheets/dbwatcher/application.scss +5 -0
- data/app/assets/stylesheets/dbwatcher/components/_badges.scss +68 -23
- data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +83 -26
- data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +3 -3
- data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +9 -0
- data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +248 -0
- data/app/assets/stylesheets/dbwatcher/components/_timeline.scss +326 -0
- data/app/assets/stylesheets/dbwatcher/vendor/_tabulator_overrides.scss +37 -0
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +18 -4
- data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +180 -0
- data/app/controllers/dbwatcher/dashboard/system_info_controller.rb +64 -0
- data/app/controllers/dbwatcher/dashboard_controller.rb +17 -0
- data/app/controllers/dbwatcher/sessions_controller.rb +3 -19
- data/app/helpers/dbwatcher/application_helper.rb +43 -11
- data/app/helpers/dbwatcher/diagram_helper.rb +0 -88
- data/app/views/dbwatcher/dashboard/_layout.html.erb +27 -0
- data/app/views/dbwatcher/dashboard/_overview.html.erb +188 -0
- data/app/views/dbwatcher/dashboard/_system_info.html.erb +22 -0
- data/app/views/dbwatcher/dashboard/_system_info_content.html.erb +389 -0
- data/app/views/dbwatcher/dashboard/index.html.erb +8 -177
- data/app/views/dbwatcher/sessions/_layout.html.erb +26 -0
- data/app/views/dbwatcher/sessions/{_summary_tab.html.erb → _summary.html.erb} +1 -1
- data/app/views/dbwatcher/sessions/_tables.html.erb +170 -0
- data/app/views/dbwatcher/sessions/_timeline.html.erb +260 -0
- data/app/views/dbwatcher/sessions/index.html.erb +107 -87
- data/app/views/dbwatcher/sessions/show.html.erb +12 -4
- data/app/views/dbwatcher/tables/index.html.erb +32 -40
- data/app/views/layouts/dbwatcher/application.html.erb +101 -48
- data/config/routes.rb +25 -7
- data/lib/dbwatcher/configuration.rb +18 -1
- data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +102 -1
- data/lib/dbwatcher/services/api/{changes_data_service.rb → tables_data_service.rb} +6 -6
- data/lib/dbwatcher/services/base_service.rb +2 -0
- data/lib/dbwatcher/services/system_info/database_info_collector.rb +263 -0
- data/lib/dbwatcher/services/system_info/machine_info_collector.rb +387 -0
- data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +328 -0
- data/lib/dbwatcher/services/system_info/system_info_collector.rb +114 -0
- data/lib/dbwatcher/services/timeline_data_service/enhancement_utilities.rb +100 -0
- data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +125 -0
- data/lib/dbwatcher/services/timeline_data_service/metadata_builder.rb +93 -0
- data/lib/dbwatcher/services/timeline_data_service.rb +130 -0
- data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +1 -1
- data/lib/dbwatcher/storage/concerns/error_handler.rb +6 -6
- data/lib/dbwatcher/storage/session.rb +5 -0
- data/lib/dbwatcher/storage/system_info_storage.rb +242 -0
- data/lib/dbwatcher/storage.rb +12 -0
- data/lib/dbwatcher/version.rb +1 -1
- data/lib/dbwatcher.rb +16 -2
- metadata +28 -16
- data/app/helpers/dbwatcher/component_helper.rb +0 -29
- data/app/views/dbwatcher/sessions/_changes_tab.html.erb +0 -265
- data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +0 -12
- data/app/views/dbwatcher/sessions/changes.html.erb +0 -21
- data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +0 -44
- data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +0 -96
- data/app/views/dbwatcher/sessions/diagrams.html.erb +0 -21
- data/app/views/dbwatcher/sessions/shared/_layout.html.erb +0 -8
- data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +0 -35
- data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +0 -25
- data/app/views/dbwatcher/sessions/summary.html.erb +0 -21
- /data/app/views/dbwatcher/sessions/{_diagrams_tab.html.erb → _diagrams.html.erb} +0 -0
@@ -0,0 +1,326 @@
|
|
1
|
+
// Timeline Component Styles
|
2
|
+
// Provides styles for the interactive timeline visualization
|
3
|
+
|
4
|
+
.timeline-container {
|
5
|
+
@apply h-full flex flex-col;
|
6
|
+
}
|
7
|
+
|
8
|
+
.timeline-controls {
|
9
|
+
@apply p-3 border-b border-gray-300 bg-gray-50;
|
10
|
+
}
|
11
|
+
|
12
|
+
.timeline-header {
|
13
|
+
@apply flex items-center justify-between mb-3;
|
14
|
+
}
|
15
|
+
|
16
|
+
.timeline-title {
|
17
|
+
@apply text-sm font-medium text-gray-900;
|
18
|
+
}
|
19
|
+
|
20
|
+
.timeline-zoom-controls {
|
21
|
+
@apply flex items-center gap-2;
|
22
|
+
}
|
23
|
+
|
24
|
+
.timeline-filter-controls {
|
25
|
+
@apply flex items-center gap-4 text-xs flex-wrap;
|
26
|
+
}
|
27
|
+
|
28
|
+
.timeline-filter-group {
|
29
|
+
@apply flex items-center gap-2;
|
30
|
+
}
|
31
|
+
|
32
|
+
.timeline-filter-label {
|
33
|
+
@apply text-gray-700 font-medium;
|
34
|
+
}
|
35
|
+
|
36
|
+
// Timeline visualization
|
37
|
+
.timeline-visualization {
|
38
|
+
@apply flex-1 overflow-hidden;
|
39
|
+
}
|
40
|
+
|
41
|
+
.timeline-time-header {
|
42
|
+
@apply h-8 bg-gray-100 border-b border-gray-200 relative;
|
43
|
+
}
|
44
|
+
|
45
|
+
.timeline-time-scale {
|
46
|
+
@apply absolute inset-0 flex items-center px-4;
|
47
|
+
}
|
48
|
+
|
49
|
+
.timeline-content {
|
50
|
+
@apply flex-1 overflow-auto p-4 bg-white;
|
51
|
+
}
|
52
|
+
|
53
|
+
.timeline-track {
|
54
|
+
@apply relative h-16 bg-gray-50 rounded border border-gray-200 mb-4;
|
55
|
+
}
|
56
|
+
|
57
|
+
.timeline-line {
|
58
|
+
@apply absolute top-1/2 left-4 right-4 h-0.5 bg-gray-300 transform -translate-y-1/2;
|
59
|
+
}
|
60
|
+
|
61
|
+
.timeline-marker {
|
62
|
+
@apply absolute top-1/2 transform -translate-y-1/2 -translate-x-1/2 cursor-pointer;
|
63
|
+
|
64
|
+
.timeline-marker-dot {
|
65
|
+
@apply w-3 h-3 rounded-full border-2 border-white shadow-sm transition-transform;
|
66
|
+
|
67
|
+
&:hover {
|
68
|
+
@apply scale-125;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
// Timeline statistics
|
74
|
+
.timeline-stats {
|
75
|
+
@apply grid grid-cols-2 gap-4 text-xs;
|
76
|
+
|
77
|
+
@screen md {
|
78
|
+
@apply grid-cols-4;
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
.timeline-stat-card {
|
83
|
+
@apply bg-gray-50 p-3 rounded;
|
84
|
+
}
|
85
|
+
|
86
|
+
.timeline-stat-label {
|
87
|
+
@apply text-gray-500 font-medium;
|
88
|
+
}
|
89
|
+
|
90
|
+
.timeline-stat-value {
|
91
|
+
@apply text-lg font-bold text-gray-900;
|
92
|
+
}
|
93
|
+
|
94
|
+
.timeline-stat-detail {
|
95
|
+
@apply text-xs text-gray-500;
|
96
|
+
}
|
97
|
+
|
98
|
+
// Operation list
|
99
|
+
.timeline-operations {
|
100
|
+
@apply mt-6;
|
101
|
+
}
|
102
|
+
|
103
|
+
.timeline-operations-title {
|
104
|
+
@apply text-sm font-medium text-gray-900 mb-3;
|
105
|
+
}
|
106
|
+
|
107
|
+
.timeline-operations-list {
|
108
|
+
@apply space-y-2 max-h-64 overflow-auto;
|
109
|
+
}
|
110
|
+
|
111
|
+
.timeline-operation-item {
|
112
|
+
@apply flex items-center justify-between p-2 bg-gray-50 rounded hover:bg-gray-100 cursor-pointer text-xs;
|
113
|
+
}
|
114
|
+
|
115
|
+
.timeline-operation-info {
|
116
|
+
@apply flex items-center gap-3;
|
117
|
+
}
|
118
|
+
|
119
|
+
.timeline-operation-marker {
|
120
|
+
@apply w-2 h-2 rounded-full;
|
121
|
+
}
|
122
|
+
|
123
|
+
.timeline-operation-type {
|
124
|
+
@apply font-medium;
|
125
|
+
}
|
126
|
+
|
127
|
+
.timeline-operation-table {
|
128
|
+
// No additional styles needed - uses default text
|
129
|
+
}
|
130
|
+
|
131
|
+
.timeline-operation-time {
|
132
|
+
@apply text-gray-500;
|
133
|
+
}
|
134
|
+
|
135
|
+
.timeline-operation-record {
|
136
|
+
@apply text-gray-500;
|
137
|
+
}
|
138
|
+
|
139
|
+
// Empty state
|
140
|
+
.timeline-empty {
|
141
|
+
@apply text-center py-8 text-gray-500;
|
142
|
+
}
|
143
|
+
|
144
|
+
.timeline-empty-icon {
|
145
|
+
@apply w-12 h-12 mx-auto mb-4 text-gray-300;
|
146
|
+
}
|
147
|
+
|
148
|
+
.timeline-empty-text {
|
149
|
+
// No additional styles needed
|
150
|
+
}
|
151
|
+
|
152
|
+
.timeline-empty-action {
|
153
|
+
@apply mt-2 text-blue-600 underline;
|
154
|
+
}
|
155
|
+
|
156
|
+
// Loading state
|
157
|
+
.timeline-loading {
|
158
|
+
@apply flex items-center justify-center h-64;
|
159
|
+
}
|
160
|
+
|
161
|
+
.timeline-loading-spinner {
|
162
|
+
@apply animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500;
|
163
|
+
}
|
164
|
+
|
165
|
+
.timeline-loading-text {
|
166
|
+
@apply ml-2 text-gray-600;
|
167
|
+
}
|
168
|
+
|
169
|
+
// Error state
|
170
|
+
.timeline-error {
|
171
|
+
@apply p-4 bg-red-50 border border-red-200 rounded m-4;
|
172
|
+
}
|
173
|
+
|
174
|
+
.timeline-error-text {
|
175
|
+
@apply text-red-700;
|
176
|
+
}
|
177
|
+
|
178
|
+
.timeline-error-retry {
|
179
|
+
@apply mt-2 text-red-600 underline;
|
180
|
+
}
|
181
|
+
|
182
|
+
// Modal styles
|
183
|
+
.timeline-modal-overlay {
|
184
|
+
@apply fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50;
|
185
|
+
}
|
186
|
+
|
187
|
+
.timeline-modal-content {
|
188
|
+
@apply bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-96 overflow-auto;
|
189
|
+
}
|
190
|
+
|
191
|
+
.timeline-modal-header {
|
192
|
+
@apply p-4 border-b border-gray-200 flex justify-between items-center;
|
193
|
+
}
|
194
|
+
|
195
|
+
.timeline-modal-title {
|
196
|
+
@apply text-lg font-medium;
|
197
|
+
}
|
198
|
+
|
199
|
+
.timeline-modal-close {
|
200
|
+
@apply text-gray-400 hover:text-gray-600;
|
201
|
+
}
|
202
|
+
|
203
|
+
.timeline-modal-body {
|
204
|
+
@apply p-4;
|
205
|
+
}
|
206
|
+
|
207
|
+
.timeline-operation-details {
|
208
|
+
@apply grid grid-cols-1 gap-4 text-sm;
|
209
|
+
|
210
|
+
@screen md {
|
211
|
+
@apply grid-cols-2;
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
.timeline-operation-detail-item {
|
216
|
+
// No additional styles needed
|
217
|
+
}
|
218
|
+
|
219
|
+
.timeline-operation-detail-label {
|
220
|
+
@apply font-medium;
|
221
|
+
}
|
222
|
+
|
223
|
+
.timeline-operation-detail-value {
|
224
|
+
@apply ml-2;
|
225
|
+
|
226
|
+
&.operation-badge {
|
227
|
+
@apply px-2 py-1 rounded text-xs;
|
228
|
+
}
|
229
|
+
|
230
|
+
&.monospace {
|
231
|
+
@apply font-mono;
|
232
|
+
}
|
233
|
+
|
234
|
+
&.small {
|
235
|
+
@apply text-xs;
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
.timeline-changes-section {
|
240
|
+
@apply mt-4;
|
241
|
+
}
|
242
|
+
|
243
|
+
.timeline-changes-title {
|
244
|
+
@apply text-sm font-medium;
|
245
|
+
}
|
246
|
+
|
247
|
+
.timeline-changes-content {
|
248
|
+
@apply mt-2 p-3 bg-gray-50 rounded text-xs overflow-auto border max-h-32;
|
249
|
+
}
|
250
|
+
|
251
|
+
.timeline-metadata-section {
|
252
|
+
@apply mt-4;
|
253
|
+
}
|
254
|
+
|
255
|
+
.timeline-metadata-title {
|
256
|
+
@apply text-sm font-medium;
|
257
|
+
}
|
258
|
+
|
259
|
+
.timeline-metadata-grid {
|
260
|
+
@apply mt-2 grid grid-cols-2 gap-2 text-xs;
|
261
|
+
}
|
262
|
+
|
263
|
+
.timeline-metadata-item {
|
264
|
+
// No additional styles needed for items that are shown
|
265
|
+
}
|
266
|
+
|
267
|
+
.timeline-metadata-key {
|
268
|
+
@apply font-medium capitalize;
|
269
|
+
}
|
270
|
+
|
271
|
+
.timeline-metadata-value {
|
272
|
+
// No additional styles needed
|
273
|
+
}
|
274
|
+
|
275
|
+
// Responsive adjustments
|
276
|
+
@screen sm {
|
277
|
+
.timeline-filter-controls {
|
278
|
+
@apply flex-nowrap;
|
279
|
+
}
|
280
|
+
|
281
|
+
.timeline-stats {
|
282
|
+
@apply grid-cols-4;
|
283
|
+
}
|
284
|
+
}
|
285
|
+
|
286
|
+
// Operation color classes
|
287
|
+
.operation-insert {
|
288
|
+
@apply text-green-600 bg-green-100;
|
289
|
+
}
|
290
|
+
|
291
|
+
.operation-update {
|
292
|
+
@apply text-blue-600 bg-blue-100;
|
293
|
+
}
|
294
|
+
|
295
|
+
.operation-delete {
|
296
|
+
@apply text-red-600 bg-red-100;
|
297
|
+
}
|
298
|
+
|
299
|
+
.operation-select {
|
300
|
+
@apply text-purple-600 bg-purple-100;
|
301
|
+
}
|
302
|
+
|
303
|
+
// Transition classes for Alpine.js
|
304
|
+
.timeline-transition-enter {
|
305
|
+
@apply transition ease-out duration-300;
|
306
|
+
}
|
307
|
+
|
308
|
+
.timeline-transition-enter-start {
|
309
|
+
@apply opacity-0;
|
310
|
+
}
|
311
|
+
|
312
|
+
.timeline-transition-enter-end {
|
313
|
+
@apply opacity-100;
|
314
|
+
}
|
315
|
+
|
316
|
+
.timeline-transition-leave {
|
317
|
+
@apply transition ease-in duration-200;
|
318
|
+
}
|
319
|
+
|
320
|
+
.timeline-transition-leave-start {
|
321
|
+
@apply opacity-100;
|
322
|
+
}
|
323
|
+
|
324
|
+
.timeline-transition-leave-end {
|
325
|
+
@apply opacity-0;
|
326
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
/**
|
2
|
+
* Tabulator Vendor Overrides
|
3
|
+
* Overrides for the Tabulator.js vendor CSS
|
4
|
+
*/
|
5
|
+
|
6
|
+
// Override specific vendor styles that can't be handled in our component CSS
|
7
|
+
.tabulator {
|
8
|
+
// Force our font settings
|
9
|
+
font-family: 'Consolas', 'Monaco', 'Lucida Console', monospace !important;
|
10
|
+
|
11
|
+
// Ensure proper header styling
|
12
|
+
.tabulator-header {
|
13
|
+
background: #f3f3f3 !important;
|
14
|
+
|
15
|
+
.tabulator-col {
|
16
|
+
background: #f3f3f3 !important;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
// Ensure proper row styling
|
21
|
+
.tabulator-tableholder .tabulator-table .tabulator-row {
|
22
|
+
background: white !important;
|
23
|
+
|
24
|
+
&:hover {
|
25
|
+
background: #f3f4f6 !important;
|
26
|
+
}
|
27
|
+
|
28
|
+
// Ensure proper hover state for sticky cells
|
29
|
+
&:hover .tabulator-cell {
|
30
|
+
&.sticky-left-0,
|
31
|
+
&.sticky-left-1,
|
32
|
+
&.sticky-left-2 {
|
33
|
+
background: #f9fafb !important;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
@@ -6,12 +6,12 @@ module Dbwatcher
|
|
6
6
|
class SessionsController < BaseController
|
7
7
|
before_action :find_session, except: [:diagram_types]
|
8
8
|
|
9
|
-
def
|
10
|
-
Rails.logger.info "API::V1::SessionsController#
|
9
|
+
def tables_data
|
10
|
+
Rails.logger.info "API::V1::SessionsController#tables_data: Getting tables for session #{@session.id}"
|
11
11
|
|
12
|
-
# Paginated, filtered
|
12
|
+
# Paginated, filtered tables data
|
13
13
|
# Convert ActionController::Parameters to a hash before passing to service
|
14
|
-
service = Dbwatcher::Services::Api::
|
14
|
+
service = Dbwatcher::Services::Api::TablesDataService.new(@session, filter_params.to_h)
|
15
15
|
render json: service.call
|
16
16
|
end
|
17
17
|
|
@@ -39,6 +39,20 @@ module Dbwatcher
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
def timeline_data
|
43
|
+
Rails.logger.info "API::V1::SessionsController#timeline_data: Getting timeline for session #{@session.id}"
|
44
|
+
|
45
|
+
# Timeline data processed from session changes
|
46
|
+
service = Dbwatcher::Services::TimelineDataService.new(@session)
|
47
|
+
result = service.call
|
48
|
+
|
49
|
+
if result[:errors].any?
|
50
|
+
render json: { error: result[:errors].first[:message] }, status: :unprocessable_entity
|
51
|
+
else
|
52
|
+
render json: result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
42
56
|
def diagram_types
|
43
57
|
Rails.logger.info "API::V1::SessionsController#diagram_types: Getting available diagram types"
|
44
58
|
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Api
|
5
|
+
module V1
|
6
|
+
# System information API controller
|
7
|
+
#
|
8
|
+
# Provides RESTful API endpoints for system information access.
|
9
|
+
# Supports JSON responses for programmatic access to system data.
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
11
|
+
class SystemInfoController < BaseController
|
12
|
+
before_action :ensure_system_info_enabled
|
13
|
+
|
14
|
+
# Get complete system information
|
15
|
+
#
|
16
|
+
# @return [void]
|
17
|
+
def index
|
18
|
+
info = system_info_storage.cached_info
|
19
|
+
|
20
|
+
render json: {
|
21
|
+
status: :ok,
|
22
|
+
data: info,
|
23
|
+
timestamp: Time.current.iso8601,
|
24
|
+
cache_age: system_info_storage.info_age&.round(2)
|
25
|
+
}
|
26
|
+
rescue StandardError => e
|
27
|
+
log_error "API: Failed to get system information: #{e.message}"
|
28
|
+
render json: { error: e.message }, status: :internal_server_error
|
29
|
+
end
|
30
|
+
|
31
|
+
# Refresh system information
|
32
|
+
#
|
33
|
+
# @return [void]
|
34
|
+
def refresh
|
35
|
+
info = system_info_storage.refresh_info
|
36
|
+
|
37
|
+
render json: {
|
38
|
+
status: :ok,
|
39
|
+
data: info,
|
40
|
+
timestamp: Time.current.iso8601,
|
41
|
+
message: "System information refreshed successfully"
|
42
|
+
}
|
43
|
+
rescue StandardError => e
|
44
|
+
log_error "API: Failed to refresh system information: #{e.message}"
|
45
|
+
render json: { error: e.message }, status: :internal_server_error
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get machine information only
|
49
|
+
#
|
50
|
+
# @return [void]
|
51
|
+
def machine
|
52
|
+
info = system_info_storage.cached_info
|
53
|
+
|
54
|
+
render json: {
|
55
|
+
status: :ok,
|
56
|
+
data: info[:machine] || {},
|
57
|
+
timestamp: Time.current.iso8601,
|
58
|
+
cache_age: system_info_storage.info_age&.round(2)
|
59
|
+
}
|
60
|
+
rescue StandardError => e
|
61
|
+
log_error "API: Failed to get machine information: #{e.message}"
|
62
|
+
render json: { error: e.message }, status: :internal_server_error
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get database information only
|
66
|
+
#
|
67
|
+
# @return [void]
|
68
|
+
def database
|
69
|
+
info = system_info_storage.cached_info
|
70
|
+
|
71
|
+
render json: {
|
72
|
+
status: :ok,
|
73
|
+
data: info[:database] || {},
|
74
|
+
timestamp: Time.current.iso8601,
|
75
|
+
cache_age: system_info_storage.info_age&.round(2)
|
76
|
+
}
|
77
|
+
rescue StandardError => e
|
78
|
+
log_error "API: Failed to get database information: #{e.message}"
|
79
|
+
render json: { error: e.message }, status: :internal_server_error
|
80
|
+
end
|
81
|
+
|
82
|
+
# Get runtime information only
|
83
|
+
#
|
84
|
+
# @return [void]
|
85
|
+
def runtime
|
86
|
+
info = system_info_storage.cached_info
|
87
|
+
|
88
|
+
render json: {
|
89
|
+
status: :ok,
|
90
|
+
data: info[:runtime] || {},
|
91
|
+
timestamp: Time.current.iso8601,
|
92
|
+
cache_age: system_info_storage.info_age&.round(2)
|
93
|
+
}
|
94
|
+
rescue StandardError => e
|
95
|
+
log_error "API: Failed to get runtime information: #{e.message}"
|
96
|
+
render json: { error: e.message }, status: :internal_server_error
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get system information summary
|
100
|
+
#
|
101
|
+
# @return [void]
|
102
|
+
def summary
|
103
|
+
summary_data = system_info_storage.summary
|
104
|
+
|
105
|
+
render json: {
|
106
|
+
status: :ok,
|
107
|
+
data: summary_data,
|
108
|
+
timestamp: Time.current.iso8601
|
109
|
+
}
|
110
|
+
rescue StandardError => e
|
111
|
+
log_error "API: Failed to get system info summary: #{e.message}"
|
112
|
+
render json: { error: e.message }, status: :internal_server_error
|
113
|
+
end
|
114
|
+
|
115
|
+
# Clear system information cache
|
116
|
+
#
|
117
|
+
# @return [void]
|
118
|
+
def clear_cache
|
119
|
+
system_info_storage.clear_cache
|
120
|
+
|
121
|
+
render json: {
|
122
|
+
status: :ok,
|
123
|
+
message: "System information cache cleared successfully",
|
124
|
+
timestamp: Time.current.iso8601
|
125
|
+
}
|
126
|
+
rescue StandardError => e
|
127
|
+
log_error "API: Failed to clear cache: #{e.message}"
|
128
|
+
render json: { error: e.message }, status: :internal_server_error
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get cache status and metadata
|
132
|
+
#
|
133
|
+
# @return [void]
|
134
|
+
# rubocop:disable Metrics/MethodLength
|
135
|
+
def cache_status
|
136
|
+
info_available = system_info_storage.info_available?
|
137
|
+
cache_age = system_info_storage.info_age
|
138
|
+
|
139
|
+
render json: {
|
140
|
+
status: :ok,
|
141
|
+
data: {
|
142
|
+
cache_available: info_available,
|
143
|
+
cache_age: cache_age&.round(2),
|
144
|
+
cache_expired: cache_age && cache_age > Dbwatcher.configuration.system_info_cache_duration,
|
145
|
+
max_cache_age: Dbwatcher.configuration.system_info_cache_duration,
|
146
|
+
refresh_interval: Dbwatcher.configuration.system_info_refresh_interval
|
147
|
+
},
|
148
|
+
timestamp: Time.current.iso8601
|
149
|
+
}
|
150
|
+
rescue StandardError => e
|
151
|
+
log_error "API: Failed to get cache status: #{e.message}"
|
152
|
+
render json: { error: e.message }, status: :internal_server_error
|
153
|
+
end
|
154
|
+
# rubocop:enable Metrics/MethodLength
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
# Get system info storage instance
|
159
|
+
#
|
160
|
+
# @return [Storage::SystemInfoStorage] storage instance
|
161
|
+
def system_info_storage
|
162
|
+
@system_info_storage ||= Storage::SystemInfoStorage.new
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check if system information collection is enabled
|
166
|
+
#
|
167
|
+
# @return [void]
|
168
|
+
def ensure_system_info_enabled
|
169
|
+
return if Dbwatcher.configuration.collect_system_info
|
170
|
+
|
171
|
+
render json: {
|
172
|
+
error: "System information collection is disabled",
|
173
|
+
status: :forbidden
|
174
|
+
}, status: :forbidden
|
175
|
+
end
|
176
|
+
end
|
177
|
+
# rubocop:enable Metrics/ClassLength
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Dashboard
|
5
|
+
class SystemInfoController < BaseController
|
6
|
+
def refresh
|
7
|
+
system_info_storage.refresh_info
|
8
|
+
|
9
|
+
respond_to do |format|
|
10
|
+
format.json do
|
11
|
+
render json: refresh_success_response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
rescue StandardError => e
|
15
|
+
respond_to do |format|
|
16
|
+
format.json do
|
17
|
+
render json: { success: false, error: e.message }, status: :internal_server_error
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_cache
|
23
|
+
system_info_storage.clear_cache
|
24
|
+
|
25
|
+
respond_to do |format|
|
26
|
+
format.json do
|
27
|
+
render json: clear_cache_success_response
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue StandardError => e
|
31
|
+
respond_to do |format|
|
32
|
+
format.json do
|
33
|
+
render json: { success: false, error: e.message }, status: :internal_server_error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Get system info storage instance
|
41
|
+
#
|
42
|
+
# @return [Storage::SystemInfoStorage] storage instance
|
43
|
+
def system_info_storage
|
44
|
+
@system_info_storage ||= Storage::SystemInfoStorage.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def refresh_success_response
|
48
|
+
{
|
49
|
+
success: true,
|
50
|
+
message: "System information refreshed successfully",
|
51
|
+
data: system_info_storage.cached_info,
|
52
|
+
summary: system_info_storage.summary
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear_cache_success_response
|
57
|
+
{
|
58
|
+
success: true,
|
59
|
+
message: "System information cache cleared successfully"
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -7,6 +7,14 @@ module Dbwatcher
|
|
7
7
|
@recent_sessions = dashboard_data[:recent_sessions]
|
8
8
|
@active_tables = dashboard_data[:active_tables]
|
9
9
|
@query_stats = dashboard_data[:query_stats]
|
10
|
+
@active_tab = params[:tab] || "overview"
|
11
|
+
|
12
|
+
# Add system information if enabled
|
13
|
+
return unless Dbwatcher.configuration.collect_system_info
|
14
|
+
|
15
|
+
@system_info_summary = system_info_storage.summary
|
16
|
+
@system_info = system_info_storage.cached_info
|
17
|
+
@info_age = system_info_storage.info_age
|
10
18
|
end
|
11
19
|
|
12
20
|
def clear_all
|
@@ -16,5 +24,14 @@ module Dbwatcher
|
|
16
24
|
root_path
|
17
25
|
)
|
18
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Get system info storage instance
|
31
|
+
#
|
32
|
+
# @return [Storage::SystemInfoStorage] storage instance
|
33
|
+
def system_info_storage
|
34
|
+
@system_info_storage ||= Storage::SystemInfoStorage.new
|
35
|
+
end
|
19
36
|
end
|
20
37
|
end
|
@@ -9,22 +9,9 @@ module Dbwatcher
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def show
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def changes
|
16
|
-
Rails.logger.info "SessionsController#changes: Loading changes for session #{@session.id}"
|
17
|
-
# No server-side data processing - API-first architecture
|
18
|
-
end
|
19
|
-
|
20
|
-
def summary
|
21
|
-
Rails.logger.info "SessionsController#summary: Loading summary for session #{@session.id}"
|
22
|
-
# No server-side data processing - API-first architecture
|
23
|
-
end
|
24
|
-
|
25
|
-
def diagrams
|
26
|
-
Rails.logger.info "SessionsController#diagrams: Loading diagrams for session #{@session.id}"
|
27
|
-
# No server-side data processing - API-first architecture
|
12
|
+
@active_tab = params[:tab] || "tables"
|
13
|
+
# Debug logging
|
14
|
+
Rails.logger.info "SessionsController#show: Session ID: #{@session.id.inspect}, Class: #{@session.class}"
|
28
15
|
end
|
29
16
|
|
30
17
|
def clear
|
@@ -41,8 +28,5 @@ module Dbwatcher
|
|
41
28
|
@session = Storage.sessions.find(params[:id])
|
42
29
|
handle_not_found("Session", sessions_path) unless @session
|
43
30
|
end
|
44
|
-
|
45
|
-
# No longer needed with API-first architecture
|
46
|
-
# All data processing happens in API services and is loaded via JavaScript
|
47
31
|
end
|
48
32
|
end
|