dbwatcher 1.1.3 → 1.1.5
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 +79 -26
- data/app/assets/images/dbwatcher/apple-touch-icon.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher-social-preview.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher-tranparent_512x512.png +0 -0
- data/app/assets/images/dbwatcher/dbwatcher_512x512.png +0 -0
- data/app/assets/images/dbwatcher/favicon-96x96.png +0 -0
- data/app/assets/images/dbwatcher/favicon.ico +0 -0
- data/app/assets/images/dbwatcher/favicon.svg +3 -0
- data/app/assets/images/dbwatcher/site.webmanifest +21 -0
- data/app/assets/images/dbwatcher/web-app-manifest-192x192.png +0 -0
- data/app/assets/images/dbwatcher/web-app-manifest-512x512.png +0 -0
- data/app/assets/stylesheets/dbwatcher/application.css +38 -4
- data/app/assets/stylesheets/dbwatcher/components/_tabulator.scss +57 -13
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +14 -18
- data/app/controllers/dbwatcher/api/v1/system_info_controller.rb +1 -1
- data/app/controllers/dbwatcher/dashboard_controller.rb +1 -1
- data/app/views/dbwatcher/dashboard/_overview.html.erb +8 -7
- data/app/views/dbwatcher/sessions/index.html.erb +42 -59
- data/app/views/layouts/dbwatcher/application.html.erb +22 -6
- data/lib/dbwatcher/configuration.rb +51 -74
- data/lib/dbwatcher/logging.rb +23 -1
- data/lib/dbwatcher/services/diagram_analyzers/concerns/activerecord_introspection.rb +60 -0
- data/lib/dbwatcher/services/diagram_analyzers/concerns/association_scope_filtering.rb +60 -0
- data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +62 -36
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/association_extractor.rb +224 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/dataset_builder.rb +226 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_analysis/model_discovery.rb +161 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +27 -514
- data/lib/dbwatcher/services/diagram_data/attribute.rb +22 -83
- data/lib/dbwatcher/services/diagram_data/base.rb +129 -0
- data/lib/dbwatcher/services/diagram_data/entity.rb +23 -72
- data/lib/dbwatcher/services/diagram_data/relationship.rb +15 -66
- data/lib/dbwatcher/services/diagram_generator.rb +35 -69
- data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +23 -9
- data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +16 -22
- data/lib/dbwatcher/services/diagram_strategies/diagram_strategy_helpers.rb +33 -0
- data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +20 -25
- data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +20 -25
- data/lib/dbwatcher/services/diagram_strategies/standard_diagram_strategy.rb +80 -0
- data/lib/dbwatcher/services/diagram_system.rb +14 -1
- data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +2 -0
- data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +2 -2
- data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +4 -14
- data/lib/dbwatcher/services/mermaid_syntax_builder.rb +10 -8
- data/lib/dbwatcher/services/system_info/runtime_info_collector.rb +7 -7
- data/lib/dbwatcher/services/system_info/system_info_collector.rb +3 -3
- data/lib/dbwatcher/services/timeline_data_service/entry_builder.rb +23 -1
- data/lib/dbwatcher/storage/session_storage.rb +2 -2
- data/lib/dbwatcher/storage.rb +1 -1
- data/lib/dbwatcher/version.rb +1 -1
- metadata +20 -2
@@ -35,24 +35,39 @@
|
|
35
35
|
position: sticky;
|
36
36
|
left: 0;
|
37
37
|
z-index: 20;
|
38
|
-
background: #f3f3f3;
|
38
|
+
background: #f3f3f3 !important;
|
39
|
+
background-color: #f3f3f3 !important;
|
39
40
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
41
|
+
will-change: transform;
|
42
|
+
transform: translateZ(0);
|
43
|
+
opacity: 1 !important;
|
44
|
+
transition: opacity 0.1s ease-out;
|
40
45
|
}
|
41
46
|
|
42
47
|
&.sticky-left-1 {
|
43
48
|
position: sticky;
|
44
49
|
left: 60px;
|
45
50
|
z-index: 19;
|
46
|
-
background: #f3f3f3;
|
51
|
+
background: #f3f3f3 !important;
|
52
|
+
background-color: #f3f3f3 !important;
|
47
53
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
54
|
+
will-change: transform;
|
55
|
+
transform: translateZ(0);
|
56
|
+
opacity: 1 !important;
|
57
|
+
transition: opacity 0.1s ease-out;
|
48
58
|
}
|
49
59
|
|
50
60
|
&.sticky-left-2 {
|
51
61
|
position: sticky;
|
52
62
|
left: 108px;
|
53
63
|
z-index: 18;
|
54
|
-
background: #f3f3f3;
|
64
|
+
background: #f3f3f3 !important;
|
65
|
+
background-color: #f3f3f3 !important;
|
55
66
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
|
67
|
+
will-change: transform;
|
68
|
+
transform: translateZ(0);
|
69
|
+
opacity: 1 !important;
|
70
|
+
transition: opacity 0.1s ease-out;
|
56
71
|
}
|
57
72
|
}
|
58
73
|
}
|
@@ -115,25 +130,40 @@
|
|
115
130
|
&.sticky-left-0 {
|
116
131
|
position: sticky;
|
117
132
|
left: 0;
|
118
|
-
background: white;
|
133
|
+
background: white !important;
|
134
|
+
background-color: white !important;
|
119
135
|
z-index: 5;
|
120
136
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
137
|
+
will-change: transform;
|
138
|
+
transform: translateZ(0);
|
139
|
+
opacity: 1 !important;
|
140
|
+
transition: opacity 0.1s ease-out;
|
121
141
|
}
|
122
142
|
|
123
143
|
&.sticky-left-1 {
|
124
144
|
position: sticky;
|
125
145
|
left: 60px;
|
126
|
-
background: white;
|
146
|
+
background: white !important;
|
147
|
+
background-color: white !important;
|
127
148
|
z-index: 4;
|
128
149
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
150
|
+
will-change: transform;
|
151
|
+
transform: translateZ(0);
|
152
|
+
opacity: 1 !important;
|
153
|
+
transition: opacity 0.1s ease-out;
|
129
154
|
}
|
130
155
|
|
131
156
|
&.sticky-left-2 {
|
132
157
|
position: sticky;
|
133
158
|
left: 108px;
|
134
|
-
background: white;
|
159
|
+
background: white !important;
|
160
|
+
background-color: white !important;
|
135
161
|
z-index: 3;
|
136
162
|
box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
|
163
|
+
will-change: transform;
|
164
|
+
transform: translateZ(0);
|
165
|
+
opacity: 1 !important;
|
166
|
+
transition: opacity 0.1s ease-out;
|
137
167
|
}
|
138
168
|
}
|
139
169
|
|
@@ -143,7 +173,9 @@
|
|
143
173
|
&.sticky-left-0,
|
144
174
|
&.sticky-left-1,
|
145
175
|
&.sticky-left-2 {
|
146
|
-
background: #f9fafb;
|
176
|
+
background: #f9fafb !important;
|
177
|
+
background-color: #f9fafb !important;
|
178
|
+
opacity: 1 !important;
|
147
179
|
}
|
148
180
|
}
|
149
181
|
}
|
@@ -151,38 +183,50 @@
|
|
151
183
|
// Operation-specific sticky cell backgrounds
|
152
184
|
&.operation-insert .tabulator-cell {
|
153
185
|
&.sticky-left-0, &.sticky-left-1, &.sticky-left-2 {
|
154
|
-
background
|
186
|
+
background: rgba(16, 185, 129, 0.05) !important;
|
187
|
+
background-color: rgba(16, 185, 129, 0.05) !important;
|
188
|
+
opacity: 1 !important;
|
155
189
|
}
|
156
190
|
}
|
157
191
|
|
158
192
|
&.operation-update .tabulator-cell {
|
159
193
|
&.sticky-left-0, &.sticky-left-1, &.sticky-left-2 {
|
160
|
-
background
|
194
|
+
background: rgba(108, 173, 223, 0.05) !important;
|
195
|
+
background-color: rgba(108, 173, 223, 0.05) !important;
|
196
|
+
opacity: 1 !important;
|
161
197
|
}
|
162
198
|
}
|
163
199
|
|
164
200
|
&.operation-delete .tabulator-cell {
|
165
201
|
&.sticky-left-0, &.sticky-left-1, &.sticky-left-2 {
|
166
|
-
background
|
202
|
+
background: rgba(239, 68, 68, 0.05) !important;
|
203
|
+
background-color: rgba(239, 68, 68, 0.05) !important;
|
204
|
+
opacity: 1 !important;
|
167
205
|
}
|
168
206
|
}
|
169
207
|
|
170
208
|
// Operation-specific sticky cell hover backgrounds
|
171
209
|
&.operation-insert:hover .tabulator-cell {
|
172
210
|
&.sticky-left-0, &.sticky-left-1, &.sticky-left-2 {
|
173
|
-
background
|
211
|
+
background: rgba(16, 185, 129, 0.1) !important;
|
212
|
+
background-color: rgba(16, 185, 129, 0.1) !important;
|
213
|
+
opacity: 1 !important;
|
174
214
|
}
|
175
215
|
}
|
176
216
|
|
177
217
|
&.operation-update:hover .tabulator-cell {
|
178
218
|
&.sticky-left-0, &.sticky-left-1, &.sticky-left-2 {
|
179
|
-
background
|
219
|
+
background: rgba(108, 173, 223, 0.1) !important;
|
220
|
+
background-color: rgba(108, 173, 223, 0.1) !important;
|
221
|
+
opacity: 1 !important;
|
180
222
|
}
|
181
223
|
}
|
182
224
|
|
183
225
|
&.operation-delete:hover .tabulator-cell {
|
184
226
|
&.sticky-left-0, &.sticky-left-1, &.sticky-left-2 {
|
185
|
-
background
|
227
|
+
background: rgba(239, 68, 68, 0.1) !important;
|
228
|
+
background-color: rgba(239, 68, 68, 0.1) !important;
|
229
|
+
opacity: 1 !important;
|
186
230
|
}
|
187
231
|
}
|
188
232
|
}
|
@@ -8,32 +8,23 @@ module Dbwatcher
|
|
8
8
|
|
9
9
|
def tables_data
|
10
10
|
Rails.logger.info "API::V1::SessionsController#tables_data: Getting tables for session #{@session.id}"
|
11
|
-
|
12
|
-
# Paginated, filtered tables data
|
13
|
-
# Convert ActionController::Parameters to a hash before passing to service
|
14
|
-
service = Dbwatcher::Services::Api::TablesDataService.new(@session, filter_params.to_h)
|
11
|
+
service = Dbwatcher::Services::Api::TablesDataService.new(@session, tables_data_params)
|
15
12
|
render json: service.call
|
16
13
|
end
|
17
14
|
|
18
15
|
def summary_data
|
19
16
|
Rails.logger.info "API::V1::SessionsController#summary_data: Getting summary for session #{@session.id}"
|
20
|
-
|
21
|
-
# Aggregated summary statistics
|
22
17
|
service = Dbwatcher::Services::Api::SummaryDataService.new(@session)
|
23
18
|
render json: service.call
|
24
19
|
end
|
25
20
|
|
26
21
|
def diagram_data
|
27
22
|
Rails.logger.info "API::V1::SessionsController#diagram_data: Getting diagram for session #{@session.id}"
|
28
|
-
|
29
|
-
# Generated diagram content with caching
|
30
|
-
# Convert ActionController::Parameters to a hash before passing to service
|
31
|
-
diagram_params = params.to_unsafe_h
|
32
23
|
service = Dbwatcher::Services::Api::DiagramDataService.new(@session, params[:type], diagram_params)
|
33
24
|
result = service.call
|
34
25
|
|
35
26
|
if result[:error]
|
36
|
-
|
27
|
+
render_error(result[:error])
|
37
28
|
else
|
38
29
|
render json: result
|
39
30
|
end
|
@@ -41,13 +32,11 @@ module Dbwatcher
|
|
41
32
|
|
42
33
|
def timeline_data
|
43
34
|
Rails.logger.info "API::V1::SessionsController#timeline_data: Getting timeline for session #{@session.id}"
|
44
|
-
|
45
|
-
# Timeline data processed from session changes
|
46
35
|
service = Dbwatcher::Services::TimelineDataService.new(@session)
|
47
36
|
result = service.call
|
48
37
|
|
49
38
|
if result[:errors].any?
|
50
|
-
|
39
|
+
render_error(result[:errors].first[:message])
|
51
40
|
else
|
52
41
|
render json: result
|
53
42
|
end
|
@@ -55,7 +44,6 @@ module Dbwatcher
|
|
55
44
|
|
56
45
|
def diagram_types
|
57
46
|
Rails.logger.info "API::V1::SessionsController#diagram_types: Getting available diagram types"
|
58
|
-
|
59
47
|
render json: {
|
60
48
|
types: Dbwatcher::Services::Api::DiagramDataService.available_types_with_metadata,
|
61
49
|
default_type: "database_tables"
|
@@ -66,11 +54,19 @@ module Dbwatcher
|
|
66
54
|
|
67
55
|
def find_session
|
68
56
|
@session = Storage.sessions.find(params[:id])
|
69
|
-
|
57
|
+
render_error("Session not found", :not_found) unless @session
|
58
|
+
end
|
59
|
+
|
60
|
+
def tables_data_params
|
61
|
+
params.permit(:id, :table, :operation, :page, :per_page, session: {}).to_h
|
62
|
+
end
|
63
|
+
|
64
|
+
def diagram_params
|
65
|
+
params.permit(:type, :format, :include_columns, :show_relationships, session: {}).to_h
|
70
66
|
end
|
71
67
|
|
72
|
-
def
|
73
|
-
|
68
|
+
def render_error(message, status = :unprocessable_entity)
|
69
|
+
render json: { error: message }, status: status
|
74
70
|
end
|
75
71
|
end
|
76
72
|
end
|
@@ -166,7 +166,7 @@ module Dbwatcher
|
|
166
166
|
#
|
167
167
|
# @return [void]
|
168
168
|
def ensure_system_info_enabled
|
169
|
-
return if Dbwatcher.configuration.
|
169
|
+
return if Dbwatcher.configuration.system_info
|
170
170
|
|
171
171
|
render json: {
|
172
172
|
error: "System information collection is disabled",
|
@@ -10,7 +10,7 @@ module Dbwatcher
|
|
10
10
|
@active_tab = params[:tab] || "overview"
|
11
11
|
|
12
12
|
# Add system information if enabled
|
13
|
-
return unless Dbwatcher.configuration.
|
13
|
+
return unless Dbwatcher.configuration.system_info
|
14
14
|
|
15
15
|
@system_info_summary = system_info_storage.summary
|
16
16
|
@system_info = system_info_storage.cached_info
|
@@ -118,10 +118,11 @@
|
|
118
118
|
</div>
|
119
119
|
<% else %>
|
120
120
|
<div class="p-8 text-center text-gray-500">
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
<%= image_tag "dbwatcher/dbwatcher-tranparent_512x512.png",
|
122
|
+
alt: "DBWatcher Logo",
|
123
|
+
class: "w-12 h-12 mx-auto opacity-30 mb-2" %>
|
124
124
|
<p class="text-sm">No recent sessions</p>
|
125
|
+
<p class="text-xs text-gray-400 mt-1">Start tracking with <code>?dbwatch=true</code></p>
|
125
126
|
</div>
|
126
127
|
<% end %>
|
127
128
|
</div>
|
@@ -175,11 +176,11 @@
|
|
175
176
|
</div>
|
176
177
|
<% else %>
|
177
178
|
<div class="p-8 text-center text-gray-500">
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
</svg>
|
179
|
+
<%= image_tag "dbwatcher/dbwatcher-tranparent_512x512.png",
|
180
|
+
alt: "DBWatcher Logo",
|
181
|
+
class: "w-12 h-12 mx-auto opacity-30 mb-2" %>
|
182
182
|
<p class="text-sm">No active tables</p>
|
183
|
+
<p class="text-xs text-gray-400 mt-1">Tables will appear when data changes</p>
|
183
184
|
</div>
|
184
185
|
<% end %>
|
185
186
|
</div>
|
@@ -41,46 +41,39 @@
|
|
41
41
|
</div>
|
42
42
|
|
43
43
|
<!-- Content Area -->
|
44
|
-
<div class="flex-1 overflow-auto
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
</
|
53
|
-
<p class="text-xs">No tracking sessions yet</p>
|
54
|
-
<p class="text-xs text-gray-400">Start tracking with <code class="bg-gray-200 px-1 rounded">Dbwatcher.track { ... }</code></p>
|
44
|
+
<div class="flex-1 overflow-auto">
|
45
|
+
<% if @sessions.empty? %>
|
46
|
+
<div class="p-8 text-center text-gray-500">
|
47
|
+
<%= image_tag "dbwatcher/dbwatcher-tranparent_512x512.png",
|
48
|
+
alt: "DBWatcher Logo",
|
49
|
+
class: "mx-auto w-12 h-12 opacity-30 mb-3" %>
|
50
|
+
<p class="text-sm font-medium text-gray-600 mb-2">No tracking sessions yet</p>
|
51
|
+
<p class="text-xs text-gray-400">Start tracking with <code class="bg-gray-100 px-1 rounded text-xs">Dbwatcher.track { ... }</code></p>
|
52
|
+
<p class="text-xs text-gray-400 mt-1">or add <code class="bg-gray-100 px-1 rounded text-xs">?dbwatch=true</code> to any URL</p>
|
55
53
|
</div>
|
56
|
-
|
57
|
-
|
58
|
-
<div class="bg-white border border-gray-300 rounded shadow-sm">
|
59
|
-
<table class="compact-table sessions-table w-full">
|
54
|
+
<% else %>
|
55
|
+
<table class="compact-table w-full">
|
60
56
|
<thead>
|
61
57
|
<tr>
|
62
|
-
<th class="text-left" style="
|
63
|
-
<th class="text-left" style="
|
64
|
-
<th class="text-center" style="width:100px">Status</th>
|
65
|
-
<th class="text-center" style="width:100px">Changes</th>
|
66
|
-
<th class="text-right" style="width:120px">Started</th>
|
67
|
-
<th class="text-right" style="width:120px">Duration</th>
|
68
|
-
<th class="text-center" style="width:80px">Actions</th>
|
58
|
+
<th class="text-left" style="width: 18%; min-width: 180px">Session ID</th>
|
59
|
+
<th class="text-left" style="width: 22%; min-width: 160px">Name</th>
|
60
|
+
<th class="text-center" style="width: 100px">Status</th>
|
61
|
+
<th class="text-center" style="width: 100px">Changes</th>
|
62
|
+
<th class="text-right" style="width: 120px">Started</th>
|
63
|
+
<th class="text-right" style="width: 120px">Duration</th>
|
64
|
+
<th class="text-center" style="width: 80px">Actions</th>
|
69
65
|
</tr>
|
70
66
|
</thead>
|
71
67
|
<tbody>
|
72
68
|
<% @sessions.each do |session| %>
|
73
69
|
<tr class="session-row">
|
74
|
-
<td
|
75
|
-
|
76
|
-
<%= safe_value(session, :id) %>
|
77
|
-
</span>
|
70
|
+
<td class="font-mono text-xs">
|
71
|
+
<%= safe_value(session, :id) %>
|
78
72
|
</td>
|
79
|
-
<td
|
73
|
+
<td title="<%= safe_value(session, :name) %>">
|
80
74
|
<%= link_to display_session_name(safe_value(session, :name)),
|
81
75
|
session_path(safe_value(session, :id)),
|
82
|
-
class: "text-navy-dark hover:text-blue-medium
|
83
|
-
style: "max-width:260px; overflow-x:auto; display:inline-block;" %>
|
76
|
+
class: "text-navy-dark hover:text-blue-medium" %>
|
84
77
|
</td>
|
85
78
|
<td class="text-center">
|
86
79
|
<%= render 'dbwatcher/shared/badge',
|
@@ -106,44 +99,34 @@
|
|
106
99
|
) rescue 'N/A' %>
|
107
100
|
<% end %>
|
108
101
|
</td>
|
109
|
-
<td class="text-center
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
title: "View session details" do %>
|
114
|
-
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
115
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
116
|
-
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
117
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
118
|
-
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
119
|
-
</svg>
|
120
|
-
<% end %>
|
121
|
-
</div>
|
102
|
+
<td class="text-center">
|
103
|
+
<%= link_to "View", session_path(safe_value(session, :id)),
|
104
|
+
class: "compact-button bg-navy-dark text-white hover:bg-blue-medium",
|
105
|
+
title: "View session details" %>
|
122
106
|
</td>
|
123
107
|
</tr>
|
124
108
|
<% end %>
|
125
109
|
</tbody>
|
126
110
|
</table>
|
127
|
-
</div>
|
128
111
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
112
|
+
<!-- No Results Message -->
|
113
|
+
<div
|
114
|
+
x-show="filterText && document.querySelectorAll('.session-row:not(.hidden)').length === 0"
|
115
|
+
x-cloak
|
116
|
+
class="p-8 text-center text-gray-500 bg-gray-50">
|
117
|
+
<p class="mb-2">No sessions match your filter criteria</p>
|
118
|
+
<button
|
119
|
+
@click="filterText = ''"
|
120
|
+
class="compact-button bg-gray-500 text-white hover:bg-gray-600">
|
121
|
+
Clear filter
|
122
|
+
</button>
|
123
|
+
</div>
|
124
|
+
<% end %>
|
125
|
+
</div>
|
142
126
|
|
143
127
|
<!-- Status Bar -->
|
144
|
-
<div class="h-6 bg-gray-100 border-t border-gray-300 flex items-center px-4 text-xs text-gray-600
|
145
|
-
<span x-show="!filterText"><%= @sessions.count %> sessions total •
|
146
|
-
<%= @sessions.count { |s| session_active?(s) } %> active</span>
|
128
|
+
<div class="h-6 bg-gray-100 border-t border-gray-300 flex items-center px-4 text-xs text-gray-600">
|
129
|
+
<span x-show="!filterText"><%= @sessions.count %> sessions total • <%= @sessions.count { |s| session_active?(s) } %> active</span>
|
147
130
|
<span x-show="filterText" x-text="`${document.querySelectorAll('.session-row:not(.hidden)').length} of ${<%= @sessions.count %>} sessions shown`"></span>
|
148
131
|
<span class="ml-auto">Last updated: <%= Time.current.strftime("%H:%M:%S") %></span>
|
149
132
|
</div>
|
@@ -5,6 +5,14 @@
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
6
|
<%= csrf_meta_tags %>
|
7
7
|
|
8
|
+
<!-- Favicon -->
|
9
|
+
<%= favicon_link_tag asset_path('dbwatcher/favicon.ico') %>
|
10
|
+
<%= favicon_link_tag asset_path('dbwatcher/favicon-96x96.png'), sizes: '96x96', type: 'image/png' %>
|
11
|
+
<%= favicon_link_tag asset_path('dbwatcher/apple-touch-icon.png'), rel: 'apple-touch-icon', sizes: '180x180' %>
|
12
|
+
<%= favicon_link_tag asset_path('dbwatcher/web-app-manifest-192x192.png'), sizes: '192x192', type: 'image/png' %>
|
13
|
+
<%= favicon_link_tag asset_path('dbwatcher/web-app-manifest-512x512.png'), sizes: '512x512', type: 'image/png' %>
|
14
|
+
<link rel="manifest" href="<%= asset_path('dbwatcher/site.webmanifest') %>"
|
15
|
+
|
8
16
|
<!-- Alpine.js and Plugins - ensure plugins load BEFORE Alpine.js core -->
|
9
17
|
<script defer src="https://unpkg.com/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
|
10
18
|
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
@@ -119,12 +127,20 @@
|
|
119
127
|
:style="{ width: sidebarCollapsed ? '48px' : sidebarWidth + 'px' }">
|
120
128
|
<div class="flex flex-col h-full">
|
121
129
|
<!-- Logo -->
|
122
|
-
<div class="h-
|
123
|
-
<div class="flex items-center gap-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
<
|
130
|
+
<div class="h-12 flex items-center justify-between px-3 border-b border-gray-700 bg-gray-800">
|
131
|
+
<div class="flex items-center gap-3" x-show="!sidebarCollapsed">
|
132
|
+
<%= image_tag "dbwatcher/dbwatcher-tranparent_512x512.png",
|
133
|
+
alt: "DBWatcher Logo",
|
134
|
+
class: "w-10 h-10" %>
|
135
|
+
<div class="flex flex-col">
|
136
|
+
<span class="text-base font-bold text-white">dbwatcher</span>
|
137
|
+
</div>
|
138
|
+
</div>
|
139
|
+
<!-- Collapsed state logo -->
|
140
|
+
<div class="flex items-center justify-center w-full" x-show="sidebarCollapsed">
|
141
|
+
<%= image_tag "dbwatcher/dbwatcher-tranparent_512x512.png",
|
142
|
+
alt: "dbwatcher Logo",
|
143
|
+
class: "w-8 h-8" %>
|
128
144
|
</div>
|
129
145
|
<button @click="sidebarCollapsed = !sidebarCollapsed"
|
130
146
|
class="p-1 hover:bg-gray-800 rounded text-gray-400">
|
@@ -3,93 +3,84 @@
|
|
3
3
|
module Dbwatcher
|
4
4
|
# Configuration class for DBWatcher
|
5
5
|
#
|
6
|
-
#
|
7
|
-
# storage, tracking, and diagram visualization settings.
|
6
|
+
# Simplified configuration with logical groupings and sensible defaults
|
8
7
|
class Configuration
|
9
|
-
#
|
10
|
-
attr_accessor :
|
8
|
+
# Core settings - what users need most
|
9
|
+
attr_accessor :enabled, :storage_path
|
11
10
|
|
12
|
-
#
|
13
|
-
attr_accessor :
|
11
|
+
# Session management - how data is stored and cleaned
|
12
|
+
attr_accessor :max_sessions, :auto_clean_days
|
14
13
|
|
15
|
-
#
|
16
|
-
attr_accessor :
|
14
|
+
# Query tracking - performance monitoring
|
15
|
+
attr_accessor :track_queries
|
17
16
|
|
18
|
-
#
|
19
|
-
attr_accessor :
|
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
|
17
|
+
# System info - debugging and monitoring
|
18
|
+
attr_accessor :system_info, :debug_mode
|
23
19
|
|
24
|
-
#
|
25
|
-
attr_accessor :
|
26
|
-
:
|
27
|
-
:
|
20
|
+
# Advanced diagram options - available but not commonly needed
|
21
|
+
attr_accessor :diagram_show_methods, :diagram_max_attributes,
|
22
|
+
:diagram_attribute_types, :diagram_relationship_labels,
|
23
|
+
:diagram_show_attributes, :diagram_show_cardinality
|
28
24
|
|
29
25
|
# Initialize with default values
|
30
26
|
def initialize
|
31
|
-
#
|
32
|
-
@storage_path = default_storage_path
|
27
|
+
# Core settings
|
33
28
|
@enabled = true
|
29
|
+
@storage_path = default_storage_path
|
30
|
+
|
31
|
+
# Session management
|
34
32
|
@max_sessions = 50
|
35
|
-
@
|
33
|
+
@auto_clean_days = 7
|
36
34
|
|
37
|
-
# Query tracking
|
35
|
+
# Query tracking
|
38
36
|
@track_queries = false
|
39
|
-
@slow_query_threshold = 200 # milliseconds
|
40
|
-
@max_query_logs_per_day = 1000
|
41
37
|
|
42
|
-
#
|
43
|
-
@
|
38
|
+
# System info
|
39
|
+
@system_info = true
|
40
|
+
@debug_mode = false
|
44
41
|
|
45
|
-
#
|
46
|
-
|
42
|
+
# Advanced diagram options - sensible defaults
|
43
|
+
@diagram_show_methods = false
|
44
|
+
@diagram_max_attributes = 10
|
45
|
+
@diagram_attribute_types = true
|
46
|
+
@diagram_relationship_labels = true
|
47
|
+
@diagram_show_attributes = true
|
48
|
+
@diagram_show_cardinality = true
|
49
|
+
end
|
47
50
|
|
48
|
-
|
49
|
-
|
51
|
+
# Fixed defaults for options that are still used in codebase but not configurable
|
52
|
+
def slow_query_threshold
|
53
|
+
200 # Fixed default value
|
50
54
|
end
|
51
55
|
|
52
|
-
|
53
|
-
|
54
|
-
@diagram_show_attributes = true
|
55
|
-
@diagram_show_methods = false # Hide methods by default
|
56
|
-
@diagram_show_cardinality = true
|
57
|
-
@diagram_max_attributes = 10
|
58
|
-
@diagram_attribute_types = true # Changed from array to boolean
|
59
|
-
@diagram_relationship_labels = true # Changed from symbol to boolean
|
60
|
-
@diagram_preserve_table_case = false # Changed from true to false
|
61
|
-
@diagram_direction = "LR" # Left to right by default
|
62
|
-
@diagram_cardinality_format = :simple # Use simpler 1:N format
|
63
|
-
@diagram_show_attribute_count = true
|
64
|
-
@diagram_show_method_count = true
|
56
|
+
def diagram_direction
|
57
|
+
"LR" # Fixed default value
|
65
58
|
end
|
66
59
|
|
67
|
-
#
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
60
|
+
# Fixed defaults for complex options that are still used in codebase but not configurable
|
61
|
+
def max_query_logs_per_day = 1000
|
62
|
+
def system_info_refresh_interval = 5 * 60
|
63
|
+
def system_info_cache_duration = 60 * 60
|
64
|
+
|
65
|
+
def collect_sensitive_env_vars?
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def system_info_include_performance_metrics?
|
70
|
+
true
|
74
71
|
end
|
75
72
|
|
76
73
|
# Validate configuration
|
77
|
-
#
|
78
|
-
# @return [Boolean] true if configuration is valid
|
79
74
|
def valid?
|
80
75
|
validate_storage_path
|
81
76
|
validate_max_sessions
|
82
|
-
|
83
|
-
validate_slow_query_threshold
|
84
|
-
validate_max_query_logs_per_day
|
77
|
+
validate_auto_clean_days
|
85
78
|
true
|
86
79
|
end
|
87
80
|
|
88
81
|
private
|
89
82
|
|
90
83
|
# Default storage path based on Rails or current directory
|
91
|
-
#
|
92
|
-
# @return [String] default storage path
|
93
84
|
def default_storage_path
|
94
85
|
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
95
86
|
Rails.root.join("tmp", "dbwatcher").to_s
|
@@ -116,25 +107,11 @@ module Dbwatcher
|
|
116
107
|
raise ConfigurationError, "max_sessions must be a positive integer"
|
117
108
|
end
|
118
109
|
|
119
|
-
# Validate auto clean
|
120
|
-
def
|
121
|
-
return if
|
122
|
-
|
123
|
-
raise ConfigurationError, "auto_clean_after_days must be a positive integer"
|
124
|
-
end
|
125
|
-
|
126
|
-
# Validate slow query threshold
|
127
|
-
def validate_slow_query_threshold
|
128
|
-
return if slow_query_threshold.is_a?(Integer) && slow_query_threshold.positive?
|
129
|
-
|
130
|
-
raise ConfigurationError, "slow_query_threshold must be a positive integer"
|
131
|
-
end
|
132
|
-
|
133
|
-
# Validate max query logs per day
|
134
|
-
def validate_max_query_logs_per_day
|
135
|
-
return if max_query_logs_per_day.is_a?(Integer) && max_query_logs_per_day.positive?
|
110
|
+
# Validate auto clean days
|
111
|
+
def validate_auto_clean_days
|
112
|
+
return if auto_clean_days.is_a?(Integer) && auto_clean_days.positive?
|
136
113
|
|
137
|
-
raise ConfigurationError, "
|
114
|
+
raise ConfigurationError, "auto_clean_days must be a positive integer"
|
138
115
|
end
|
139
116
|
end
|
140
117
|
|