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,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dbwatcher
|
4
|
+
module Dashboard
|
5
|
+
class SystemInfoController < BaseController
|
6
|
+
def refresh
|
7
|
+
system_info_storage.refresh_info
|
8
|
+
|
9
|
+
respond_to do |format|
|
10
|
+
format.json do
|
11
|
+
render json: refresh_success_response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
rescue StandardError => e
|
15
|
+
respond_to do |format|
|
16
|
+
format.json do
|
17
|
+
render json: { success: false, error: e.message }, status: :internal_server_error
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_cache
|
23
|
+
system_info_storage.clear_cache
|
24
|
+
|
25
|
+
respond_to do |format|
|
26
|
+
format.json do
|
27
|
+
render json: clear_cache_success_response
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue StandardError => e
|
31
|
+
respond_to do |format|
|
32
|
+
format.json do
|
33
|
+
render json: { success: false, error: e.message }, status: :internal_server_error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Get system info storage instance
|
41
|
+
#
|
42
|
+
# @return [Storage::SystemInfoStorage] storage instance
|
43
|
+
def system_info_storage
|
44
|
+
@system_info_storage ||= Storage::SystemInfoStorage.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def refresh_success_response
|
48
|
+
{
|
49
|
+
success: true,
|
50
|
+
message: "System information refreshed successfully",
|
51
|
+
data: system_info_storage.cached_info,
|
52
|
+
summary: system_info_storage.summary
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear_cache_success_response
|
57
|
+
{
|
58
|
+
success: true,
|
59
|
+
message: "System information cache cleared successfully"
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -7,6 +7,14 @@ module Dbwatcher
|
|
7
7
|
@recent_sessions = dashboard_data[:recent_sessions]
|
8
8
|
@active_tables = dashboard_data[:active_tables]
|
9
9
|
@query_stats = dashboard_data[:query_stats]
|
10
|
+
@active_tab = params[:tab] || "overview"
|
11
|
+
|
12
|
+
# Add system information if enabled
|
13
|
+
return unless Dbwatcher.configuration.collect_system_info
|
14
|
+
|
15
|
+
@system_info_summary = system_info_storage.summary
|
16
|
+
@system_info = system_info_storage.cached_info
|
17
|
+
@info_age = system_info_storage.info_age
|
10
18
|
end
|
11
19
|
|
12
20
|
def clear_all
|
@@ -16,5 +24,14 @@ module Dbwatcher
|
|
16
24
|
root_path
|
17
25
|
)
|
18
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Get system info storage instance
|
31
|
+
#
|
32
|
+
# @return [Storage::SystemInfoStorage] storage instance
|
33
|
+
def system_info_storage
|
34
|
+
@system_info_storage ||= Storage::SystemInfoStorage.new
|
35
|
+
end
|
19
36
|
end
|
20
37
|
end
|
@@ -9,22 +9,9 @@ module Dbwatcher
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def show
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def changes
|
16
|
-
Rails.logger.info "SessionsController#changes: Loading changes for session #{@session.id}"
|
17
|
-
# No server-side data processing - API-first architecture
|
18
|
-
end
|
19
|
-
|
20
|
-
def summary
|
21
|
-
Rails.logger.info "SessionsController#summary: Loading summary for session #{@session.id}"
|
22
|
-
# No server-side data processing - API-first architecture
|
23
|
-
end
|
24
|
-
|
25
|
-
def diagrams
|
26
|
-
Rails.logger.info "SessionsController#diagrams: Loading diagrams for session #{@session.id}"
|
27
|
-
# No server-side data processing - API-first architecture
|
12
|
+
@active_tab = params[:tab] || "changes"
|
13
|
+
# Debug logging
|
14
|
+
Rails.logger.info "SessionsController#show: Session ID: #{@session.id.inspect}, Class: #{@session.class}"
|
28
15
|
end
|
29
16
|
|
30
17
|
def clear
|
@@ -41,8 +28,5 @@ module Dbwatcher
|
|
41
28
|
@session = Storage.sessions.find(params[:id])
|
42
29
|
handle_not_found("Session", sessions_path) unless @session
|
43
30
|
end
|
44
|
-
|
45
|
-
# No longer needed with API-first architecture
|
46
|
-
# All data processing happens in API services and is loaded via JavaScript
|
47
31
|
end
|
48
32
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Dbwatcher
|
4
|
+
# rubocop:disable Metrics/ModuleLength
|
4
5
|
module ApplicationHelper
|
5
6
|
include FormattingHelper
|
6
7
|
include SessionHelper
|
@@ -21,7 +22,11 @@ module Dbwatcher
|
|
21
22
|
sessions: sessions_icon_svg,
|
22
23
|
tables: tables_icon_svg,
|
23
24
|
queries: queries_icon_svg,
|
24
|
-
performance: performance_icon_svg
|
25
|
+
performance: performance_icon_svg,
|
26
|
+
system: system_icon_svg,
|
27
|
+
memory: memory_icon_svg,
|
28
|
+
database: database_icon_svg,
|
29
|
+
runtime: runtime_icon_svg
|
25
30
|
}
|
26
31
|
end
|
27
32
|
|
@@ -60,19 +65,45 @@ module Dbwatcher
|
|
60
65
|
SVG
|
61
66
|
end
|
62
67
|
|
63
|
-
|
68
|
+
def system_icon_svg
|
69
|
+
<<~SVG
|
70
|
+
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
71
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
72
|
+
d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
73
|
+
</svg>
|
74
|
+
SVG
|
75
|
+
end
|
64
76
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
].join.html_safe
|
73
|
-
end
|
77
|
+
def memory_icon_svg
|
78
|
+
<<~SVG
|
79
|
+
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
80
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
81
|
+
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
82
|
+
</svg>
|
83
|
+
SVG
|
74
84
|
end
|
75
85
|
|
86
|
+
def database_icon_svg
|
87
|
+
<<~SVG
|
88
|
+
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
89
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
90
|
+
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"/>
|
91
|
+
</svg>
|
92
|
+
SVG
|
93
|
+
end
|
94
|
+
|
95
|
+
def runtime_icon_svg
|
96
|
+
<<~SVG
|
97
|
+
<svg class="w-4 h-4 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
98
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
99
|
+
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
|
100
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
101
|
+
</svg>
|
102
|
+
SVG
|
103
|
+
end
|
104
|
+
|
105
|
+
public
|
106
|
+
|
76
107
|
# Create action buttons with consistent styling
|
77
108
|
def action_button(text, path, color: "blue")
|
78
109
|
link_to text, path, class: "px-3 py-1 text-xs bg-#{color}-600 text-white rounded hover:bg-#{color}-700"
|
@@ -100,4 +131,5 @@ module Dbwatcher
|
|
100
131
|
end
|
101
132
|
end
|
102
133
|
end
|
134
|
+
# rubocop:enable Metrics/ModuleLength
|
103
135
|
end
|
@@ -2,39 +2,6 @@
|
|
2
2
|
|
3
3
|
module Dbwatcher
|
4
4
|
module DiagramHelper
|
5
|
-
# Generate diagram configuration for Alpine.js
|
6
|
-
def diagram_config(session, active_tab)
|
7
|
-
{
|
8
|
-
auto_generate: active_tab == "diagrams",
|
9
|
-
default_type: "database_tables",
|
10
|
-
endpoint: diagram_data_api_v1_session_path(session),
|
11
|
-
container_id: "diagram-container"
|
12
|
-
}.to_json
|
13
|
-
end
|
14
|
-
|
15
|
-
# Generate diagram type options dynamically from registry
|
16
|
-
def diagram_type_options
|
17
|
-
registry = Dbwatcher::Services::DiagramTypeRegistry.new
|
18
|
-
options = registry.available_types_with_metadata.map do |type, metadata|
|
19
|
-
[metadata[:display_name], type]
|
20
|
-
end
|
21
|
-
|
22
|
-
options_for_select(options)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Generate CSS variables for diagram container height calculation
|
26
|
-
def diagram_container_css_variables
|
27
|
-
{
|
28
|
-
"--header-height": "64px",
|
29
|
-
"--tab-bar-height": "40px",
|
30
|
-
"--toolbar-height": "72px",
|
31
|
-
"--footer-height": "0px",
|
32
|
-
"--diagram-height": "calc(100vh - var(--header-height) - var(--tab-bar-height) - " \
|
33
|
-
"var(--toolbar-height) - var(--footer-height) - 2rem)",
|
34
|
-
"--diagram-min-height": "500px"
|
35
|
-
}.map { |key, value| "#{key}: #{value}" }.join("; ")
|
36
|
-
end
|
37
|
-
|
38
5
|
# Generate button classes for diagram controls
|
39
6
|
def diagram_button_classes(type = :default)
|
40
7
|
base_classes = "compact-button text-xs rounded"
|
@@ -51,60 +18,5 @@ module Dbwatcher
|
|
51
18
|
style = button_styles[type] || button_styles[:primary]
|
52
19
|
"#{base_classes} #{style}"
|
53
20
|
end
|
54
|
-
|
55
|
-
# Generate a code view with syntax highlighting for Mermaid code
|
56
|
-
def diagram_code_view(content)
|
57
|
-
content_tag(:div, class: "diagram-code-view") do
|
58
|
-
content_tag(:pre,
|
59
|
-
class: "text-xs font-mono p-4 bg-gray-50 rounded border border-gray-200 " \
|
60
|
-
"overflow-x-auto whitespace-pre-wrap",
|
61
|
-
style: "max-height: calc(100vh - 220px); overflow-y: auto;") do
|
62
|
-
content_tag(:code, content)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Generate copy to clipboard button
|
68
|
-
def copy_to_clipboard_button(target_id)
|
69
|
-
button_tag(
|
70
|
-
type: "button",
|
71
|
-
class: diagram_button_classes(:icon),
|
72
|
-
"x-on:click": "copyToClipboard('#{target_id}')",
|
73
|
-
title: "Copy to clipboard"
|
74
|
-
) do
|
75
|
-
copy_icon_svg
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
# Generate copy icon SVG
|
82
|
-
def copy_icon_svg
|
83
|
-
content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
84
|
-
tag.path(
|
85
|
-
stroke_linecap: "round",
|
86
|
-
stroke_linejoin: "round",
|
87
|
-
stroke_width: "2",
|
88
|
-
d: "M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2" \
|
89
|
-
"M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
|
90
|
-
)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Generate toggle view button
|
95
|
-
def toggle_view_button
|
96
|
-
button_tag(
|
97
|
-
type: "button",
|
98
|
-
class: diagram_button_classes(:toggle),
|
99
|
-
"x-on:click": "toggleViewMode()",
|
100
|
-
title: "Toggle between code and preview"
|
101
|
-
) do
|
102
|
-
concat(content_tag(:svg, class: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24") do
|
103
|
-
tag.path(stroke_linecap: "round", stroke_linejoin: "round", stroke_width: "2",
|
104
|
-
d: "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4")
|
105
|
-
end)
|
106
|
-
concat(content_tag(:span, "x-text": "viewMode === 'preview' ? 'View Code' : 'View Preview'"))
|
107
|
-
end
|
108
|
-
end
|
109
21
|
end
|
110
22
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<%# Dashboard Layout - Common structure for all dashboard pages %>
|
2
|
+
<div class="h-full flex flex-col">
|
3
|
+
<!-- Compact Header -->
|
4
|
+
<div class="h-10 bg-navy-dark text-white flex items-center px-4">
|
5
|
+
<h1 class="text-sm font-medium">Dashboard</h1>
|
6
|
+
<span class="ml-auto text-xs text-blue-light">
|
7
|
+
<%= Time.current.strftime("%Y-%m-%d %H:%M:%S") %>
|
8
|
+
</span>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<!-- Tab Bar -->
|
12
|
+
<div class="tab-bar">
|
13
|
+
<%= link_to root_path, class: "tab-item #{active_tab == 'overview' ? 'active' : ''}" do %>
|
14
|
+
Overview
|
15
|
+
<% end %>
|
16
|
+
<% if @system_info_summary && !@system_info_summary.empty? %>
|
17
|
+
<%= link_to root_path(tab: 'system_info'), class: "tab-item #{active_tab == 'system_info' ? 'active' : ''}" do %>
|
18
|
+
System Info
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<!-- Content Area -->
|
24
|
+
<div class="flex-1 overflow-auto p-4">
|
25
|
+
<%= yield %>
|
26
|
+
</div>
|
27
|
+
</div>
|
@@ -0,0 +1,188 @@
|
|
1
|
+
<%# Dashboard Overview Tab Content %>
|
2
|
+
<!-- Overview Tab Content -->
|
3
|
+
<div class="tab-content active" data-tab-content="overview">
|
4
|
+
<!-- Compact Stats Grid -->
|
5
|
+
<div class="grid grid-cols-4 gap-3 mb-4">
|
6
|
+
<!-- Sessions Card -->
|
7
|
+
<%= render 'dbwatcher/shared/stats_card',
|
8
|
+
label: 'Active Sessions',
|
9
|
+
value: @recent_sessions&.count || 0,
|
10
|
+
description: 'Last 24 hours',
|
11
|
+
icon_html: stats_icon(:sessions) %>
|
12
|
+
|
13
|
+
<!-- Tables Card -->
|
14
|
+
<%= render 'dbwatcher/shared/stats_card',
|
15
|
+
label: 'Modified Tables',
|
16
|
+
value: @active_tables&.count || 0,
|
17
|
+
description: 'With changes',
|
18
|
+
icon_html: stats_icon(:tables) %>
|
19
|
+
|
20
|
+
<!-- Queries Card -->
|
21
|
+
<%= render 'dbwatcher/shared/stats_card',
|
22
|
+
label: 'SQL Queries',
|
23
|
+
value: @query_stats&.dig(:total) || 0,
|
24
|
+
description: 'Today',
|
25
|
+
icon_html: stats_icon(:queries) %>
|
26
|
+
|
27
|
+
<!-- Performance Card -->
|
28
|
+
<% slow_queries = @query_stats&.dig(:slow_queries) || 0 %>
|
29
|
+
<%= render 'dbwatcher/shared/stats_card',
|
30
|
+
label: 'Slow Queries',
|
31
|
+
value: slow_queries,
|
32
|
+
value_class: (slow_queries > 0 ? 'text-red-600' : 'text-navy-dark'),
|
33
|
+
description: '> 100ms',
|
34
|
+
icon_html: stats_icon(:performance) %>
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<!-- System Information Summary -->
|
38
|
+
<% if @system_info_summary && !@system_info_summary.empty? %>
|
39
|
+
<div class="grid grid-cols-4 gap-3 mb-4">
|
40
|
+
<!-- System Load Card -->
|
41
|
+
<%= render 'dbwatcher/shared/stats_card',
|
42
|
+
label: 'System Load',
|
43
|
+
value: @system_info_summary[:cpu_load] ? "#{@system_info_summary[:cpu_load]}" : 'N/A',
|
44
|
+
description: '1min avg',
|
45
|
+
icon_html: stats_icon(:system) %>
|
46
|
+
|
47
|
+
<!-- Memory Usage Card -->
|
48
|
+
<%= render 'dbwatcher/shared/stats_card',
|
49
|
+
label: 'Memory Usage',
|
50
|
+
value: @system_info_summary[:memory_usage] ? "#{@system_info_summary[:memory_usage]}%" : 'N/A',
|
51
|
+
value_class: (@system_info_summary[:memory_usage] && @system_info_summary[:memory_usage] > 80) ? 'text-red-600' : 'text-navy-dark',
|
52
|
+
description: 'of total',
|
53
|
+
icon_html: stats_icon(:memory) %>
|
54
|
+
|
55
|
+
<!-- DB Connections Card -->
|
56
|
+
<%= render 'dbwatcher/shared/stats_card',
|
57
|
+
label: 'DB Connections',
|
58
|
+
value: @system_info_summary[:active_connections] || 'N/A',
|
59
|
+
description: 'active',
|
60
|
+
icon_html: stats_icon(:database) %>
|
61
|
+
|
62
|
+
<!-- Runtime Info Card -->
|
63
|
+
<%= render 'dbwatcher/shared/stats_card',
|
64
|
+
label: 'Ruby/Rails',
|
65
|
+
value: @system_info_summary[:ruby_version] || 'N/A',
|
66
|
+
description: @system_info_summary[:rails_version] ? "Rails #{@system_info_summary[:rails_version]}" : 'Ruby only',
|
67
|
+
icon_html: stats_icon(:runtime) %>
|
68
|
+
</div>
|
69
|
+
<% end %>
|
70
|
+
|
71
|
+
<!-- Two Column Layout -->
|
72
|
+
<div class="grid grid-cols-2 gap-4">
|
73
|
+
<!-- Recent Sessions -->
|
74
|
+
<div class="border border-gray-200 rounded-lg shadow-sm">
|
75
|
+
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-3 border-b border-gray-200 rounded-t-lg">
|
76
|
+
<div class="flex items-center">
|
77
|
+
<svg class="w-4 h-4 text-blue-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
78
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
79
|
+
</svg>
|
80
|
+
<h3 class="text-sm font-semibold text-gray-800">Recent Sessions</h3>
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
<div class="max-h-64 overflow-auto">
|
84
|
+
<% if @recent_sessions&.any? %>
|
85
|
+
<div class="divide-y divide-gray-100">
|
86
|
+
<% @recent_sessions.each_with_index do |session, index| %>
|
87
|
+
<div class="px-4 py-3 hover:bg-gray-50 transition-colors duration-150">
|
88
|
+
<div class="flex items-center justify-between">
|
89
|
+
<div class="flex-1 min-w-0">
|
90
|
+
<% session_id = session[:id] || session['id'] %>
|
91
|
+
<% session_name = (session[:name] || session['name']).to_s.gsub(/^HTTP \w+ /, '') %>
|
92
|
+
<%= link_to session_path(session_id), class: "block" do %>
|
93
|
+
<div class="flex items-center">
|
94
|
+
<div class="h-2 w-2 bg-blue-400 rounded-full mr-3"></div>
|
95
|
+
<p class="text-sm font-medium text-gray-900 truncate" title="<%= session_name %>">
|
96
|
+
<%= session_name %>
|
97
|
+
</p>
|
98
|
+
</div>
|
99
|
+
<% end %>
|
100
|
+
<div class="flex items-center mt-1">
|
101
|
+
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
102
|
+
<%= session[:change_count] || session['change_count'] || 0 %> changes
|
103
|
+
</span>
|
104
|
+
<span class="ml-2 text-xs text-gray-500">
|
105
|
+
<% started_at = session[:started_at] || session['started_at'] %>
|
106
|
+
<%= Time.parse(started_at).strftime("%H:%M:%S") rescue 'N/A' %>
|
107
|
+
</span>
|
108
|
+
</div>
|
109
|
+
</div>
|
110
|
+
<div class="ml-4 flex-shrink-0">
|
111
|
+
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
112
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
113
|
+
</svg>
|
114
|
+
</div>
|
115
|
+
</div>
|
116
|
+
</div>
|
117
|
+
<% end %>
|
118
|
+
</div>
|
119
|
+
<% else %>
|
120
|
+
<div class="p-8 text-center text-gray-500">
|
121
|
+
<svg class="w-8 h-8 mx-auto text-gray-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
122
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
123
|
+
</svg>
|
124
|
+
<p class="text-sm">No recent sessions</p>
|
125
|
+
</div>
|
126
|
+
<% end %>
|
127
|
+
</div>
|
128
|
+
</div>
|
129
|
+
|
130
|
+
<!-- Active Tables -->
|
131
|
+
<div class="border border-gray-200 rounded-lg shadow-sm">
|
132
|
+
<div class="bg-gradient-to-r from-green-50 to-emerald-50 px-4 py-3 border-b border-gray-200 rounded-t-lg">
|
133
|
+
<div class="flex items-center">
|
134
|
+
<svg class="w-4 h-4 text-green-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
135
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"/>
|
136
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5a2 2 0 012-2h2a2 2 0 012 2v2H8V5z"/>
|
137
|
+
</svg>
|
138
|
+
<h3 class="text-sm font-semibold text-gray-800">Most Active Tables</h3>
|
139
|
+
</div>
|
140
|
+
</div>
|
141
|
+
<div class="max-h-64 overflow-auto">
|
142
|
+
<% if @active_tables&.any? %>
|
143
|
+
<div class="divide-y divide-gray-100">
|
144
|
+
<% @active_tables.first(10).each_with_index do |(table_name, count), index| %>
|
145
|
+
<div class="px-4 py-3 hover:bg-gray-50 transition-colors duration-150">
|
146
|
+
<div class="flex items-center justify-between">
|
147
|
+
<div class="flex-1 min-w-0">
|
148
|
+
<%= link_to table_path(table_name), class: "block" do %>
|
149
|
+
<div class="flex items-center">
|
150
|
+
<div class="h-2 w-2 bg-green-400 rounded-full mr-3"></div>
|
151
|
+
<p class="text-sm font-medium text-gray-900 truncate" title="<%= table_name %>">
|
152
|
+
<%= table_name %>
|
153
|
+
</p>
|
154
|
+
</div>
|
155
|
+
<% end %>
|
156
|
+
<div class="flex items-center mt-1">
|
157
|
+
<div class="flex gap-1">
|
158
|
+
<span class="badge badge-insert text-xs" title="Inserts">I</span>
|
159
|
+
<span class="badge badge-update text-xs" title="Updates">U</span>
|
160
|
+
<span class="badge badge-delete text-xs" title="Deletes">D</span>
|
161
|
+
</div>
|
162
|
+
<span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
163
|
+
<%= count %> changes
|
164
|
+
</span>
|
165
|
+
</div>
|
166
|
+
</div>
|
167
|
+
<div class="ml-4 flex-shrink-0">
|
168
|
+
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
169
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
170
|
+
</svg>
|
171
|
+
</div>
|
172
|
+
</div>
|
173
|
+
</div>
|
174
|
+
<% end %>
|
175
|
+
</div>
|
176
|
+
<% else %>
|
177
|
+
<div class="p-8 text-center text-gray-500">
|
178
|
+
<svg class="w-8 h-8 mx-auto text-gray-400 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
179
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z"/>
|
180
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5a2 2 0 012-2h2a2 2 0 012 2v2H8V5z"/>
|
181
|
+
</svg>
|
182
|
+
<p class="text-sm">No active tables</p>
|
183
|
+
</div>
|
184
|
+
<% end %>
|
185
|
+
</div>
|
186
|
+
</div>
|
187
|
+
</div>
|
188
|
+
</div>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<div class="system-info-container">
|
2
|
+
<div class="flex items-center justify-between mb-4">
|
3
|
+
<h2 class="text-lg font-semibold text-gray-800">System Information</h2>
|
4
|
+
<div class="flex items-center gap-2">
|
5
|
+
<% if @system_info&.dig(:collected_at) %>
|
6
|
+
<span class="text-sm text-gray-500">
|
7
|
+
Last updated: <%= time_ago_in_words(Time.parse(@system_info[:collected_at])) %> ago
|
8
|
+
</span>
|
9
|
+
<% end %>
|
10
|
+
<button id="refresh-system-info" class="px-3 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors">
|
11
|
+
Refresh
|
12
|
+
</button>
|
13
|
+
<button id="clear-cache-system-info" class="px-3 py-1 text-sm bg-red-600 text-white rounded hover:bg-red-700 transition-colors">
|
14
|
+
Clear Cache
|
15
|
+
</button>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div id="system-info-content">
|
20
|
+
<%= render 'dbwatcher/dashboard/system_info_content' %>
|
21
|
+
</div>
|
22
|
+
</div>
|