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
@@ -0,0 +1,91 @@
|
|
1
|
+
<!-- Changes Content - Hybrid Tabulator Implementation -->
|
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 - Enhanced UI Structure -->
|
28
|
+
<template x-if="!loading && !error && Object.keys(tableData).length > 0">
|
29
|
+
<div class="h-full overflow-auto p-2 bg-gray-50">
|
30
|
+
<template x-for="[tableName, tableInfo] in Object.entries(tableData)" :key="tableName">
|
31
|
+
<div class="mb-4 bg-white border border-gray-200 rounded shadow-sm" 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 border-b border-gray-200"
|
34
|
+
@click="expanded = !expanded"
|
35
|
+
:id="`table-${tableName}`">
|
36
|
+
<svg class="w-4 h-4 mr-2 transition-transform text-gray-600"
|
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 changes-table-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 flex items-center">
|
51
|
+
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
52
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7"></path>
|
53
|
+
</svg>
|
54
|
+
Columns
|
55
|
+
</button>
|
56
|
+
</div>
|
57
|
+
|
58
|
+
<!-- Column Selector Dropdown -->
|
59
|
+
<div x-show="showColumnSelector === tableName"
|
60
|
+
x-transition
|
61
|
+
@click.away="showColumnSelector = null"
|
62
|
+
class="absolute z-50 bg-white border border-gray-300 rounded shadow-lg p-3 max-h-64 overflow-auto column-selector">
|
63
|
+
<div class="text-xs font-medium mb-2">Select Visible Columns:</div>
|
64
|
+
<div class="space-y-1 min-w-48">
|
65
|
+
<template x-for="column in tableInfo.columns" :key="column">
|
66
|
+
<label class="flex items-center text-xs hover:bg-gray-50 p-1 rounded">
|
67
|
+
<input type="checkbox"
|
68
|
+
:checked="isColumnVisible(tableName, column)"
|
69
|
+
@change="toggleColumnVisibility(tableName, column)"
|
70
|
+
class="mr-2">
|
71
|
+
<span class="flex-1" x-text="column"></span>
|
72
|
+
</label>
|
73
|
+
</template>
|
74
|
+
</div>
|
75
|
+
<div class="mt-2 pt-2 border-t border-gray-200 flex gap-1">
|
76
|
+
<button @click="selectAllColumns(tableName)"
|
77
|
+
class="text-xs bg-blue-600 text-white px-2 py-1 rounded hover:bg-blue-700">All</button>
|
78
|
+
<button @click="selectNoneColumns(tableName)"
|
79
|
+
class="text-xs bg-gray-600 text-white px-2 py-1 rounded hover:bg-gray-700">None</button>
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
|
83
|
+
<!-- Tabulator Container for This Table -->
|
84
|
+
<div x-show="expanded" x-collapse>
|
85
|
+
<div :id="`changes-table-${tableName}`" class="table-container"></div>
|
86
|
+
</div>
|
87
|
+
</div>
|
88
|
+
</template>
|
89
|
+
</div>
|
90
|
+
</template>
|
91
|
+
</div>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<%# Session Layout - Common structure for all session views %>
|
2
|
+
<div class="h-full flex flex-col">
|
3
|
+
<!-- Session Header -->
|
4
|
+
<%= render 'dbwatcher/sessions/session_header', session: session %>
|
5
|
+
|
6
|
+
<!-- Tab Bar -->
|
7
|
+
<div class="tab-bar">
|
8
|
+
<%= link_to session_path(session.id, tab: 'changes'), class: "tab-item #{active_tab == 'changes' ? 'active' : ''}" do %>
|
9
|
+
Changes
|
10
|
+
<% end %>
|
11
|
+
<%= link_to session_path(session.id, tab: 'summary'), class: "tab-item #{active_tab == 'summary' ? 'active' : ''}" do %>
|
12
|
+
Summary
|
13
|
+
<% end %>
|
14
|
+
<%= link_to session_path(session.id, tab: 'diagrams'), class: "tab-item #{active_tab == 'diagrams' ? 'active' : ''}" do %>
|
15
|
+
Diagrams
|
16
|
+
<% end %>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<!-- Content Area -->
|
20
|
+
<div class="flex-1 overflow-auto p-4">
|
21
|
+
<%= yield %>
|
22
|
+
</div>
|
23
|
+
</div>
|
@@ -1,31 +1,30 @@
|
|
1
1
|
<%# Sessions Index Page %>
|
2
|
-
<div class="h-full flex flex-col"
|
2
|
+
<div class="h-full flex flex-col" x-data="{
|
3
|
+
filterText: '',
|
4
|
+
filterSessions(session, filterText) {
|
5
|
+
if (!filterText) return true;
|
6
|
+
const searchText = filterText.toLowerCase();
|
7
|
+
const sessionId = session.querySelector('td:first-child').textContent.toLowerCase();
|
8
|
+
const sessionName = session.querySelector('td:nth-child(2)').textContent.toLowerCase();
|
9
|
+
return sessionId.includes(searchText) || sessionName.includes(searchText);
|
10
|
+
}
|
11
|
+
}">
|
3
12
|
<%= render 'dbwatcher/shared/header', title: 'Tracking Sessions', subtitle: "#{@sessions.count} sessions" %>
|
4
13
|
|
5
14
|
<%= render 'dbwatcher/shared/tab_bar', tabs: [
|
6
|
-
{ name: 'All Sessions', active: true }
|
7
|
-
{ name: 'Active', active: false },
|
8
|
-
{ name: 'Recent', active: false }
|
15
|
+
{ name: 'All Sessions', active: true }
|
9
16
|
] %>
|
10
17
|
|
11
18
|
<!-- Toolbar -->
|
12
19
|
<div class="h-8 bg-gray-100 border-b border-gray-300 flex items-center px-4 gap-2">
|
13
20
|
<input type="text" placeholder="Filter sessions..."
|
14
|
-
class="compact-input flex-1 max-w-xs"
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
</select>
|
21
|
+
class="compact-input flex-1 max-w-xs"
|
22
|
+
x-model="filterText"
|
23
|
+
@input="document.querySelectorAll('.session-row').forEach(row => {
|
24
|
+
row.classList.toggle('hidden', !filterSessions(row, filterText));
|
25
|
+
})">
|
20
26
|
|
21
27
|
<div class="ml-auto flex items-center gap-2">
|
22
|
-
<button class="compact-button bg-blue-medium text-white hover:bg-blue-700">
|
23
|
-
<svg class="w-3 h-3 inline mr-1" fill="currentColor" viewBox="0 0 20 20">
|
24
|
-
<path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd"/>
|
25
|
-
</svg>
|
26
|
-
Refresh
|
27
|
-
</button>
|
28
|
-
|
29
28
|
<%= button_to clear_sessions_path,
|
30
29
|
method: :delete,
|
31
30
|
class: "compact-button bg-red-600 text-white hover:bg-red-700",
|
@@ -56,79 +55,100 @@
|
|
56
55
|
</div>
|
57
56
|
</div>
|
58
57
|
<% else %>
|
59
|
-
<
|
60
|
-
<
|
61
|
-
<
|
62
|
-
<
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
</thead>
|
71
|
-
<tbody>
|
72
|
-
<% @sessions.each do |session| %>
|
73
|
-
<tr class="hover:bg-blue-50">
|
74
|
-
<td class="font-mono text-xs" style="min-width:180px; max-width:260px; width:18%">
|
75
|
-
<span class="inline-block whitespace-nowrap overflow-x-auto" style="max-width:260px;">
|
76
|
-
<%= safe_value(session, :id) %>
|
77
|
-
</span>
|
78
|
-
</td>
|
79
|
-
<td style="min-width:160px; max-width:260px; width:22%" title="<%= safe_value(session, :name) %>">
|
80
|
-
<%= link_to display_session_name(safe_value(session, :name)),
|
81
|
-
session_path(safe_value(session, :id)),
|
82
|
-
class: "text-navy-dark hover:text-blue-medium whitespace-normal break-words inline-block",
|
83
|
-
style: "max-width:260px; overflow-x:auto; display:inline-block;" %>
|
84
|
-
</td>
|
85
|
-
<td class="text-center">
|
86
|
-
<%= render 'dbwatcher/shared/badge',
|
87
|
-
content: (session_active?(session) ? 'Active' : 'Completed'),
|
88
|
-
badge_class: (session_active?(session) ? 'badge-success' : 'badge-primary') %>
|
89
|
-
</td>
|
90
|
-
<td class="text-center">
|
91
|
-
<% change_count = session_change_count(session) %>
|
92
|
-
<%= render 'dbwatcher/shared/badge',
|
93
|
-
content: change_count > 99 ? "#{change_count}" : change_count,
|
94
|
-
badge_class: 'bg-gray-600 text-white whitespace-nowrap' %>
|
95
|
-
</td>
|
96
|
-
<td class="text-right text-xs whitespace-nowrap">
|
97
|
-
<%= format_timestamp(safe_value(session, :started_at)) %>
|
98
|
-
</td>
|
99
|
-
<td class="text-right text-xs whitespace-nowrap">
|
100
|
-
<% if session_active?(session) %>
|
101
|
-
<span class="text-blue-600">Active</span>
|
102
|
-
<% else %>
|
103
|
-
<%= distance_of_time_in_words(
|
104
|
-
Time.parse(safe_value(session, :started_at)),
|
105
|
-
Time.parse(safe_value(session, :ended_at))
|
106
|
-
) rescue 'N/A' %>
|
107
|
-
<% end %>
|
108
|
-
</td>
|
109
|
-
<td class="text-center">
|
110
|
-
<div class="flex gap-1 justify-end">
|
111
|
-
<%= link_to session_path(safe_value(session, :id)),
|
112
|
-
class: "compact-button bg-navy-dark text-white hover:bg-blue-medium" do %>
|
113
|
-
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
114
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
115
|
-
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
116
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
117
|
-
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"/>
|
118
|
-
</svg>
|
119
|
-
<% end %>
|
120
|
-
</div>
|
121
|
-
</td>
|
58
|
+
<div class="bg-white border border-gray-300 rounded shadow-sm">
|
59
|
+
<table class="compact-table sessions-table w-full">
|
60
|
+
<thead>
|
61
|
+
<tr>
|
62
|
+
<th class="text-left" style="min-width:180px; max-width:260px; width:18%">Session ID</th>
|
63
|
+
<th class="text-left" style="min-width:160px; max-width:260px; width:22%">Name</th>
|
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>
|
122
69
|
</tr>
|
123
|
-
|
124
|
-
|
125
|
-
|
70
|
+
</thead>
|
71
|
+
<tbody>
|
72
|
+
<% @sessions.each do |session| %>
|
73
|
+
<tr class="session-row">
|
74
|
+
<td style="min-width:180px; max-width:260px; width:18%">
|
75
|
+
<span class="font-mono text-xs">
|
76
|
+
<%= safe_value(session, :id) %>
|
77
|
+
</span>
|
78
|
+
</td>
|
79
|
+
<td style="min-width:160px; max-width:260px; width:22%" title="<%= safe_value(session, :name) %>">
|
80
|
+
<%= link_to display_session_name(safe_value(session, :name)),
|
81
|
+
session_path(safe_value(session, :id)),
|
82
|
+
class: "text-navy-dark hover:text-blue-medium whitespace-normal break-words inline-block",
|
83
|
+
style: "max-width:260px; overflow-x:auto; display:inline-block;" %>
|
84
|
+
</td>
|
85
|
+
<td class="text-center">
|
86
|
+
<%= render 'dbwatcher/shared/badge',
|
87
|
+
content: (session_active?(session) ? 'Active' : 'Completed'),
|
88
|
+
badge_class: (session_active?(session) ? 'badge-success' : 'badge-primary') %>
|
89
|
+
</td>
|
90
|
+
<td class="text-center">
|
91
|
+
<% change_count = session_change_count(session) %>
|
92
|
+
<%= render 'dbwatcher/shared/badge',
|
93
|
+
content: change_count > 99 ? "#{change_count}" : change_count,
|
94
|
+
badge_class: 'bg-gray-600 text-white whitespace-nowrap' %>
|
95
|
+
</td>
|
96
|
+
<td class="text-right text-xs whitespace-nowrap">
|
97
|
+
<%= format_timestamp(safe_value(session, :started_at)) %>
|
98
|
+
</td>
|
99
|
+
<td class="text-right text-xs whitespace-nowrap">
|
100
|
+
<% if session_active?(session) %>
|
101
|
+
<span class="text-blue-600 font-medium">Active</span>
|
102
|
+
<% else %>
|
103
|
+
<%= distance_of_time_in_words(
|
104
|
+
Time.parse(safe_value(session, :started_at)),
|
105
|
+
Time.parse(safe_value(session, :ended_at))
|
106
|
+
) rescue 'N/A' %>
|
107
|
+
<% end %>
|
108
|
+
</td>
|
109
|
+
<td class="text-center actions-cell">
|
110
|
+
<div class="flex gap-1 justify-end">
|
111
|
+
<%= link_to session_path(safe_value(session, :id)),
|
112
|
+
class: "compact-button bg-navy-dark text-white hover:bg-blue-medium",
|
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>
|
122
|
+
</td>
|
123
|
+
</tr>
|
124
|
+
<% end %>
|
125
|
+
</tbody>
|
126
|
+
</table>
|
127
|
+
</div>
|
128
|
+
|
129
|
+
<!-- No Results Message -->
|
130
|
+
<div
|
131
|
+
x-show="filterText && document.querySelectorAll('.session-row:not(.hidden)').length === 0"
|
132
|
+
x-cloak
|
133
|
+
class="mt-4 text-center py-8 bg-gray-50 border border-gray-200 rounded">
|
134
|
+
<p class="text-gray-500">No sessions match your filter criteria</p>
|
135
|
+
<button
|
136
|
+
@click="filterText = ''"
|
137
|
+
class="mt-2 text-blue-medium hover:text-blue-700 text-sm">
|
138
|
+
Clear filter
|
139
|
+
</button>
|
140
|
+
</div>
|
126
141
|
<% end %>
|
127
142
|
|
128
143
|
<!-- Status Bar -->
|
129
|
-
<div class="h-6 bg-gray-100 border-t border-gray-300 flex items-center px-4 text-xs text-gray-600">
|
130
|
-
|
131
|
-
<%= @sessions.count { |s| session_active?(s) } %> active
|
132
|
-
|
144
|
+
<div class="h-6 bg-gray-100 border-t border-gray-300 flex items-center px-4 text-xs text-gray-600 mt-4">
|
145
|
+
<span x-show="!filterText"><%= @sessions.count %> sessions total •
|
146
|
+
<%= @sessions.count { |s| session_active?(s) } %> active</span>
|
147
|
+
<span x-show="filterText" x-text="`${document.querySelectorAll('.session-row:not(.hidden)').length} of ${<%= @sessions.count %>} sessions shown`"></span>
|
148
|
+
<span class="ml-auto">Last updated: <%= Time.current.strftime("%H:%M:%S") %></span>
|
133
149
|
</div>
|
134
150
|
</div>
|
151
|
+
|
152
|
+
<style>
|
153
|
+
[x-cloak] { display: none !important; }
|
154
|
+
</style>
|
@@ -1,4 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
<%= render layout: 'layout', locals: { active_tab: @active_tab, session: @session } do %>
|
2
|
+
<% case @active_tab %>
|
3
|
+
<% when 'summary' %>
|
4
|
+
<%= render partial: 'summary' %>
|
5
|
+
<% when 'diagrams' %>
|
6
|
+
<%= render partial: 'diagrams' %>
|
7
|
+
<% else %>
|
8
|
+
<%= render partial: 'changes' %>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
@@ -1,51 +1,43 @@
|
|
1
1
|
<%# Tables Index Page %>
|
2
|
-
<div class="h-full flex flex-col">
|
2
|
+
<div class="h-full flex flex-col" x-data="{ searchTerm: '' }">
|
3
3
|
<%= render 'dbwatcher/shared/header', title: 'Database Tables', subtitle: "#{@tables.count} tables" %>
|
4
|
-
|
4
|
+
|
5
5
|
<%= render 'dbwatcher/shared/tab_bar', tabs: [
|
6
|
-
{ name: 'All Tables', active: true }
|
7
|
-
{ name: 'Modified', active: false },
|
8
|
-
{ name: 'Recent', active: false }
|
6
|
+
{ name: 'All Tables', active: true }
|
9
7
|
] %>
|
10
|
-
|
8
|
+
|
11
9
|
<!-- Toolbar -->
|
12
10
|
<div class="h-8 bg-gray-100 border-b border-gray-300 flex items-center px-4 gap-2">
|
13
|
-
<input type="text" placeholder="Filter tables..."
|
11
|
+
<input type="text" placeholder="Filter tables..."
|
14
12
|
class="compact-input flex-1 max-w-xs"
|
15
|
-
x-
|
13
|
+
x-model="searchTerm"
|
16
14
|
@input="filterTables()">
|
17
|
-
<select class="compact-select">
|
18
|
-
<option>All Types</option>
|
19
|
-
<option>User Tables</option>
|
20
|
-
<option>System Tables</option>
|
21
|
-
</select>
|
22
|
-
<button class="compact-button bg-blue-medium text-white hover:bg-blue-dark">
|
23
|
-
Refresh
|
24
|
-
</button>
|
25
15
|
</div>
|
26
|
-
|
16
|
+
|
27
17
|
<!-- Content Area -->
|
28
18
|
<div class="flex-1 overflow-auto">
|
29
19
|
<table class="compact-table w-full">
|
30
20
|
<thead>
|
31
21
|
<tr>
|
32
|
-
<th class="text-left">Table Name</th>
|
33
|
-
<th class="text-center">Changes</th>
|
34
|
-
<th class="text-center">Last Modified</th>
|
35
|
-
<th class="text-center">Operations</th>
|
36
|
-
<th class="text-right">Actions</th>
|
22
|
+
<th class="text-left" style="min-width: 250px; width: 40%">Table Name</th>
|
23
|
+
<th class="text-center" style="width: 100px">Changes</th>
|
24
|
+
<th class="text-center" style="width: 180px">Last Modified</th>
|
25
|
+
<th class="text-center" style="width: 120px">Operations</th>
|
26
|
+
<th class="text-right" style="width: 100px">Actions</th>
|
37
27
|
</tr>
|
38
28
|
</thead>
|
39
29
|
<tbody>
|
40
30
|
<% @tables.each do |table| %>
|
41
31
|
<% next if table[:name].nil? || table[:name].empty? %>
|
42
32
|
<tr class="hover:bg-gray-50">
|
43
|
-
<td class="font-medium text-navy-dark">
|
33
|
+
<td class="font-medium text-navy-dark" style="max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
44
34
|
<div class="flex items-center gap-2">
|
45
|
-
<svg class="w-3 h-3 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
35
|
+
<svg class="w-3 h-3 text-gray-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
46
36
|
<path fill-rule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11 4a1 1 0 10-2 0v4a1 1 0 102 0V7zm-3 1a1 1 0 10-2 0v3a1 1 0 102 0V8zM8 9a1 1 0 00-2 0v2a1 1 0 102 0V9z" clip-rule="evenodd"/>
|
47
37
|
</svg>
|
48
|
-
|
38
|
+
<span class="truncate" title="<%= table[:name] %>">
|
39
|
+
<%= link_to table[:name], table_path(table[:name]), class: "text-navy-dark hover:text-blue-medium" %>
|
40
|
+
</span>
|
49
41
|
</div>
|
50
42
|
</td>
|
51
43
|
<td class="text-center">
|
@@ -57,35 +49,35 @@
|
|
57
49
|
</td>
|
58
50
|
<td class="text-center text-gray-600">
|
59
51
|
<% if table[:last_change] %>
|
60
|
-
<%=
|
52
|
+
<%= format_timestamp(table[:last_change]) %>
|
61
53
|
<% else %>
|
62
54
|
<span class="text-gray-400">No changes</span>
|
63
55
|
<% end %>
|
64
56
|
</td>
|
65
57
|
<td class="text-center">
|
66
|
-
<div class="flex gap-
|
67
|
-
<span class="badge badge-insert text-xs" title="Inserts">
|
68
|
-
<span class="badge badge-update text-xs" title="Updates">
|
69
|
-
<span class="badge badge-delete text-xs" title="Deletes">
|
58
|
+
<div class="flex gap-2 justify-center">
|
59
|
+
<span class="badge badge-insert text-xs px-2" title="Inserts">Insert</span>
|
60
|
+
<span class="badge badge-update text-xs px-2" title="Updates">Update</span>
|
61
|
+
<span class="badge badge-delete text-xs px-2" title="Deletes">Delete</span>
|
70
62
|
</div>
|
71
63
|
</td>
|
72
64
|
<td class="text-right">
|
73
65
|
<div class="flex gap-1 justify-end">
|
74
|
-
<%= link_to changes_table_path(table[:name]),
|
75
|
-
class: "compact-button bg-gray-500 text-white hover:bg-gray-600",
|
66
|
+
<%= link_to changes_table_path(table[:name]),
|
67
|
+
class: "compact-button bg-gray-500 text-white hover:bg-gray-600",
|
76
68
|
title: "View Changes" do %>
|
77
69
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
78
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
70
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
79
71
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
80
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
72
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
81
73
|
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"/>
|
82
74
|
</svg>
|
83
75
|
<% end %>
|
84
|
-
<%= link_to table_path(table[:name]),
|
85
|
-
class: "compact-button bg-navy-dark text-white hover:bg-blue-medium",
|
76
|
+
<%= link_to table_path(table[:name]),
|
77
|
+
class: "compact-button bg-navy-dark text-white hover:bg-blue-medium",
|
86
78
|
title: "Table Details" do %>
|
87
79
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
88
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
80
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
89
81
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
90
82
|
</svg>
|
91
83
|
<% end %>
|
@@ -96,10 +88,10 @@
|
|
96
88
|
</tbody>
|
97
89
|
</table>
|
98
90
|
</div>
|
99
|
-
|
91
|
+
|
100
92
|
<!-- Status Bar -->
|
101
93
|
<div class="h-6 bg-gray-100 border-t border-gray-300 flex items-center px-4 text-xs text-gray-600">
|
102
|
-
<%= @tables.count %> tables total •
|
94
|
+
<%= @tables.count %> tables total •
|
103
95
|
<%= @tables.count { |t| t[:change_count] > 0 } %> with changes •
|
104
96
|
Last updated: <%= Time.current.strftime("%H:%M:%S") %>
|
105
97
|
</div>
|
@@ -110,7 +102,7 @@ function filterTables() {
|
|
110
102
|
// Simple table filtering functionality
|
111
103
|
const searchTerm = document.querySelector('input[x-model="searchTerm"]').value.toLowerCase();
|
112
104
|
const rows = document.querySelectorAll('tbody tr');
|
113
|
-
|
105
|
+
|
114
106
|
rows.forEach(row => {
|
115
107
|
const tableName = row.querySelector('td').textContent.toLowerCase();
|
116
108
|
if (tableName.includes(searchTerm)) {
|