mysql_genius-core 0.7.0 → 0.7.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/CHANGELOG.md +14 -0
- data/lib/mysql_genius/core/ai/client.rb +44 -8
- data/lib/mysql_genius/core/ai/config.rb +2 -1
- data/lib/mysql_genius/core/version.rb +1 -1
- data/lib/mysql_genius/core/views/mysql_genius/queries/_shared_results.html.erb +56 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_ai_tools.html.erb +43 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_dashboard.html.erb +97 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_duplicate_indexes.html.erb +35 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_query_explorer.html.erb +110 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_query_stats.html.erb +26 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_server.html.erb +54 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_slow_queries.html.erb +17 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_table_sizes.html.erb +33 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/_tab_unused_indexes.html.erb +36 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/dashboard.html.erb +1595 -0
- data/lib/mysql_genius/core/views/mysql_genius/queries/query_detail.html.erb +465 -0
- data/mysql_genius-core.gemspec +1 -1
- metadata +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 321f2469ac2b5213d704f7afd4ac6211a0a327d1c8d054c9fdb95843dd4b2bde
|
|
4
|
+
data.tar.gz: 62a5b202b55bf2ffa2c7d76b5c263c7685bd4d9de14f206903ae35604dd146ce
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5666c4264a19bfddb7052233e3d02c9499fdbf7ea6ffb3b0e4fa60c1bea117de03a3a349cf399b716cb947733050ac053a5cc52c287d2798b17ed7f6160c6306
|
|
7
|
+
data.tar.gz: c5f99646c2c9da80629824366b5ee1a0dcf7710cf84bf06f2cfe8e783d4f42b1a3fd0d4635a9ddc1fd7ff29ea5c7e4e77734bd4f995fef47ea0a32a6174e4504
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.2
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Anthropic Messages API support** in `Core::Ai::Client` — detects `:x_api_key` auth style, sends `x-api-key` + `anthropic-version` headers, uses top-level `system` parameter and `content[0].text` response parsing.
|
|
7
|
+
- **`max_tokens` field** on `Core::Ai::Config` (default 4096) — sent in both OpenAI and Anthropic request bodies.
|
|
8
|
+
- **Copy response button** on all AI result sections in the shared dashboard template.
|
|
9
|
+
- **Dark mode CSS classes** for AI result sections (`mg-ai-section`, `mg-ai-danger`, `mg-ai-warning`, `mg-ai-info`) replacing hardcoded inline styles.
|
|
10
|
+
- **`capability?(:standalone_header)`** guard on the dashboard heading — hides it when the rendering adapter provides its own header.
|
|
11
|
+
|
|
12
|
+
## 0.7.1
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **ERB templates missing from gem package.** The `spec.files` glob in the gemspec only matched `*.rb` files, excluding the shared ERB templates at `lib/mysql_genius/core/views/`. The `mysql_genius-desktop` sidecar crashed with `Errno::ENOENT` when installed from RubyGems (vs path dependency). Fixed by changing the glob to `*.{rb,erb}`.
|
|
16
|
+
|
|
3
17
|
## 0.7.0
|
|
4
18
|
|
|
5
19
|
### Added
|
|
@@ -27,12 +27,11 @@ module MysqlGenius
|
|
|
27
27
|
|
|
28
28
|
raise NotConfigured, "AI is not configured" unless @config.enabled?
|
|
29
29
|
|
|
30
|
-
body =
|
|
31
|
-
messages
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
body[:model] = @config.model if @config.model && !@config.model.empty?
|
|
30
|
+
body = if anthropic?
|
|
31
|
+
build_anthropic_body(messages, temperature)
|
|
32
|
+
else
|
|
33
|
+
build_openai_body(messages, temperature)
|
|
34
|
+
end
|
|
36
35
|
|
|
37
36
|
response = post_with_redirects(URI(@config.endpoint), body.to_json)
|
|
38
37
|
parsed = JSON.parse(response.body)
|
|
@@ -41,7 +40,11 @@ module MysqlGenius
|
|
|
41
40
|
raise ApiError, "AI API error: #{parsed["error"]["message"] || parsed["error"]}"
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
content =
|
|
43
|
+
content = if anthropic?
|
|
44
|
+
parsed.dig("content", 0, "text")
|
|
45
|
+
else
|
|
46
|
+
parsed.dig("choices", 0, "message", "content")
|
|
47
|
+
end
|
|
45
48
|
raise ApiError, "No content in AI response" if content.nil?
|
|
46
49
|
|
|
47
50
|
parse_json_content(content)
|
|
@@ -49,6 +52,35 @@ module MysqlGenius
|
|
|
49
52
|
|
|
50
53
|
private
|
|
51
54
|
|
|
55
|
+
def anthropic?
|
|
56
|
+
@config.auth_style == :x_api_key
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_openai_body(messages, temperature)
|
|
60
|
+
body = {
|
|
61
|
+
messages: messages,
|
|
62
|
+
response_format: { type: "json_object" },
|
|
63
|
+
temperature: temperature,
|
|
64
|
+
}
|
|
65
|
+
body[:max_tokens] = @config.max_tokens if @config.max_tokens
|
|
66
|
+
body[:model] = @config.model if @config.model && !@config.model.empty?
|
|
67
|
+
body
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def build_anthropic_body(messages, temperature)
|
|
71
|
+
system_text = messages.select { |m| m[:role] == "system" }.map { |m| m[:content] }.join("\n\n")
|
|
72
|
+
user_messages = messages.reject { |m| m[:role] == "system" }
|
|
73
|
+
|
|
74
|
+
body = {
|
|
75
|
+
messages: user_messages,
|
|
76
|
+
max_tokens: @config.max_tokens || 4096,
|
|
77
|
+
temperature: temperature,
|
|
78
|
+
}
|
|
79
|
+
body[:system] = system_text unless system_text.empty?
|
|
80
|
+
body[:model] = @config.model if @config.model && !@config.model.empty?
|
|
81
|
+
body
|
|
82
|
+
end
|
|
83
|
+
|
|
52
84
|
def parse_json_content(content)
|
|
53
85
|
JSON.parse(content)
|
|
54
86
|
rescue JSON::ParserError
|
|
@@ -78,8 +110,12 @@ module MysqlGenius
|
|
|
78
110
|
|
|
79
111
|
request = Net::HTTP::Post.new(uri)
|
|
80
112
|
request["Content-Type"] = "application/json"
|
|
81
|
-
|
|
113
|
+
case @config.auth_style
|
|
114
|
+
when :bearer
|
|
82
115
|
request["Authorization"] = "Bearer #{@config.api_key}"
|
|
116
|
+
when :x_api_key
|
|
117
|
+
request["x-api-key"] = @config.api_key
|
|
118
|
+
request["anthropic-version"] = "2023-06-01"
|
|
83
119
|
else
|
|
84
120
|
request["api-key"] = @config.api_key
|
|
85
121
|
end
|
|
@@ -26,10 +26,11 @@ module MysqlGenius
|
|
|
26
26
|
:auth_style,
|
|
27
27
|
:system_context,
|
|
28
28
|
:domain_context,
|
|
29
|
+
:max_tokens,
|
|
29
30
|
keyword_init: true,
|
|
30
31
|
) do
|
|
31
32
|
def initialize(**kwargs)
|
|
32
|
-
super(domain_context: "", **kwargs)
|
|
33
|
+
super(domain_context: "", max_tokens: 4096, **kwargs)
|
|
33
34
|
freeze
|
|
34
35
|
end
|
|
35
36
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!-- Explain Results -->
|
|
2
|
+
<div id="explain-results" class="mg-mt mg-hidden">
|
|
3
|
+
<div class="mg-card">
|
|
4
|
+
<div class="mg-card-header">
|
|
5
|
+
<span><strong>🔎 EXPLAIN Output</strong></span>
|
|
6
|
+
<div>
|
|
7
|
+
<% if @ai_enabled %>
|
|
8
|
+
<button id="explain-optimize" class="mg-btn mg-btn-outline mg-btn-sm">⚡ AI Optimization</button>
|
|
9
|
+
<button id="explain-index-advisor" class="mg-btn mg-btn-outline mg-btn-sm">⚡ Index Advisor</button>
|
|
10
|
+
<% end %>
|
|
11
|
+
<button id="explain-close" class="mg-btn mg-btn-outline-secondary mg-btn-sm">✕ Close</button>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="mg-card-body">
|
|
15
|
+
<div class="mg-table-wrap">
|
|
16
|
+
<table class="mg-table">
|
|
17
|
+
<thead id="explain-thead"></thead>
|
|
18
|
+
<tbody id="explain-tbody"></tbody>
|
|
19
|
+
</table>
|
|
20
|
+
</div>
|
|
21
|
+
<div id="optimize-results" class="mg-hidden mg-mt">
|
|
22
|
+
<div id="optimize-content" class="mg-alert mg-alert-info"></div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Results Area -->
|
|
29
|
+
<div id="query-results" class="mg-mt">
|
|
30
|
+
<div id="results-alert" class="mg-hidden"></div>
|
|
31
|
+
<div id="results-stats" class="mg-mb mg-hidden">
|
|
32
|
+
<span id="results-row-count" class="mg-badge mg-badge-info"></span>
|
|
33
|
+
<span id="results-time" class="mg-badge mg-badge-secondary"></span>
|
|
34
|
+
<span id="results-truncated" class="mg-badge mg-badge-warning mg-hidden">Results truncated</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div id="results-table-wrapper" class="mg-table-wrap mg-hidden">
|
|
37
|
+
<table class="mg-table">
|
|
38
|
+
<thead id="results-thead"></thead>
|
|
39
|
+
<tbody id="results-tbody"></tbody>
|
|
40
|
+
</table>
|
|
41
|
+
</div>
|
|
42
|
+
<div id="results-empty" class="mg-text-center mg-text-muted mg-hidden">No rows returned.</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- AI Query Analysis Results -->
|
|
46
|
+
<div id="ai-query-result" class="mg-mt mg-hidden">
|
|
47
|
+
<div class="mg-card">
|
|
48
|
+
<div class="mg-card-header">
|
|
49
|
+
<span id="ai-query-title"><strong>⚡ AI Analysis</strong></span>
|
|
50
|
+
<button id="ai-query-close" class="mg-btn mg-btn-outline-secondary mg-btn-sm">✕</button>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="mg-card-body">
|
|
53
|
+
<div id="ai-query-content" style="font-size:13px;"></div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!-- AI Tools Tab -->
|
|
2
|
+
<div class="mg-tab-content" id="tab-aitools">
|
|
3
|
+
<!-- Schema Review -->
|
|
4
|
+
<div class="mg-card mg-mb">
|
|
5
|
+
<div class="mg-card-header"><strong>⚡ Schema Review</strong></div>
|
|
6
|
+
<div class="mg-card-body">
|
|
7
|
+
<div class="mg-text-muted mg-mb" style="font-size:12px;">Analyze your schema for anti-patterns: inappropriate column types, missing indexes, naming inconsistencies, and more.</div>
|
|
8
|
+
<div class="mg-row" style="align-items:flex-end;">
|
|
9
|
+
<div class="mg-col-4 mg-field">
|
|
10
|
+
<label for="schema-table">Table (leave blank for all)</label>
|
|
11
|
+
<select id="schema-table">
|
|
12
|
+
<option value="">All tables (top 20)</option>
|
|
13
|
+
<% @all_tables.each do |table| %>
|
|
14
|
+
<option value="<%= table %>"><%= table %></option>
|
|
15
|
+
<% end %>
|
|
16
|
+
</select>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="mg-field">
|
|
19
|
+
<button id="schema-review-btn" class="mg-btn mg-btn-primary mg-btn-sm">⚡ Analyze Schema</button>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div id="schema-result" class="mg-mt mg-hidden">
|
|
23
|
+
<div id="schema-result-content" style="font-size:13px;"></div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Migration Risk Assessment -->
|
|
29
|
+
<div class="mg-card">
|
|
30
|
+
<div class="mg-card-header"><strong>⚡ Migration Risk Assessment</strong></div>
|
|
31
|
+
<div class="mg-card-body">
|
|
32
|
+
<div class="mg-text-muted mg-mb" style="font-size:12px;">Paste a Rails migration or DDL and get a risk assessment: lock duration, impact on active queries, deployment strategy.</div>
|
|
33
|
+
<div class="mg-field">
|
|
34
|
+
<textarea id="migration-input" rows="8" placeholder="class AddIndexToUsers < ActiveRecord::Migration[7.0] def change add_index :users, :email, unique: true end end"></textarea>
|
|
35
|
+
</div>
|
|
36
|
+
<button id="migration-assess-btn" class="mg-btn mg-btn-primary mg-btn-sm">⚡ Assess Risk</button>
|
|
37
|
+
<div id="migration-result" class="mg-mt mg-hidden">
|
|
38
|
+
<div id="migration-risk-badge" style="margin-bottom:8px;"></div>
|
|
39
|
+
<div id="migration-result-content" style="font-size:13px;"></div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<!-- Dashboard Tab -->
|
|
2
|
+
<div class="mg-tab-content active" id="tab-dashboard">
|
|
3
|
+
<div id="dash-loading" class="mg-text-center"><span class="mg-spinner"></span> Loading dashboard...</div>
|
|
4
|
+
<div id="dash-error" class="mg-hidden"></div>
|
|
5
|
+
<div id="dash-content" class="mg-hidden">
|
|
6
|
+
|
|
7
|
+
<!-- Server Summary -->
|
|
8
|
+
<div class="mg-card mg-mb">
|
|
9
|
+
<div class="mg-card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
10
|
+
<strong>Server</strong>
|
|
11
|
+
<button class="mg-btn mg-btn-outline-secondary mg-btn-sm dash-jump-tab" data-target="server">Details →</button>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px;margin-bottom:16px;">
|
|
15
|
+
<div class="mg-card">
|
|
16
|
+
<div class="mg-card-header"><strong>Overview</strong></div>
|
|
17
|
+
<div class="mg-card-body">
|
|
18
|
+
<div class="mg-stat-grid" id="dash-server-info"></div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="mg-card">
|
|
22
|
+
<div class="mg-card-header"><strong>Connections</strong></div>
|
|
23
|
+
<div class="mg-card-body">
|
|
24
|
+
<div id="dash-conn-bar" style="margin-bottom:8px;"></div>
|
|
25
|
+
<div class="mg-stat-grid" id="dash-conn-info"></div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="mg-card">
|
|
29
|
+
<div class="mg-card-header"><strong>InnoDB Buffer Pool</strong></div>
|
|
30
|
+
<div class="mg-card-body">
|
|
31
|
+
<div id="dash-innodb-bar" style="margin-bottom:8px;"></div>
|
|
32
|
+
<div class="mg-stat-grid" id="dash-innodb-info"></div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="mg-card">
|
|
36
|
+
<div class="mg-card-header"><strong>Query Activity</strong></div>
|
|
37
|
+
<div class="mg-card-body">
|
|
38
|
+
<div class="mg-stat-grid" id="dash-query-info"></div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<% if capability?(:slow_queries) %>
|
|
44
|
+
<!-- Top 5 Slow Queries -->
|
|
45
|
+
<div class="mg-card mg-mb">
|
|
46
|
+
<div class="mg-card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
47
|
+
<strong>Slow Queries</strong>
|
|
48
|
+
<button class="mg-btn mg-btn-outline-secondary mg-btn-sm dash-jump-tab" data-target="slow">View all →</button>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="mg-card-body">
|
|
51
|
+
<div id="dash-slow-empty" class="mg-text-muted mg-hidden"></div>
|
|
52
|
+
<div id="dash-slow-table" class="mg-table-wrap mg-hidden">
|
|
53
|
+
<table class="mg-table">
|
|
54
|
+
<thead><tr><th style="width:100px">Duration</th><th style="width:160px">Time</th><th>SQL</th></tr></thead>
|
|
55
|
+
<tbody id="dash-slow-tbody"></tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<% end %>
|
|
61
|
+
|
|
62
|
+
<!-- Top 5 Expensive Queries -->
|
|
63
|
+
<div class="mg-card mg-mb">
|
|
64
|
+
<div class="mg-card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
65
|
+
<strong>Most Expensive Queries</strong>
|
|
66
|
+
<button class="mg-btn mg-btn-outline-secondary mg-btn-sm dash-jump-tab" data-target="qstats">View all →</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="mg-card-body">
|
|
69
|
+
<div id="dash-qstats-empty" class="mg-text-muted mg-hidden"></div>
|
|
70
|
+
<div id="dash-qstats-error" class="mg-hidden"></div>
|
|
71
|
+
<div id="dash-qstats-table" class="mg-table-wrap mg-hidden">
|
|
72
|
+
<table class="mg-table">
|
|
73
|
+
<thead><tr><th>Query</th><th style="text-align:right">Calls</th><th style="text-align:right">Total Time</th><th style="text-align:right">Avg Time</th></tr></thead>
|
|
74
|
+
<tbody id="dash-qstats-tbody"></tbody>
|
|
75
|
+
</table>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Index Alerts -->
|
|
81
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:12px;">
|
|
82
|
+
<div class="mg-card dash-jump-tab" data-target="indexes" style="cursor:pointer;">
|
|
83
|
+
<div class="mg-card-body mg-text-center">
|
|
84
|
+
<div id="dash-dup-count" style="font-size:24px;font-weight:700;">--</div>
|
|
85
|
+
<div class="mg-text-muted">Duplicate Indexes</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="mg-card dash-jump-tab" data-target="unused" style="cursor:pointer;">
|
|
89
|
+
<div class="mg-card-body mg-text-center">
|
|
90
|
+
<div id="dash-unused-count" style="font-size:24px;font-weight:700;">--</div>
|
|
91
|
+
<div class="mg-text-muted">Unused Indexes</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!-- Duplicate Indexes Tab -->
|
|
2
|
+
<div class="mg-tab-content" id="tab-indexes">
|
|
3
|
+
<div class="mg-row" style="justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
4
|
+
<div class="mg-text-muted">Indexes whose columns are a left-prefix of another index on the same table. These are redundant and can safely be dropped. <span id="dup-count" class="mg-badge mg-badge-secondary"></span></div>
|
|
5
|
+
<button id="dup-refresh" class="mg-btn mg-btn-outline-secondary mg-btn-sm">↻ Refresh</button>
|
|
6
|
+
</div>
|
|
7
|
+
<div id="dup-loading" class="mg-text-center mg-hidden"><span class="mg-spinner"></span> Scanning indexes...</div>
|
|
8
|
+
<div id="dup-empty" class="mg-text-center mg-text-muted mg-hidden">No duplicate indexes found.</div>
|
|
9
|
+
<div id="dup-table-wrapper" class="mg-table-wrap mg-hidden">
|
|
10
|
+
<table class="mg-table">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Table</th>
|
|
14
|
+
<th>Duplicate Index</th>
|
|
15
|
+
<th>Columns</th>
|
|
16
|
+
<th>Covered By</th>
|
|
17
|
+
<th>Covered Columns</th>
|
|
18
|
+
<th>DROP Statement</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody id="dup-tbody"></tbody>
|
|
22
|
+
</table>
|
|
23
|
+
</div>
|
|
24
|
+
<div id="dup-migration" class="mg-hidden" style="margin-top:16px;">
|
|
25
|
+
<div class="mg-card">
|
|
26
|
+
<div class="mg-card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
27
|
+
<strong>Suggested Migration</strong>
|
|
28
|
+
<button id="dup-copy-migration" class="mg-btn mg-btn-outline-secondary mg-btn-sm">📋 Copy</button>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="mg-card-body">
|
|
31
|
+
<pre id="dup-migration-code" style="font-size:12px;margin:0;white-space:pre-wrap;user-select:all;"></pre>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!-- Query Explorer Tab -->
|
|
2
|
+
<div class="mg-tab-content" id="tab-explorer">
|
|
3
|
+
<div style="margin-bottom:12px;">
|
|
4
|
+
<button class="mg-btn mg-btn-sm qe-mode active" data-mode="visual">Visual Builder</button>
|
|
5
|
+
<button class="mg-btn mg-btn-sm mg-btn-outline qe-mode" data-mode="sql">SQL Editor</button>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<!-- Visual Builder Mode -->
|
|
9
|
+
<div id="qe-visual">
|
|
10
|
+
<div class="mg-row mg-mb">
|
|
11
|
+
<div class="mg-col-4 mg-field">
|
|
12
|
+
<label for="vb-table">Table</label>
|
|
13
|
+
<select id="vb-table">
|
|
14
|
+
<option value="">-- Select a table --</option>
|
|
15
|
+
<% if @featured_tables != @all_tables %>
|
|
16
|
+
<optgroup label="Featured">
|
|
17
|
+
<% @featured_tables.each do |table| %>
|
|
18
|
+
<option value="<%= table %>"><%= table %></option>
|
|
19
|
+
<% end %>
|
|
20
|
+
</optgroup>
|
|
21
|
+
<optgroup label="All Tables">
|
|
22
|
+
<% (@all_tables - @featured_tables).each do |table| %>
|
|
23
|
+
<option value="<%= table %>"><%= table %></option>
|
|
24
|
+
<% end %>
|
|
25
|
+
</optgroup>
|
|
26
|
+
<% else %>
|
|
27
|
+
<% @all_tables.each do |table| %>
|
|
28
|
+
<option value="<%= table %>"><%= table %></option>
|
|
29
|
+
<% end %>
|
|
30
|
+
<% end %>
|
|
31
|
+
</select>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="mg-col-2 mg-field">
|
|
34
|
+
<label for="vb-row-limit">Row Limit</label>
|
|
35
|
+
<input type="number" id="vb-row-limit" value="25" min="1" max="<%= MysqlGenius.configuration.max_row_limit %>">
|
|
36
|
+
</div>
|
|
37
|
+
<div class="mg-field">
|
|
38
|
+
<label> </label>
|
|
39
|
+
<button id="vb-run" class="mg-btn mg-btn-primary" disabled>▶ Run Query</button>
|
|
40
|
+
<button id="vb-explain" class="mg-btn mg-btn-outline" disabled>🔎 Explain</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div id="vb-columns-section" class="mg-mb mg-hidden">
|
|
45
|
+
<label>Columns
|
|
46
|
+
<span id="vb-toggle-all" class="mg-link">Toggle All</span>
|
|
47
|
+
<span id="vb-show-defaults" class="mg-link">Reset Defaults</span>
|
|
48
|
+
</label>
|
|
49
|
+
<div id="vb-columns" class="mg-checks"></div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div id="vb-filters-section" class="mg-mb mg-hidden">
|
|
53
|
+
<label>Filters</label>
|
|
54
|
+
<div id="vb-filters"></div>
|
|
55
|
+
<button id="vb-add-filter" class="mg-btn mg-btn-outline-secondary mg-btn-sm" style="margin-top:4px;">+ Add Filter</button>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div id="vb-order-section" class="mg-mb mg-hidden">
|
|
59
|
+
<label>Order By</label>
|
|
60
|
+
<div id="vb-orders"></div>
|
|
61
|
+
<button id="vb-add-order" class="mg-btn mg-btn-outline-secondary mg-btn-sm" style="margin-top:4px;">+ Add Sort</button>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div id="vb-generated-sql" class="mg-mb mg-hidden">
|
|
65
|
+
<label>Generated SQL</label>
|
|
66
|
+
<textarea id="vb-sql-preview" rows="2" readonly></textarea>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- SQL Editor Mode -->
|
|
71
|
+
<div id="qe-sql" class="mg-hidden">
|
|
72
|
+
<% if @ai_enabled %>
|
|
73
|
+
<div class="mg-card mg-mb">
|
|
74
|
+
<div class="mg-card-header">
|
|
75
|
+
<span class="mg-card-toggle" id="ai-toggle">⚡ AI Assistant <span class="mg-text-muted">- Describe what you want in plain English</span></span>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="mg-card-body mg-hidden" id="ai-panel">
|
|
78
|
+
<div class="mg-field">
|
|
79
|
+
<textarea id="ai-prompt" rows="2" placeholder="e.g. Show me all users created in the last 30 days"></textarea>
|
|
80
|
+
<div class="mg-text-muted" style="margin-top:2px;">Only table names and column names are sent to the AI. No row data leaves the system.</div>
|
|
81
|
+
</div>
|
|
82
|
+
<button id="ai-suggest" class="mg-btn mg-btn-primary mg-btn-sm mg-mb">⚡ Suggest Query</button>
|
|
83
|
+
<div id="ai-result" class="mg-hidden">
|
|
84
|
+
<div id="ai-explanation" class="mg-alert mg-alert-info"></div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<% end %>
|
|
89
|
+
|
|
90
|
+
<div class="mg-field">
|
|
91
|
+
<label for="sql-input">SQL Query</label>
|
|
92
|
+
<textarea id="sql-input" rows="5" placeholder="SELECT * FROM users LIMIT 10"></textarea>
|
|
93
|
+
</div>
|
|
94
|
+
<div class="mg-row">
|
|
95
|
+
<div class="mg-col-2 mg-field">
|
|
96
|
+
<label for="sql-row-limit">Row Limit</label>
|
|
97
|
+
<input type="number" id="sql-row-limit" value="25" min="1" max="<%= MysqlGenius.configuration.max_row_limit %>">
|
|
98
|
+
</div>
|
|
99
|
+
<div class="mg-field">
|
|
100
|
+
<label> </label>
|
|
101
|
+
<button id="sql-run" class="mg-btn mg-btn-primary">▶ Run Query</button>
|
|
102
|
+
<button id="sql-explain" class="mg-btn mg-btn-outline">🔎 Explain</button>
|
|
103
|
+
<% if @ai_enabled %>
|
|
104
|
+
<button id="sql-describe" class="mg-btn mg-btn-outline mg-btn-sm" style="margin-left:8px;">⚡ Describe</button>
|
|
105
|
+
<button id="sql-rewrite" class="mg-btn mg-btn-outline mg-btn-sm">⚡ Rewrite</button>
|
|
106
|
+
<% end %>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!-- Query Stats Tab -->
|
|
2
|
+
<div class="mg-tab-content" id="tab-qstats">
|
|
3
|
+
<div class="mg-row" style="justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
4
|
+
<div class="mg-text-muted">Top queries from <code>performance_schema.events_statements_summary_by_digest</code>. <span id="qstats-count" class="mg-badge mg-badge-secondary"></span></div>
|
|
5
|
+
</div>
|
|
6
|
+
<div id="qstats-loading" class="mg-text-center mg-hidden"><span class="mg-spinner"></span> Loading...</div>
|
|
7
|
+
<div id="qstats-error" class="mg-hidden"></div>
|
|
8
|
+
<div id="qstats-empty" class="mg-text-center mg-text-muted mg-hidden">No query statistics available.</div>
|
|
9
|
+
<div id="qstats-table-wrapper" class="mg-table-wrap mg-hidden">
|
|
10
|
+
<table class="mg-table">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Query</th>
|
|
14
|
+
<th style="text-align:right">Calls</th>
|
|
15
|
+
<th style="text-align:right">Total Time</th>
|
|
16
|
+
<th style="text-align:right">Avg Time</th>
|
|
17
|
+
<th style="text-align:right">Max Time</th>
|
|
18
|
+
<th style="text-align:right">Rows Examined</th>
|
|
19
|
+
<th style="text-align:right">Rows Sent</th>
|
|
20
|
+
<th style="text-align:right">Exam/Sent</th>
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
<tbody id="qstats-tbody"></tbody>
|
|
24
|
+
</table>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!-- Server Tab -->
|
|
2
|
+
<div class="mg-tab-content" id="tab-server">
|
|
3
|
+
<div class="mg-row" style="justify-content:flex-end;margin-bottom:12px;">
|
|
4
|
+
<% if @ai_enabled && capability?(:slow_queries) %>
|
|
5
|
+
<button id="server-root-cause" class="mg-btn mg-btn-primary mg-btn-sm">⚡ Why is it slow?</button>
|
|
6
|
+
<button id="server-anomaly" class="mg-btn mg-btn-outline mg-btn-sm">⚡ Anomaly Detection</button>
|
|
7
|
+
<% end %>
|
|
8
|
+
<button id="server-refresh" class="mg-btn mg-btn-outline-secondary mg-btn-sm">↻ Refresh</button>
|
|
9
|
+
</div>
|
|
10
|
+
<div id="server-loading" class="mg-text-center mg-hidden"><span class="mg-spinner"></span> Loading...</div>
|
|
11
|
+
<div id="server-error" class="mg-hidden"></div>
|
|
12
|
+
<div id="server-content" class="mg-hidden">
|
|
13
|
+
<!-- Server Info -->
|
|
14
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;margin-bottom:16px;">
|
|
15
|
+
<div class="mg-card">
|
|
16
|
+
<div class="mg-card-header"><strong>Server</strong></div>
|
|
17
|
+
<div class="mg-card-body">
|
|
18
|
+
<div class="mg-stat-grid" id="server-info"></div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="mg-card">
|
|
22
|
+
<div class="mg-card-header"><strong>Connections</strong></div>
|
|
23
|
+
<div class="mg-card-body">
|
|
24
|
+
<div id="conn-bar" style="margin-bottom:8px;"></div>
|
|
25
|
+
<div class="mg-stat-grid" id="conn-info"></div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="mg-card">
|
|
29
|
+
<div class="mg-card-header"><strong>InnoDB Buffer Pool</strong></div>
|
|
30
|
+
<div class="mg-card-body">
|
|
31
|
+
<div id="innodb-bar" style="margin-bottom:8px;"></div>
|
|
32
|
+
<div class="mg-stat-grid" id="innodb-info"></div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="mg-card">
|
|
36
|
+
<div class="mg-card-header"><strong>Query Activity</strong></div>
|
|
37
|
+
<div class="mg-card-body">
|
|
38
|
+
<div class="mg-stat-grid" id="query-info"></div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div id="server-ai-result" class="mg-mt mg-hidden">
|
|
43
|
+
<div class="mg-card">
|
|
44
|
+
<div class="mg-card-header">
|
|
45
|
+
<span id="server-ai-title"><strong>⚡ AI Analysis</strong></span>
|
|
46
|
+
<button id="server-ai-close" class="mg-btn mg-btn-outline-secondary mg-btn-sm">✕</button>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="mg-card-body">
|
|
49
|
+
<div id="server-ai-content" style="font-size:13px;"></div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- Slow Queries Tab -->
|
|
2
|
+
<div class="mg-tab-content" id="tab-slow">
|
|
3
|
+
<div class="mg-row" style="justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
4
|
+
<div class="mg-text-muted">SELECT queries exceeding <%= MysqlGenius.configuration.slow_query_threshold_ms %>ms. <span id="slow-count" class="mg-badge mg-badge-secondary"></span></div>
|
|
5
|
+
<button id="slow-refresh" class="mg-btn mg-btn-outline-secondary mg-btn-sm">↻ Refresh</button>
|
|
6
|
+
</div>
|
|
7
|
+
<div id="slow-loading" class="mg-text-center mg-hidden"><span class="mg-spinner"></span> Loading...</div>
|
|
8
|
+
<div id="slow-empty" class="mg-text-center mg-text-muted mg-hidden">No slow queries recorded.</div>
|
|
9
|
+
<div id="slow-table-wrapper" class="mg-table-wrap mg-hidden">
|
|
10
|
+
<table class="mg-table">
|
|
11
|
+
<thead>
|
|
12
|
+
<tr><th style="width:100px">Duration</th><th style="width:160px">Time</th><th>SQL</th><th style="width:150px">Actions</th></tr>
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody id="slow-tbody"></tbody>
|
|
15
|
+
</table>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<!-- Tables Tab -->
|
|
2
|
+
<div class="mg-tab-content" id="tab-tables">
|
|
3
|
+
<div class="mg-row" style="justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
4
|
+
<div class="mg-text-muted">Table details from <code>information_schema</code>, sorted by total size. <span id="sizes-total" class="mg-badge mg-badge-secondary"></span></div>
|
|
5
|
+
<button id="sizes-refresh" class="mg-btn mg-btn-outline-secondary mg-btn-sm">↻ Refresh</button>
|
|
6
|
+
</div>
|
|
7
|
+
<div id="sizes-loading" class="mg-text-center mg-hidden"><span class="mg-spinner"></span> Loading...</div>
|
|
8
|
+
<div id="sizes-chart-wrapper" class="mg-hidden" style="margin-bottom:16px;">
|
|
9
|
+
<div style="display:flex;align-items:center;gap:24px;flex-wrap:wrap;">
|
|
10
|
+
<canvas id="sizes-pie" width="280" height="280" style="flex-shrink:0;"></canvas>
|
|
11
|
+
<div id="sizes-legend" style="font-size:12px;line-height:1.8;"></div>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div id="sizes-table-wrapper" class="mg-table-wrap mg-hidden">
|
|
15
|
+
<table class="mg-table">
|
|
16
|
+
<thead>
|
|
17
|
+
<tr>
|
|
18
|
+
<th>Table</th>
|
|
19
|
+
<th style="text-align:right">Rows</th>
|
|
20
|
+
<th>Engine</th>
|
|
21
|
+
<th style="text-align:right">Data</th>
|
|
22
|
+
<th style="text-align:right">Indexes</th>
|
|
23
|
+
<th style="text-align:right">Total</th>
|
|
24
|
+
<th style="text-align:right">Fragmented</th>
|
|
25
|
+
<th>Updated</th>
|
|
26
|
+
<th style="width:200px" data-no-sort></th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody id="sizes-tbody"></tbody>
|
|
30
|
+
</table>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
</div>
|