dbwatcher 1.1.2 → 1.1.4
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/app/assets/config/dbwatcher_manifest.js +1 -0
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +184 -97
- 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 +298 -1
- data/app/assets/stylesheets/dbwatcher/application.scss +1 -0
- data/app/assets/stylesheets/dbwatcher/components/_timeline.scss +326 -0
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +27 -17
- data/app/controllers/dbwatcher/sessions_controller.rb +1 -1
- data/app/views/dbwatcher/sessions/_layout.html.erb +5 -2
- data/app/views/dbwatcher/sessions/_summary.html.erb +1 -1
- data/app/views/dbwatcher/sessions/{_changes.html.erb → _tables.html.erb} +84 -5
- data/app/views/dbwatcher/sessions/_timeline.html.erb +260 -0
- data/app/views/dbwatcher/sessions/show.html.erb +3 -1
- data/app/views/layouts/dbwatcher/application.html.erb +1 -0
- data/config/routes.rb +2 -1
- data/lib/dbwatcher/configuration.rb +11 -0
- data/lib/dbwatcher/logging.rb +23 -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/diagram_analyzers/inferred_relationship_analyzer.rb +62 -36
- 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_builder.rb +10 -8
- 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/version.rb +1 -1
- data/lib/dbwatcher.rb +1 -1
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b14e37821dd10efdc21bfa6e9a68ff8ffea9096d616616893944aca66dd1c58
|
4
|
+
data.tar.gz: 983a92a4065533bdb7f15a0adeeefc3fd0a97109cf5b08a9c4adaef11bd829f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86071979a9e26aa6e0e5f289b4079804615e4477357dc894305d8eab047c328c9a1d97651e37889c913f073fc8f156abd410582adbe8581f68443f3d195fc5f4
|
7
|
+
data.tar.gz: '09bacda0cc050854b53d8567b46c791cfc440825598bb3c06300e2f4f572dd1f3cde1df0b127a332edd9a3995077ef7beae9bafc47cb7629478d2671a0d2691a'
|
@@ -4,6 +4,7 @@
|
|
4
4
|
//= link dbwatcher/alpine_registrations.js
|
5
5
|
//= link_tree ../javascripts/dbwatcher/components
|
6
6
|
//= link dbwatcher/components/changes_table_hybrid.js
|
7
|
+
//= link dbwatcher/components/timeline.js
|
7
8
|
//= link_tree ../javascripts/dbwatcher/core
|
8
9
|
//= link_tree ../javascripts/dbwatcher/services
|
9
10
|
//= link dbwatcher/vendor/lodash.min.js
|
@@ -18,7 +18,8 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
18
18
|
filters: {
|
19
19
|
search: '',
|
20
20
|
operation: '',
|
21
|
-
table: ''
|
21
|
+
table: '',
|
22
|
+
selectedTables: []
|
22
23
|
},
|
23
24
|
showColumnSelector: null,
|
24
25
|
expandedRows: {},
|
@@ -57,46 +58,55 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
57
58
|
|
58
59
|
// Apply filters directly to Tabulator (no server reload needed)
|
59
60
|
applyTabulatorFilters() {
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
// Apply filters to all tabulator instances
|
62
|
+
Object.keys(this.tabulators).forEach(tableName => {
|
63
|
+
const tabulator = this.tabulators[tableName];
|
64
|
+
if (!tabulator) return;
|
65
|
+
|
66
|
+
// Clear existing filters
|
67
|
+
tabulator.clearFilter();
|
68
|
+
|
69
|
+
// Create a combined filter function that handles all filters
|
70
|
+
const hasSearch = this.filters.search && this.filters.search.trim();
|
71
|
+
const hasOperation = this.filters.operation;
|
72
|
+
const hasTable = this.filters.table;
|
73
|
+
|
74
|
+
if (hasSearch || hasOperation || hasTable) {
|
75
|
+
const searchTerm = hasSearch ? this.filters.search.trim().toLowerCase() : '';
|
76
|
+
|
77
|
+
// Apply combined custom filter
|
78
|
+
tabulator.setFilter((data) => {
|
79
|
+
// Search filter
|
80
|
+
if (hasSearch) {
|
81
|
+
const searchableContent = [
|
82
|
+
data.table_name,
|
83
|
+
data.operation,
|
84
|
+
data.timestamp,
|
85
|
+
data.index,
|
86
|
+
...Object.values(data).filter(val => val !== null && val !== undefined)
|
87
|
+
].join(' ').toLowerCase();
|
88
|
+
|
89
|
+
if (!searchableContent.includes(searchTerm)) {
|
90
|
+
return false;
|
91
|
+
}
|
92
|
+
}
|
67
93
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
field: 'searchable_content',
|
73
|
-
type: 'like',
|
74
|
-
value: searchTerm
|
75
|
-
});
|
76
|
-
}
|
94
|
+
// Operation filter
|
95
|
+
if (hasOperation && data.operation !== this.filters.operation) {
|
96
|
+
return false;
|
97
|
+
}
|
77
98
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
type: '=',
|
83
|
-
value: this.filters.operation
|
84
|
-
});
|
85
|
-
}
|
99
|
+
// Table filter
|
100
|
+
if (hasTable && data.table_name !== this.filters.table) {
|
101
|
+
return false;
|
102
|
+
}
|
86
103
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
field: 'table_name',
|
91
|
-
type: '=',
|
92
|
-
value: this.filters.table
|
93
|
-
});
|
94
|
-
}
|
104
|
+
return true;
|
105
|
+
});
|
106
|
+
}
|
95
107
|
|
96
|
-
|
97
|
-
|
98
|
-
this.tabulator.setFilter(filters);
|
99
|
-
}
|
108
|
+
// Note: Multi-table filtering is handled at the template level via x-show
|
109
|
+
});
|
100
110
|
},
|
101
111
|
|
102
112
|
// Load data from API
|
@@ -117,11 +127,18 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
117
127
|
if (this.filters.operation) params.append('operation', this.filters.operation);
|
118
128
|
if (this.filters.search) params.append('search', this.filters.search);
|
119
129
|
|
120
|
-
const url = `/dbwatcher/api/v1/sessions/${this.sessionId}/
|
130
|
+
const url = `/dbwatcher/api/v1/sessions/${this.sessionId}/tables_data?${params.toString()}`;
|
121
131
|
const data = await this.fetchData(url);
|
122
132
|
|
123
133
|
if (data.tables_summary) {
|
124
134
|
this.tableData = data.tables_summary;
|
135
|
+
|
136
|
+
// Debug: Log the table data structure to verify model_class is included
|
137
|
+
console.log('Table data received:', Object.keys(this.tableData));
|
138
|
+
Object.entries(this.tableData).forEach(([tableName, tableInfo]) => {
|
139
|
+
console.log(`Table ${tableName} model_class:`, tableInfo.model_class);
|
140
|
+
});
|
141
|
+
|
125
142
|
this.initializeColumnVisibility();
|
126
143
|
this.initializeTabulators();
|
127
144
|
} else {
|
@@ -166,30 +183,30 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
166
183
|
|
167
184
|
// Transform data for this specific table
|
168
185
|
const tabulatorData = this.transformTableDataForTabulator(tableName, tableInfo);
|
169
|
-
|
186
|
+
|
170
187
|
// Create Tabulator instance for this table
|
171
188
|
this.tabulators[tableName] = new Tabulator(container, {
|
172
189
|
data: tabulatorData,
|
173
190
|
layout: 'fitDataFill',
|
174
191
|
responsiveLayout: false,
|
175
192
|
height: Math.max(200, Math.min(400, (tabulatorData.length * 35) + 80)), // Minimum 200px, expand based on content
|
176
|
-
|
193
|
+
|
177
194
|
// Force Tabulator to use our custom rowId field
|
178
195
|
index: 'rowId', // Tell Tabulator to use the 'rowId' field as the row identifier
|
179
|
-
|
196
|
+
|
180
197
|
// Performance optimizations - disable virtual DOM to ensure all rows render
|
181
198
|
virtualDom: false,
|
182
199
|
pagination: false, // Ensure no pagination
|
183
|
-
|
200
|
+
|
184
201
|
// Column configuration for this table
|
185
202
|
columns: this.buildColumnsForTable(tableName, tableInfo),
|
186
|
-
|
203
|
+
|
187
204
|
// Row formatting
|
188
205
|
rowFormatter: this.customRowFormatter.bind(this),
|
189
|
-
|
206
|
+
|
190
207
|
// No initial sorting - data should be in correct order from API
|
191
208
|
// initialSort: [],
|
192
|
-
|
209
|
+
|
193
210
|
// Enable header sorting
|
194
211
|
headerSortTristate: true,
|
195
212
|
|
@@ -204,13 +221,13 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
204
221
|
transformTableDataForTabulator(tableName, tableInfo) {
|
205
222
|
const rows = [];
|
206
223
|
const changes = tableInfo.changes || [];
|
207
|
-
|
224
|
+
|
208
225
|
changes.forEach((change, index) => {
|
209
226
|
const columnData = this.extractColumnData(change, tableInfo.columns);
|
210
|
-
|
227
|
+
|
211
228
|
// Create truly unique row ID using table name and index only (for Tabulator internal use)
|
212
229
|
const uniqueRowId = `${tableName}_row_${index}`;
|
213
|
-
|
230
|
+
|
214
231
|
const row = {
|
215
232
|
rowId: uniqueRowId, // Tabulator's internal row identifier
|
216
233
|
index: index + 1, // Display index (1-based) - should maintain API order
|
@@ -220,7 +237,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
220
237
|
change_data: change, // Keep original change data with ID intact
|
221
238
|
...columnData // Include all column data including actual record ID
|
222
239
|
};
|
223
|
-
|
240
|
+
|
224
241
|
rows.push(row);
|
225
242
|
});
|
226
243
|
|
@@ -241,7 +258,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
241
258
|
formatter: (cell) => {
|
242
259
|
const rowData = cell.getRow().getData();
|
243
260
|
return `<div class="flex items-center justify-center gap-1">
|
244
|
-
<button class="expand-btn text-gray-400 hover:text-gray-600 transition-colors p-1 rounded hover:bg-gray-100"
|
261
|
+
<button class="expand-btn text-gray-400 hover:text-gray-600 transition-colors p-1 rounded hover:bg-gray-100"
|
245
262
|
data-row-id="${rowData.rowId}">
|
246
263
|
<svg class="w-3 h-3 transition-transform" fill="currentColor" viewBox="0 0 20 20">
|
247
264
|
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 5.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
@@ -302,7 +319,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
302
319
|
const value = cell.getValue();
|
303
320
|
const rowData = cell.getRow().getData();
|
304
321
|
const change = rowData.change_data;
|
305
|
-
|
322
|
+
|
306
323
|
// Handle different operations with appropriate styling
|
307
324
|
if (change.operation === 'UPDATE' && change.changes) {
|
308
325
|
const columnChange = change.changes.find(c => c.column === col);
|
@@ -334,7 +351,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
334
351
|
// Extract column data from change record
|
335
352
|
extractColumnData(change, columns) {
|
336
353
|
const data = {};
|
337
|
-
|
354
|
+
|
338
355
|
columns.forEach(col => {
|
339
356
|
// Get value from record snapshot or change data
|
340
357
|
if (change.record_snapshot && change.record_snapshot[col] !== undefined) {
|
@@ -364,7 +381,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
364
381
|
formatter: (cell) => {
|
365
382
|
const rowData = cell.getRow().getData();
|
366
383
|
return `<div class="flex items-center justify-center gap-1">
|
367
|
-
<button class="expand-btn text-gray-400 hover:text-gray-600 transition-colors p-1 rounded hover:bg-gray-100"
|
384
|
+
<button class="expand-btn text-gray-400 hover:text-gray-600 transition-colors p-1 rounded hover:bg-gray-100"
|
368
385
|
data-row-id="${rowData.rowId}">
|
369
386
|
<svg class="w-3 h-3 transition-transform" fill="currentColor" viewBox="0 0 20 20">
|
370
387
|
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 5.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
|
@@ -431,7 +448,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
431
448
|
const value = cell.getValue();
|
432
449
|
const rowData = cell.getRow().getData();
|
433
450
|
const change = rowData.change_data;
|
434
|
-
|
451
|
+
|
435
452
|
// Handle different operations with appropriate styling
|
436
453
|
if (change.operation === 'UPDATE' && change.changes) {
|
437
454
|
const columnChange = change.changes.find(c => c.column === col);
|
@@ -463,15 +480,15 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
463
480
|
if (columnName.toLowerCase().includes('id') || columnName.toLowerCase().includes('uuid')) {
|
464
481
|
return 'string';
|
465
482
|
}
|
466
|
-
|
483
|
+
|
467
484
|
// Timestamp columns - use string sorting to avoid Luxon dependency
|
468
|
-
if (columnName.toLowerCase().includes('time') ||
|
485
|
+
if (columnName.toLowerCase().includes('time') ||
|
469
486
|
columnName.toLowerCase().includes('date') ||
|
470
487
|
columnName.toLowerCase().includes('created') ||
|
471
488
|
columnName.toLowerCase().includes('updated')) {
|
472
489
|
return 'string'; // Changed from 'datetime' to 'string'
|
473
490
|
}
|
474
|
-
|
491
|
+
|
475
492
|
// Numeric columns
|
476
493
|
if (columnName.toLowerCase().includes('count') ||
|
477
494
|
columnName.toLowerCase().includes('amount') ||
|
@@ -479,7 +496,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
479
496
|
columnName.toLowerCase().includes('quantity')) {
|
480
497
|
return 'number';
|
481
498
|
}
|
482
|
-
|
499
|
+
|
483
500
|
// Default to alphanum for mixed content
|
484
501
|
return 'alphanum';
|
485
502
|
},
|
@@ -488,11 +505,11 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
488
505
|
applyHeaderClasses(tableName) {
|
489
506
|
const tabulator = this.tabulators[tableName];
|
490
507
|
if (!tabulator) return;
|
491
|
-
|
508
|
+
|
492
509
|
const headers = tabulator.getHeaderElements();
|
493
510
|
headers.forEach((header) => {
|
494
511
|
const field = header.getAttribute('tabulator-field');
|
495
|
-
|
512
|
+
|
496
513
|
if (field === 'index') {
|
497
514
|
header.classList.add('sticky-left-0');
|
498
515
|
} else if (field === 'operation') {
|
@@ -507,10 +524,10 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
507
524
|
applyRowClasses(tableName, row) {
|
508
525
|
const element = row.getElement();
|
509
526
|
const cells = element.querySelectorAll('.tabulator-cell');
|
510
|
-
|
527
|
+
|
511
528
|
cells.forEach((cell) => {
|
512
529
|
const field = cell.getAttribute('tabulator-field');
|
513
|
-
|
530
|
+
|
514
531
|
if (field === 'index') {
|
515
532
|
cell.classList.add('sticky-left-0');
|
516
533
|
} else if (field === 'operation') {
|
@@ -525,7 +542,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
525
542
|
customRowFormatter(row) {
|
526
543
|
const rowData = row.getData();
|
527
544
|
const element = row.getElement();
|
528
|
-
|
545
|
+
|
529
546
|
// Add classes based on operation
|
530
547
|
const operation = rowData.operation;
|
531
548
|
if (operation) {
|
@@ -536,7 +553,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
536
553
|
element.addEventListener('mouseenter', () => {
|
537
554
|
element.style.backgroundColor = '#f3f4f6';
|
538
555
|
});
|
539
|
-
|
556
|
+
|
540
557
|
element.addEventListener('mouseleave', () => {
|
541
558
|
element.style.backgroundColor = '';
|
542
559
|
});
|
@@ -546,11 +563,11 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
546
563
|
// Toggle row expansion
|
547
564
|
toggleRowExpansion(rowId) {
|
548
565
|
this.expandedRows[rowId] = !this.expandedRows[rowId];
|
549
|
-
|
566
|
+
|
550
567
|
// Find the row across all tabulator instances
|
551
568
|
let targetRow = null;
|
552
569
|
let foundInTable = null;
|
553
|
-
|
570
|
+
|
554
571
|
Object.keys(this.tabulators).forEach(tableName => {
|
555
572
|
const tabulator = this.tabulators[tableName];
|
556
573
|
if (tabulator && !targetRow) {
|
@@ -577,7 +594,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
577
594
|
}
|
578
595
|
}
|
579
596
|
});
|
580
|
-
|
597
|
+
|
581
598
|
if (targetRow) {
|
582
599
|
if (this.expandedRows[rowId]) {
|
583
600
|
this.showRowDetails(targetRow);
|
@@ -593,34 +610,34 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
593
610
|
showRowDetails(row) {
|
594
611
|
const rowData = row.getData();
|
595
612
|
const element = row.getElement();
|
596
|
-
|
597
|
-
|
613
|
+
|
614
|
+
|
598
615
|
// Check if detail row already exists
|
599
616
|
const existingDetail = element.nextElementSibling;
|
600
617
|
if (existingDetail && existingDetail.classList.contains('row-detail')) {
|
601
618
|
return; // Already expanded
|
602
619
|
}
|
603
|
-
|
620
|
+
|
604
621
|
// Create detail row as a proper table row
|
605
622
|
const detailRow = document.createElement('tr');
|
606
623
|
detailRow.className = 'row-detail bg-gray-50';
|
607
624
|
detailRow.setAttribute('data-parent-id', rowData.rowId);
|
608
|
-
|
625
|
+
|
609
626
|
// Create full-width cell
|
610
627
|
const detailCell = document.createElement('td');
|
611
628
|
detailCell.colSpan = 1000; // Span all columns
|
612
629
|
detailCell.className = 'p-0 border-t border-gray-200';
|
613
|
-
|
630
|
+
|
614
631
|
try {
|
615
632
|
detailCell.innerHTML = this.generateExpandedContent(rowData);
|
616
633
|
detailRow.appendChild(detailCell);
|
617
|
-
|
634
|
+
|
618
635
|
// Insert after the current row
|
619
636
|
element.parentNode.insertBefore(detailRow, element.nextSibling);
|
620
|
-
|
637
|
+
|
621
638
|
// Update expand button
|
622
639
|
this.updateExpandButton(element, true);
|
623
|
-
|
640
|
+
|
624
641
|
// Dynamically increase table height when expanded
|
625
642
|
const tabulator = this.findTabulatorForRow(rowData.table_name);
|
626
643
|
if (tabulator) {
|
@@ -635,22 +652,22 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
635
652
|
}
|
636
653
|
},
|
637
654
|
|
638
|
-
// Hide row details
|
655
|
+
// Hide row details
|
639
656
|
hideRowDetails(row) {
|
640
657
|
const element = row.getElement();
|
641
658
|
const rowData = row.getData();
|
642
|
-
|
643
|
-
|
659
|
+
|
660
|
+
|
644
661
|
// Find and remove the detail row
|
645
662
|
const detailRow = element.parentNode.querySelector(`tr.row-detail[data-parent-id="${rowData.rowId}"]`);
|
646
663
|
if (detailRow) {
|
647
664
|
detailRow.remove();
|
648
665
|
} else {
|
649
666
|
}
|
650
|
-
|
667
|
+
|
651
668
|
// Update expand button
|
652
669
|
this.updateExpandButton(element, false);
|
653
|
-
|
670
|
+
|
654
671
|
// Shrink table height back when collapsed
|
655
672
|
const tabulator = this.findTabulatorForRow(rowData.table_name);
|
656
673
|
if (tabulator) {
|
@@ -675,12 +692,12 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
675
692
|
}
|
676
693
|
},
|
677
694
|
|
678
|
-
// Generate expanded content for a row - inline table format
|
695
|
+
// Generate expanded content for a row - inline table format
|
679
696
|
generateExpandedContent(rowData) {
|
680
697
|
const change = rowData.change_data;
|
681
698
|
const tableInfo = this.tableData[rowData.table_name];
|
682
699
|
const columns = tableInfo ? tableInfo.columns : [];
|
683
|
-
|
700
|
+
|
684
701
|
// Create a table row that matches the column structure
|
685
702
|
let content = `
|
686
703
|
<div class="bg-gray-50 border-t border-gray-200">
|
@@ -700,21 +717,21 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
700
717
|
<div class="text-gray-500 mt-1">${rowData.table_name}</div>
|
701
718
|
</div>
|
702
719
|
`;
|
703
|
-
|
720
|
+
|
704
721
|
if (change.operation === 'UPDATE' && change.changes) {
|
705
722
|
content += `<div class="text-blue-600 font-medium">${change.changes.length} columns modified</div>`;
|
706
723
|
}
|
707
|
-
|
724
|
+
|
708
725
|
if (change.record_snapshot && (change.record_snapshot.id || change.record_snapshot.uuid)) {
|
709
726
|
const fullId = change.record_snapshot.id || change.record_snapshot.uuid;
|
710
727
|
content += `<div class="text-gray-500 font-mono text-xs bg-gray-200 p-1 rounded truncate" title="${fullId}">ID: ${String(fullId).substring(0, 12)}...</div>`;
|
711
728
|
}
|
712
|
-
|
729
|
+
|
713
730
|
content += `
|
714
731
|
</div>
|
715
732
|
</td>
|
716
733
|
`;
|
717
|
-
|
734
|
+
|
718
735
|
// Add detail cells for each column that matches the table structure
|
719
736
|
columns.forEach(col => {
|
720
737
|
// Only show if column is visible in the main table
|
@@ -724,7 +741,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
724
741
|
<div class="text-xs font-medium text-gray-600 mb-1">${col}</div>
|
725
742
|
<div class="text-xs">
|
726
743
|
`;
|
727
|
-
|
744
|
+
|
728
745
|
if (change.operation === 'UPDATE' && change.changes) {
|
729
746
|
const columnChange = change.changes.find(c => c.column === col);
|
730
747
|
if (columnChange) {
|
@@ -779,21 +796,21 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
779
796
|
</div>
|
780
797
|
`;
|
781
798
|
}
|
782
|
-
|
799
|
+
|
783
800
|
content += `
|
784
801
|
</div>
|
785
802
|
</td>
|
786
803
|
`;
|
787
804
|
}
|
788
805
|
});
|
789
|
-
|
806
|
+
|
790
807
|
content += `
|
791
808
|
</tr>
|
792
809
|
</tbody>
|
793
810
|
</table>
|
794
811
|
</div>
|
795
812
|
`;
|
796
|
-
|
813
|
+
|
797
814
|
return content;
|
798
815
|
},
|
799
816
|
|
@@ -802,7 +819,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
802
819
|
if (value === null || value === undefined) {
|
803
820
|
return '<span class="text-gray-400 italic">NULL</span>';
|
804
821
|
}
|
805
|
-
|
822
|
+
|
806
823
|
if (typeof value === 'string' && this.isJsonValue(value)) {
|
807
824
|
try {
|
808
825
|
const parsed = JSON.parse(value);
|
@@ -811,7 +828,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
811
828
|
return String(value);
|
812
829
|
}
|
813
830
|
}
|
814
|
-
|
831
|
+
|
815
832
|
return String(value);
|
816
833
|
},
|
817
834
|
|
@@ -831,11 +848,11 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
831
848
|
if (value === null || value === undefined) {
|
832
849
|
return '<span class="text-gray-400">NULL</span>';
|
833
850
|
}
|
834
|
-
|
851
|
+
|
835
852
|
if (typeof value === 'string' && value.length > 50) {
|
836
853
|
return `<span title="${value}">${value.substring(0, 50)}...</span>`;
|
837
854
|
}
|
838
|
-
|
855
|
+
|
839
856
|
return String(value);
|
840
857
|
},
|
841
858
|
|
@@ -888,7 +905,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
888
905
|
// Initialize column visibility state (per table)
|
889
906
|
initializeColumnVisibility() {
|
890
907
|
this.tableColumns = {};
|
891
|
-
|
908
|
+
|
892
909
|
Object.keys(this.tableData).forEach(tableName => {
|
893
910
|
const tableInfo = this.tableData[tableName];
|
894
911
|
if (tableInfo && tableInfo.columns) {
|
@@ -916,7 +933,7 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
916
933
|
// Rebuild columns for this table
|
917
934
|
const tableInfo = this.tableData[tableName];
|
918
935
|
const newColumns = this.buildColumnsForTable(tableName, tableInfo);
|
919
|
-
|
936
|
+
|
920
937
|
// Update the tabulator columns
|
921
938
|
tabulator.setColumns(newColumns);
|
922
939
|
},
|
@@ -984,6 +1001,76 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
984
1001
|
});
|
985
1002
|
},
|
986
1003
|
|
1004
|
+
// Get available operations for filtering
|
1005
|
+
getAvailableOperations() {
|
1006
|
+
const operations = new Set();
|
1007
|
+
Object.values(this.tableData).forEach(tableInfo => {
|
1008
|
+
if (tableInfo.operations) {
|
1009
|
+
Object.keys(tableInfo.operations).forEach(op => operations.add(op));
|
1010
|
+
}
|
1011
|
+
});
|
1012
|
+
return Array.from(operations).sort();
|
1013
|
+
},
|
1014
|
+
|
1015
|
+
// Get available tables for filtering
|
1016
|
+
getAvailableTables() {
|
1017
|
+
return Object.keys(this.tableData).sort();
|
1018
|
+
},
|
1019
|
+
|
1020
|
+
// Select all tables
|
1021
|
+
selectAllTables() {
|
1022
|
+
this.filters.selectedTables = this.getAvailableTables();
|
1023
|
+
this.applyFilters();
|
1024
|
+
},
|
1025
|
+
|
1026
|
+
// Clear table filters
|
1027
|
+
clearTableFilters() {
|
1028
|
+
this.filters.selectedTables = [];
|
1029
|
+
this.applyFilters();
|
1030
|
+
},
|
1031
|
+
|
1032
|
+
// Clear all filters
|
1033
|
+
clearAllFilters() {
|
1034
|
+
this.filters = {
|
1035
|
+
search: '',
|
1036
|
+
operation: '',
|
1037
|
+
table: '',
|
1038
|
+
selectedTables: []
|
1039
|
+
};
|
1040
|
+
this.applyFilters();
|
1041
|
+
},
|
1042
|
+
|
1043
|
+
// Get active filter count
|
1044
|
+
getActiveFilterCount() {
|
1045
|
+
let count = 0;
|
1046
|
+
if (this.filters.search) count++;
|
1047
|
+
if (this.filters.operation) count++;
|
1048
|
+
if (this.filters.table) count++;
|
1049
|
+
if (this.filters.selectedTables.length > 0) count++;
|
1050
|
+
return count;
|
1051
|
+
},
|
1052
|
+
|
1053
|
+
// Get filtered row count
|
1054
|
+
getFilteredRowCount() {
|
1055
|
+
let count = 0;
|
1056
|
+
Object.values(this.tabulators).forEach(tabulator => {
|
1057
|
+
if (tabulator) {
|
1058
|
+
count += tabulator.getDataCount('active');
|
1059
|
+
}
|
1060
|
+
});
|
1061
|
+
return count;
|
1062
|
+
},
|
1063
|
+
|
1064
|
+
// Get total row count
|
1065
|
+
getTotalRowCount() {
|
1066
|
+
let count = 0;
|
1067
|
+
Object.values(this.tabulators).forEach(tabulator => {
|
1068
|
+
if (tabulator) {
|
1069
|
+
count += tabulator.getDataCount();
|
1070
|
+
}
|
1071
|
+
});
|
1072
|
+
return count;
|
1073
|
+
},
|
987
1074
|
|
988
1075
|
// Component cleanup
|
989
1076
|
componentDestroy() {
|
@@ -995,4 +1082,4 @@ DBWatcher.registerComponent('changesTableHybrid', function(config) {
|
|
995
1082
|
this.tabulators = {};
|
996
1083
|
}
|
997
1084
|
};
|
998
|
-
});
|
1085
|
+
});
|