dbwatcher 1.1.0 → 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/diagram_analyzers/model_association_analyzer.rb +177 -138
- data/lib/dbwatcher/services/diagram_data/dataset.rb +2 -0
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_builder.rb +13 -9
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_helper.rb +3 -1
- data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +17 -1
- 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
@@ -1,265 +0,0 @@
|
|
1
|
-
<!-- Changes Content - Hybrid Tabulator Implementation with Original UI Structure -->
|
2
|
-
<div class="h-full"
|
3
|
-
x-data="changesTableHybrid({ sessionId: '<%= @session.id %>' })">
|
4
|
-
|
5
|
-
<!-- Loading State -->
|
6
|
-
<div x-show="loading" class="flex items-center justify-center h-64">
|
7
|
-
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
8
|
-
<span class="ml-2 text-gray-600">Loading changes...</span>
|
9
|
-
</div>
|
10
|
-
|
11
|
-
<!-- Error State -->
|
12
|
-
<div x-show="error" class="p-4 bg-red-50 border border-red-200 rounded">
|
13
|
-
<p class="text-red-700" x-text="error"></p>
|
14
|
-
<button @click="loadChangesData()" class="mt-2 text-red-600 underline">Retry</button>
|
15
|
-
</div>
|
16
|
-
|
17
|
-
<!-- No Data State -->
|
18
|
-
<div x-show="!loading && !error && Object.keys(tableData).length === 0"
|
19
|
-
class="flex flex-col items-center justify-center h-64">
|
20
|
-
<svg class="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
21
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
22
|
-
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
23
|
-
</svg>
|
24
|
-
<p class="mt-2 text-gray-500">No changes data available</p>
|
25
|
-
</div>
|
26
|
-
|
27
|
-
<!-- Multiple Tables - Original UI Structure -->
|
28
|
-
<template x-if="!loading && !error && Object.keys(tableData).length > 0">
|
29
|
-
<div class="h-full overflow-auto">
|
30
|
-
<template x-for="[tableName, tableInfo] in Object.entries(tableData)" :key="tableName">
|
31
|
-
<div class="border-b border-gray-300" x-data="{ expanded: true }">
|
32
|
-
<!-- Table Header with Column Controls -->
|
33
|
-
<div class="bg-gray-100 px-3 py-2 flex items-center cursor-pointer"
|
34
|
-
@click="expanded = !expanded"
|
35
|
-
:id="`table-${tableName}`">
|
36
|
-
<svg class="w-3 h-3 mr-2 transition-transform"
|
37
|
-
:class="{ 'rotate-90': expanded }"
|
38
|
-
fill="currentColor" viewBox="0 0 20 20">
|
39
|
-
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 111.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
40
|
-
</svg>
|
41
|
-
<h3 class="text-sm font-medium text-gray-900 flex-1" x-text="tableName"></h3>
|
42
|
-
<div class="flex gap-2 mr-4">
|
43
|
-
<template x-for="[op, count] in Object.entries(tableInfo.operations || {})" :key="op">
|
44
|
-
<span x-show="count > 0" class="badge" :class="`badge-${op.toLowerCase()}`" x-text="count"></span>
|
45
|
-
</template>
|
46
|
-
</div>
|
47
|
-
|
48
|
-
<!-- Column Visibility Button -->
|
49
|
-
<button @click.stop="toggleColumnSelector(tableName)"
|
50
|
-
class="text-xs bg-white border border-gray-300 px-2 py-1 rounded hover:bg-gray-50 relative">
|
51
|
-
Columns
|
52
|
-
</button>
|
53
|
-
</div>
|
54
|
-
|
55
|
-
<!-- Column Selector Dropdown -->
|
56
|
-
<div x-show="showColumnSelector === tableName"
|
57
|
-
x-transition
|
58
|
-
@click.away="showColumnSelector = null"
|
59
|
-
class="absolute z-50 bg-white border border-gray-300 rounded shadow-lg p-3 max-h-64 overflow-auto"
|
60
|
-
style="right: 1rem; margin-top: -2px;">
|
61
|
-
<div class="text-xs font-medium mb-2">Select Visible Columns:</div>
|
62
|
-
<div class="space-y-1 min-w-48">
|
63
|
-
<template x-for="column in tableInfo.columns" :key="column">
|
64
|
-
<label class="flex items-center text-xs hover:bg-gray-50 p-1 rounded">
|
65
|
-
<input type="checkbox"
|
66
|
-
:checked="isColumnVisible(tableName, column)"
|
67
|
-
@change="toggleColumnVisibility(tableName, column)"
|
68
|
-
class="mr-2">
|
69
|
-
<span class="flex-1" x-text="column"></span>
|
70
|
-
</label>
|
71
|
-
</template>
|
72
|
-
</div>
|
73
|
-
<div class="mt-2 pt-2 border-t border-gray-200 flex gap-1">
|
74
|
-
<button @click="selectAllColumns(tableName)"
|
75
|
-
class="text-xs bg-blue-600 text-white px-2 py-1 rounded hover:bg-blue-700">All</button>
|
76
|
-
<button @click="selectNoneColumns(tableName)"
|
77
|
-
class="text-xs bg-gray-600 text-white px-2 py-1 rounded hover:bg-gray-700">None</button>
|
78
|
-
</div>
|
79
|
-
</div>
|
80
|
-
|
81
|
-
<!-- Tabulator Container for This Table -->
|
82
|
-
<div x-show="expanded" x-collapse>
|
83
|
-
<div :id="`changes-table-${tableName}`" class="table-container"></div>
|
84
|
-
</div>
|
85
|
-
</div>
|
86
|
-
</template>
|
87
|
-
</div>
|
88
|
-
</template>
|
89
|
-
</div>
|
90
|
-
|
91
|
-
<!-- Custom Tabulator Styles -->
|
92
|
-
<style>
|
93
|
-
/* Override Tabulator styles to match current design */
|
94
|
-
.tabulator {
|
95
|
-
font-family: 'Consolas', 'Monaco', 'Lucida Console', monospace !important;
|
96
|
-
font-size: 12px !important;
|
97
|
-
border: none;
|
98
|
-
background: white;
|
99
|
-
border-collapse: separate;
|
100
|
-
border-spacing: 0;
|
101
|
-
}
|
102
|
-
|
103
|
-
.tabulator .tabulator-header {
|
104
|
-
background: #f3f3f3 !important;
|
105
|
-
border-bottom: 2px solid #e8e8e8 !important;
|
106
|
-
font-size: 11px !important;
|
107
|
-
}
|
108
|
-
|
109
|
-
.tabulator .tabulator-header .tabulator-col {
|
110
|
-
background: #f3f3f3 !important;
|
111
|
-
border-right: 1px solid #e8e8e8 !important;
|
112
|
-
padding: 4px 8px !important;
|
113
|
-
font-weight: 500 !important;
|
114
|
-
text-transform: none !important;
|
115
|
-
height: 32px !important;
|
116
|
-
text-align: left;
|
117
|
-
position: sticky;
|
118
|
-
top: 0;
|
119
|
-
z-index: 10;
|
120
|
-
}
|
121
|
-
|
122
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row {
|
123
|
-
background: white !important;
|
124
|
-
border-bottom: 1px solid #f0f0f0 !important;
|
125
|
-
min-height: auto;
|
126
|
-
}
|
127
|
-
|
128
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover {
|
129
|
-
background: #f3f4f6 !important;
|
130
|
-
}
|
131
|
-
|
132
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell {
|
133
|
-
border-right: 1px solid #f0f0f0 !important;
|
134
|
-
padding: 2px 8px !important;
|
135
|
-
overflow: hidden;
|
136
|
-
text-overflow: ellipsis;
|
137
|
-
white-space: nowrap;
|
138
|
-
vertical-align: top;
|
139
|
-
font-size: 12px !important;
|
140
|
-
height: auto;
|
141
|
-
min-height: 28px;
|
142
|
-
}
|
143
|
-
|
144
|
-
/* Override for UPDATE operation cells to allow multi-line */
|
145
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell:has(.space-y-1) {
|
146
|
-
white-space: normal;
|
147
|
-
height: auto;
|
148
|
-
padding: 4px 8px !important;
|
149
|
-
}
|
150
|
-
|
151
|
-
/* Maintain sticky columns exactly as current */
|
152
|
-
.tabulator .tabulator-header .tabulator-col.sticky-left-0 {
|
153
|
-
position: sticky !important;
|
154
|
-
left: 0 !important;
|
155
|
-
z-index: 20 !important;
|
156
|
-
background: #f3f3f3 !important;
|
157
|
-
box-shadow: 2px 0 4px rgba(0,0,0,0.1) !important;
|
158
|
-
}
|
159
|
-
|
160
|
-
.tabulator .tabulator-header .tabulator-col.sticky-left-1 {
|
161
|
-
position: sticky !important;
|
162
|
-
left: 60px !important;
|
163
|
-
z-index: 19 !important;
|
164
|
-
background: #f3f3f3 !important;
|
165
|
-
box-shadow: 2px 0 4px rgba(0,0,0,0.1) !important;
|
166
|
-
}
|
167
|
-
|
168
|
-
.tabulator .tabulator-header .tabulator-col.sticky-left-2 {
|
169
|
-
position: sticky !important;
|
170
|
-
left: 108px !important;
|
171
|
-
z-index: 18 !important;
|
172
|
-
background: #f3f3f3 !important;
|
173
|
-
box-shadow: 2px 0 4px rgba(0,0,0,0.1) !important;
|
174
|
-
}
|
175
|
-
|
176
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell.sticky-left-0 {
|
177
|
-
position: sticky !important;
|
178
|
-
left: 0 !important;
|
179
|
-
background: white !important;
|
180
|
-
z-index: 5 !important;
|
181
|
-
box-shadow: 2px 0 4px rgba(0,0,0,0.05) !important;
|
182
|
-
}
|
183
|
-
|
184
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell.sticky-left-1 {
|
185
|
-
position: sticky !important;
|
186
|
-
left: 60px !important;
|
187
|
-
background: white !important;
|
188
|
-
z-index: 4 !important;
|
189
|
-
box-shadow: 2px 0 4px rgba(0,0,0,0.05) !important;
|
190
|
-
}
|
191
|
-
|
192
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row .tabulator-cell.sticky-left-2 {
|
193
|
-
position: sticky !important;
|
194
|
-
left: 108px !important;
|
195
|
-
background: white !important;
|
196
|
-
z-index: 3 !important;
|
197
|
-
box-shadow: 2px 0 4px rgba(0,0,0,0.05) !important;
|
198
|
-
}
|
199
|
-
|
200
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover .tabulator-cell.sticky-left-0,
|
201
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover .tabulator-cell.sticky-left-1,
|
202
|
-
.tabulator .tabulator-tableholder .tabulator-table .tabulator-row:hover .tabulator-cell.sticky-left-2 {
|
203
|
-
background: #f9fafb !important;
|
204
|
-
}
|
205
|
-
|
206
|
-
/* Operation badges */
|
207
|
-
.badge-insert { background: #10b981; color: white; }
|
208
|
-
.badge-update { background: #6CADDF; color: white; }
|
209
|
-
.badge-delete { background: #ef4444; color: white; }
|
210
|
-
.badge-select { background: #6b7280; color: white; }
|
211
|
-
|
212
|
-
.badge {
|
213
|
-
padding: 1px 6px;
|
214
|
-
font-size: 10px;
|
215
|
-
border-radius: 3px;
|
216
|
-
font-weight: 500;
|
217
|
-
text-transform: uppercase;
|
218
|
-
display: inline-block;
|
219
|
-
width: 18px;
|
220
|
-
height: 18px;
|
221
|
-
line-height: 18px;
|
222
|
-
text-align: center;
|
223
|
-
}
|
224
|
-
|
225
|
-
/* Row detail styling */
|
226
|
-
.row-detail {
|
227
|
-
border-top: 1px solid #e5e7eb;
|
228
|
-
background: #f9fafb;
|
229
|
-
padding: 0;
|
230
|
-
}
|
231
|
-
|
232
|
-
.row-detail td {
|
233
|
-
padding: 8px !important;
|
234
|
-
vertical-align: top !important;
|
235
|
-
border-right: 1px solid #e5e7eb !important;
|
236
|
-
}
|
237
|
-
|
238
|
-
.row-detail h4 {
|
239
|
-
font-weight: 600;
|
240
|
-
color: #374151;
|
241
|
-
margin-bottom: 8px;
|
242
|
-
}
|
243
|
-
|
244
|
-
/* Maintain sticky positioning in expanded rows - combined first 3 columns */
|
245
|
-
.row-detail .sticky-left-0 {
|
246
|
-
position: sticky !important;
|
247
|
-
left: 0 !important;
|
248
|
-
z-index: 5 !important; /* Lower z-index to prevent overlay issues */
|
249
|
-
background: #f9fafb !important; /* Match row-detail background */
|
250
|
-
box-shadow: 2px 0 4px rgba(0,0,0,0.1) !important;
|
251
|
-
width: 268px !important;
|
252
|
-
min-width: 268px !important;
|
253
|
-
}
|
254
|
-
|
255
|
-
/* Expand button styling */
|
256
|
-
.expand-btn {
|
257
|
-
padding: 2px;
|
258
|
-
border-radius: 2px;
|
259
|
-
transition: all 0.15s ease;
|
260
|
-
}
|
261
|
-
|
262
|
-
.expand-btn:hover {
|
263
|
-
background-color: #f3f4f6;
|
264
|
-
}
|
265
|
-
</style>
|
@@ -1,12 +0,0 @@
|
|
1
|
-
<!-- Tab Bar -->
|
2
|
-
<div class="tab-bar">
|
3
|
-
<div class="tab-item"
|
4
|
-
:class="{ active: activeTab === 'changes' }"
|
5
|
-
@click="setActiveTab('changes')">Changes</div>
|
6
|
-
<div class="tab-item"
|
7
|
-
:class="{ active: activeTab === 'summary' }"
|
8
|
-
@click="setActiveTab('summary')">Summary</div>
|
9
|
-
<div class="tab-item"
|
10
|
-
:class="{ active: activeTab === 'diagrams' }"
|
11
|
-
@click="setActiveTab('diagrams')">Diagrams</div>
|
12
|
-
</div>
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<div class="h-full flex flex-col">
|
2
|
-
<%= render partial: 'session_header', locals: { session: @session } %>
|
3
|
-
|
4
|
-
<!-- Tab Bar with URL-based navigation -->
|
5
|
-
<div class="tab-bar">
|
6
|
-
<%= link_to changes_session_path(@session.id), class: "tab-item active" do %>
|
7
|
-
Changes
|
8
|
-
<% end %>
|
9
|
-
<%= link_to summary_session_path(@session.id), class: "tab-item" do %>
|
10
|
-
Summary
|
11
|
-
<% end %>
|
12
|
-
<%= link_to diagrams_session_path(@session.id), class: "tab-item" do %>
|
13
|
-
Diagrams
|
14
|
-
<% end %>
|
15
|
-
</div>
|
16
|
-
|
17
|
-
<!-- Changes Content -->
|
18
|
-
<div class="flex-1 overflow-auto">
|
19
|
-
<%= render partial: 'changes_tab', locals: { tables_summary: @tables_summary } %>
|
20
|
-
</div>
|
21
|
-
</div>
|
@@ -1,44 +0,0 @@
|
|
1
|
-
<div class="flex flex-wrap items-center gap-4">
|
2
|
-
<div class="flex items-center gap-2">
|
3
|
-
<label for="table-filter" class="text-sm font-medium text-gray-700">Table:</label>
|
4
|
-
<select id="table-filter"
|
5
|
-
x-model="filters.table"
|
6
|
-
@change="applyFilters()"
|
7
|
-
class="compact-select">
|
8
|
-
<option value="">All Tables</option>
|
9
|
-
<% if defined?(tables_summary) && tables_summary %>
|
10
|
-
<% tables_summary.keys.each do |table_name| %>
|
11
|
-
<option value="<%= table_name %>" <%= 'selected' if filters && filters[:table] == table_name %>><%= table_name %></option>
|
12
|
-
<% end %>
|
13
|
-
<% end %>
|
14
|
-
</select>
|
15
|
-
</div>
|
16
|
-
|
17
|
-
<div class="flex items-center gap-2">
|
18
|
-
<label for="operation-filter" class="text-sm font-medium text-gray-700">Operation:</label>
|
19
|
-
<select id="operation-filter"
|
20
|
-
x-model="filters.operation"
|
21
|
-
@change="applyFilters()"
|
22
|
-
class="compact-select">
|
23
|
-
<option value="">All Operations</option>
|
24
|
-
<option value="INSERT" <%= 'selected' if filters && filters[:operation] == 'INSERT' %>>INSERT</option>
|
25
|
-
<option value="UPDATE" <%= 'selected' if filters && filters[:operation] == 'UPDATE' %>>UPDATE</option>
|
26
|
-
<option value="DELETE" <%= 'selected' if filters && filters[:operation] == 'DELETE' %>>DELETE</option>
|
27
|
-
</select>
|
28
|
-
</div>
|
29
|
-
|
30
|
-
<div class="flex items-center gap-2">
|
31
|
-
<button @click="clearFilters()"
|
32
|
-
class="text-sm text-gray-500 hover:text-gray-700 underline">
|
33
|
-
Clear Filters
|
34
|
-
</button>
|
35
|
-
</div>
|
36
|
-
|
37
|
-
<div class="ml-auto flex items-center gap-2" x-show="loading">
|
38
|
-
<svg class="animate-spin h-4 w-4 text-blue-500" fill="none" viewBox="0 0 24 24">
|
39
|
-
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
40
|
-
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
41
|
-
</svg>
|
42
|
-
<span class="text-sm text-gray-500">Loading...</span>
|
43
|
-
</div>
|
44
|
-
</div>
|
@@ -1,96 +0,0 @@
|
|
1
|
-
<!-- Table List Navigation -->
|
2
|
-
<div class="bg-white border-b border-gray-200 p-4">
|
3
|
-
<h3 class="text-sm font-medium text-gray-900 mb-3">Tables with Changes</h3>
|
4
|
-
<div class="flex flex-wrap gap-2">
|
5
|
-
<template x-for="[tableName, summary] in Object.entries(tablesData)" :key="tableName">
|
6
|
-
<button
|
7
|
-
@click="scrollToTable(tableName)"
|
8
|
-
class="px-3 py-1 text-xs bg-gray-100 hover:bg-gray-200 rounded-md transition-colors">
|
9
|
-
<span x-text="tableName"></span>
|
10
|
-
<span class="ml-1 text-gray-500" x-text="`(${Object.values(summary.changes || {}).reduce((sum, count) => sum + count, 0)})`"></span>
|
11
|
-
</button>
|
12
|
-
</template>
|
13
|
-
</div>
|
14
|
-
</div>
|
15
|
-
|
16
|
-
<!-- Table Details -->
|
17
|
-
<template x-for="[tableName, summary] in Object.entries(tablesData)" :key="tableName">
|
18
|
-
<div :id="`table-${tableName}`" class="border-b border-gray-300" :data-table-name="tableName">
|
19
|
-
<!-- Table Header -->
|
20
|
-
<div class="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
21
|
-
<div class="flex items-center justify-between">
|
22
|
-
<div class="flex items-center space-x-3">
|
23
|
-
<h4 class="text-sm font-medium text-gray-900" x-text="tableName"></h4>
|
24
|
-
<div class="flex space-x-2">
|
25
|
-
<template x-for="[operation, count] in Object.entries(summary.changes || {})" :key="operation">
|
26
|
-
<span :class="getOperationClass(operation)" class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium">
|
27
|
-
<span x-text="`${operation}: ${count}`"></span>
|
28
|
-
</span>
|
29
|
-
</template>
|
30
|
-
</div>
|
31
|
-
</div>
|
32
|
-
<button
|
33
|
-
@click="toggleColumnSelector(tableName)"
|
34
|
-
class="text-sm text-blue-600 hover:text-blue-800">
|
35
|
-
Configure Columns
|
36
|
-
</button>
|
37
|
-
</div>
|
38
|
-
</div>
|
39
|
-
|
40
|
-
<!-- Column Selector -->
|
41
|
-
<div x-show="showColumnSelector === tableName" x-collapse class="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
42
|
-
<div class="flex items-center justify-between mb-2">
|
43
|
-
<span class="text-sm font-medium text-gray-700">Column Visibility</span>
|
44
|
-
<div class="space-x-2">
|
45
|
-
<button @click="selectAllColumns(tableName)" class="text-xs text-blue-600 hover:text-blue-800">All</button>
|
46
|
-
<button @click="selectNoneColumns(tableName)" class="text-xs text-blue-600 hover:text-blue-800">None</button>
|
47
|
-
</div>
|
48
|
-
</div>
|
49
|
-
<div class="grid grid-cols-4 gap-2">
|
50
|
-
<template x-for="[column, visible] in Object.entries(tableColumns[tableName] || {})" :key="column">
|
51
|
-
<label class="flex items-center space-x-2">
|
52
|
-
<input
|
53
|
-
type="checkbox"
|
54
|
-
:checked="visible"
|
55
|
-
@change="tableColumns[tableName][column] = $event.target.checked"
|
56
|
-
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500">
|
57
|
-
<span class="text-xs text-gray-700" x-text="column"></span>
|
58
|
-
</label>
|
59
|
-
</template>
|
60
|
-
</div>
|
61
|
-
</div>
|
62
|
-
|
63
|
-
<!-- Table Content -->
|
64
|
-
<div class="overflow-auto">
|
65
|
-
<table class="compact-table w-full">
|
66
|
-
<thead>
|
67
|
-
<tr class="sticky top-0 bg-gray-100 z-10">
|
68
|
-
<th class="text-center w-16 sticky left-0 bg-gray-100 z-20 border-r border-gray-300">
|
69
|
-
<span class="text-xs">Op</span>
|
70
|
-
</th>
|
71
|
-
<th class="text-left w-24 sticky bg-gray-100 z-20 border-r border-gray-300">Time</th>
|
72
|
-
<template x-for="[column, visible] in Object.entries(tableColumns[tableName] || {})" :key="column">
|
73
|
-
<th x-show="visible" class="text-left min-w-32 px-2" x-text="column"></th>
|
74
|
-
</template>
|
75
|
-
</tr>
|
76
|
-
</thead>
|
77
|
-
<tbody>
|
78
|
-
<template x-for="(change, index) in summary.changes || []" :key="index">
|
79
|
-
<tr class="hover:bg-gray-50">
|
80
|
-
<td class="text-center sticky left-0 bg-white z-10">
|
81
|
-
<span :class="getOperationClass(change.operation)" class="inline-block w-2 h-2 rounded-full"></span>
|
82
|
-
</td>
|
83
|
-
<td class="sticky bg-white z-10" x-text="formatTimestamp(change.timestamp)"></td>
|
84
|
-
<template x-for="[column, visible] in Object.entries(tableColumns[tableName] || {})" :key="column">
|
85
|
-
<td x-show="visible" class="px-2">
|
86
|
-
<span x-text="change.data?.[column] || '--'"></span>
|
87
|
-
<span x-show="hasColumnChanges(change, column)" class="ml-1 text-xs text-orange-600">*</span>
|
88
|
-
</td>
|
89
|
-
</template>
|
90
|
-
</tr>
|
91
|
-
</template>
|
92
|
-
</tbody>
|
93
|
-
</table>
|
94
|
-
</div>
|
95
|
-
</div>
|
96
|
-
</template>
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<div class="h-full flex flex-col">
|
2
|
-
<%= render partial: 'session_header', locals: { session: @session } %>
|
3
|
-
|
4
|
-
<!-- Tab Bar with URL-based navigation -->
|
5
|
-
<div class="tab-bar">
|
6
|
-
<%= link_to changes_session_path(@session.id), class: "tab-item" do %>
|
7
|
-
Changes
|
8
|
-
<% end %>
|
9
|
-
<%= link_to summary_session_path(@session.id), class: "tab-item" do %>
|
10
|
-
Summary
|
11
|
-
<% end %>
|
12
|
-
<%= link_to diagrams_session_path(@session.id), class: "tab-item active" do %>
|
13
|
-
Diagrams
|
14
|
-
<% end %>
|
15
|
-
</div>
|
16
|
-
|
17
|
-
<!-- Diagrams Content -->
|
18
|
-
<div class="flex-1 overflow-auto">
|
19
|
-
<%= render partial: 'diagrams_tab' %>
|
20
|
-
</div>
|
21
|
-
</div>
|
@@ -1,8 +0,0 @@
|
|
1
|
-
<div class="h-full flex flex-col" x-data="sessionNavigation('<%= @session.id %>')">
|
2
|
-
<%= render 'dbwatcher/sessions/shared/session_header', session: @session %>
|
3
|
-
<%= render 'dbwatcher/sessions/shared/navigation' %>
|
4
|
-
|
5
|
-
<div class="flex-1 overflow-auto">
|
6
|
-
<%= yield %>
|
7
|
-
</div>
|
8
|
-
</div>
|
@@ -1,35 +0,0 @@
|
|
1
|
-
<nav class="bg-white border-b border-gray-200" x-data="sessionNavigation('<%= @session.id %>')">
|
2
|
-
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
3
|
-
<div class="flex justify-between h-16">
|
4
|
-
<div class="flex">
|
5
|
-
<div class="flex-shrink-0 flex items-center">
|
6
|
-
<h1 class="text-xl font-semibold text-gray-900">
|
7
|
-
Session <%= @session.id %>
|
8
|
-
</h1>
|
9
|
-
</div>
|
10
|
-
<div class="ml-6 flex space-x-8">
|
11
|
-
<a href="<%= changes_session_path(@session.id) %>"
|
12
|
-
@click.prevent="$store.session.setView('changes')"
|
13
|
-
:class="$store.session.currentView === 'changes' ? 'border-blue-500 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
14
|
-
class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium transition-colors">
|
15
|
-
Changes
|
16
|
-
</a>
|
17
|
-
|
18
|
-
<a href="<%= summary_session_path(@session.id) %>"
|
19
|
-
@click.prevent="$store.session.setView('summary')"
|
20
|
-
:class="$store.session.currentView === 'summary' ? 'border-blue-500 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
21
|
-
class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium transition-colors">
|
22
|
-
Summary
|
23
|
-
</a>
|
24
|
-
|
25
|
-
<a href="<%= diagrams_session_path(@session.id) %>"
|
26
|
-
@click.prevent="$store.session.setView('diagrams')"
|
27
|
-
:class="$store.session.currentView === 'diagrams' ? 'border-blue-500 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"
|
28
|
-
class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium transition-colors">
|
29
|
-
Diagrams
|
30
|
-
</a>
|
31
|
-
</div>
|
32
|
-
</div>
|
33
|
-
</div>
|
34
|
-
</div>
|
35
|
-
</nav>
|
@@ -1,25 +0,0 @@
|
|
1
|
-
<div class="bg-white border-b border-gray-200 px-4 py-3">
|
2
|
-
<div class="flex items-center justify-between">
|
3
|
-
<div class="flex items-center space-x-4">
|
4
|
-
<h1 class="text-xl font-semibold text-gray-900">
|
5
|
-
Session <%= display_session_id(session.id) %>
|
6
|
-
</h1>
|
7
|
-
<div class="text-sm text-gray-500">
|
8
|
-
<% if session.started_at %>
|
9
|
-
<% started_time = session.started_at.is_a?(String) ? session.started_at : session.started_at.strftime('%Y-%m-%d %H:%M:%S') %>
|
10
|
-
<span>Started: <%= started_time %></span>
|
11
|
-
<% else %>
|
12
|
-
<span>Started: Unknown</span>
|
13
|
-
<% end %>
|
14
|
-
<% if session.ended_at %>
|
15
|
-
<% ended_time = session.ended_at.is_a?(String) ? session.ended_at : session.ended_at.strftime('%Y-%m-%d %H:%M:%S') %>
|
16
|
-
<span class="ml-4">Ended: <%= ended_time %></span>
|
17
|
-
<% end %>
|
18
|
-
</div>
|
19
|
-
</div>
|
20
|
-
|
21
|
-
<div class="flex items-center space-x-3">
|
22
|
-
<%= link_to 'All Sessions', sessions_path, class: 'text-sm text-blue-600 hover:text-blue-800' %>
|
23
|
-
</div>
|
24
|
-
</div>
|
25
|
-
</div>
|
@@ -1,21 +0,0 @@
|
|
1
|
-
<div class="h-full flex flex-col">
|
2
|
-
<%= render partial: 'session_header', locals: { session: @session } %>
|
3
|
-
|
4
|
-
<!-- Tab Bar with URL-based navigation -->
|
5
|
-
<div class="tab-bar">
|
6
|
-
<%= link_to changes_session_path(@session.id), class: "tab-item" do %>
|
7
|
-
Changes
|
8
|
-
<% end %>
|
9
|
-
<%= link_to summary_session_path(@session.id), class: "tab-item active" do %>
|
10
|
-
Summary
|
11
|
-
<% end %>
|
12
|
-
<%= link_to diagrams_session_path(@session.id), class: "tab-item" do %>
|
13
|
-
Diagrams
|
14
|
-
<% end %>
|
15
|
-
</div>
|
16
|
-
|
17
|
-
<!-- Summary Content -->
|
18
|
-
<div class="flex-1 overflow-auto">
|
19
|
-
<%= render partial: 'summary_tab', locals: { tables_summary: @tables_summary } %>
|
20
|
-
</div>
|
21
|
-
</div>
|
File without changes
|
File without changes
|