mysql_genius 0.3.0 → 0.3.1
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/.github/workflows/publish.yml +32 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +9 -0
- data/README.md +31 -200
- data/app/controllers/concerns/mysql_genius/database_analysis.rb +8 -0
- data/app/views/layouts/mysql_genius/application.html.erb +7 -1
- data/app/views/mysql_genius/queries/_tab_query_stats.html.erb +2 -2
- data/app/views/mysql_genius/queries/_tab_table_sizes.html.erb +1 -1
- data/app/views/mysql_genius/queries/index.html.erb +59 -0
- data/lib/mysql_genius/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e54ecfd5bfe23f9c1e97caee0dd7d76499375488a37c2bbf2f58951b2b37dd85
|
|
4
|
+
data.tar.gz: d0fd0253422d4fab85dbc40497ad60d4c8ed4ed699de435a8210fbed1981734d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eb7a5cf0dbda1f1c0b5df59b543735d3a83a902219d54e354f06434a85101d858a7c80407ecf4e492f4bdceaf9b26d2e8d854af3396cad6e4cf6ef503781f500
|
|
7
|
+
data.tar.gz: a910f441a3a234e91c95b338ae74427fd19c68184ad8b5a7b4019adc4f6220e9440381f646c0f2daeaacfec7cb73a3204c1aa1efc0e4dc960ce5b7633a8b46b0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Publish to RubyGems
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: ruby/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: 3.3
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
- run: bundle exec rspec
|
|
18
|
+
|
|
19
|
+
publish:
|
|
20
|
+
needs: test
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: ruby/setup-ruby@v1
|
|
25
|
+
with:
|
|
26
|
+
ruby-version: 3.3
|
|
27
|
+
- name: Build gem
|
|
28
|
+
run: gem build mysql_genius.gemspec
|
|
29
|
+
- name: Publish to RubyGems
|
|
30
|
+
run: gem push mysql_genius-*.gem
|
|
31
|
+
env:
|
|
32
|
+
GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Sortable columns** -- click any column header to sort ascending/descending on all data tables
|
|
7
|
+
- **Automated RubyGems publishing** -- GitHub Actions workflow publishes gem on tag push
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Query stats noise** -- MySQLGenius internal queries (information_schema, performance_schema, SHOW, etc.) are now excluded from the Query Stats tab
|
|
11
|
+
|
|
3
12
|
## 0.3.0
|
|
4
13
|
|
|
5
14
|
### Improved
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MySQLGenius
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An AI-powered MySQL dashboard for Rails to help you optimize your database for maximum performance, inspired by [PgHero](https://github.com/ankane/pghero).
|
|
4
4
|
|
|
5
5
|
## Screenshots
|
|
6
6
|
|
|
@@ -10,13 +10,9 @@ At-a-glance server health, top slow queries, most expensive queries, and index a
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
-
### Slow Queries
|
|
14
|
-
|
|
15
|
-
SELECT queries exceeding the configured threshold, captured via ActiveSupport notifications and Redis.
|
|
16
|
-
|
|
17
13
|
### Query Stats
|
|
18
14
|
|
|
19
|
-
Top queries from `performance_schema` sorted by total time, with
|
|
15
|
+
Top queries from `performance_schema` sorted by total time, with SQL syntax highlighting and color-coded durations.
|
|
20
16
|
|
|
21
17
|

|
|
22
18
|
|
|
@@ -26,9 +22,9 @@ Server health: version, connections, InnoDB buffer pool, and query activity with
|
|
|
26
22
|
|
|
27
23
|

|
|
28
24
|
|
|
29
|
-
###
|
|
25
|
+
### Tables
|
|
30
26
|
|
|
31
|
-
|
|
27
|
+
Row counts, data size, index size, engine, fragmentation, and optimize suggestions for every table.
|
|
32
28
|
|
|
33
29
|

|
|
34
30
|
|
|
@@ -46,232 +42,69 @@ Build queries visually or write raw SQL. Optional AI assistant generates queries
|
|
|
46
42
|
|
|
47
43
|
### AI Tools
|
|
48
44
|
|
|
49
|
-
Schema review
|
|
45
|
+
Schema review, query optimization, index advisor, anomaly detection, root cause analysis, and migration risk assessment.
|
|
50
46
|
|
|
51
47
|

|
|
52
48
|
|
|
53
49
|
## Features
|
|
54
50
|
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
51
|
+
- **Dashboard** -- server health, slow queries, expensive queries, index alerts at a glance
|
|
52
|
+
- **Query Explorer** -- visual builder + raw SQL editor with AI assistant
|
|
53
|
+
- **SQL Syntax Highlighting** -- dark-themed code blocks with color-coded keywords, functions, strings
|
|
54
|
+
- **Safe SQL Execution** -- read-only enforcement, blocked tables, masked columns, row limits, timeouts
|
|
57
55
|
- **EXPLAIN Analysis** -- run EXPLAIN on any query and view the execution plan
|
|
58
|
-
- **AI
|
|
59
|
-
- **
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
62
|
-
- **Table Size Dashboard** -- view row counts, data size, index size, and fragmentation for all tables
|
|
63
|
-
- **Audit Logging** -- logs all query executions, rejections, and errors
|
|
56
|
+
- **9 AI Tools** -- suggestions, optimization, schema review, query rewrite, index advisor, anomaly detection, root cause analysis, migration risk ([details](https://github.com/antarr/mysql_genius/wiki/AI-Features))
|
|
57
|
+
- **Slow Query Monitoring** -- captures slow queries via ActiveSupport notifications and Redis ([details](https://github.com/antarr/mysql_genius/wiki/Slow-Query-Monitoring))
|
|
58
|
+
- **Index Analysis** -- duplicate index detection, unused index detection with DROP statements
|
|
59
|
+
- **Dark Theme** -- auto-detects system preference with manual toggle ([details](https://github.com/antarr/mysql_genius/wiki/Dark-Theme))
|
|
64
60
|
- **MariaDB Support** -- automatically detects MariaDB and uses appropriate timeout syntax
|
|
65
|
-
- **Self-contained UI** -- no external CSS/JS dependencies, works with any Rails layout
|
|
66
|
-
- **Zero jQuery** -- pure vanilla JavaScript frontend
|
|
67
|
-
|
|
68
|
-
## Requirements
|
|
69
|
-
|
|
70
|
-
- Rails 5.2+
|
|
71
|
-
- Ruby 2.6+
|
|
72
|
-
- MySQL or MariaDB
|
|
73
|
-
- Redis (optional, for slow query monitoring)
|
|
61
|
+
- **Self-contained UI** -- no external CSS/JS dependencies, no jQuery, works with any Rails layout
|
|
74
62
|
|
|
75
|
-
##
|
|
76
|
-
|
|
77
|
-
Add to your Gemfile:
|
|
63
|
+
## Quick Start
|
|
78
64
|
|
|
79
65
|
```ruby
|
|
66
|
+
# Gemfile
|
|
80
67
|
gem "mysql_genius"
|
|
81
68
|
```
|
|
82
69
|
|
|
83
|
-
Or from GitHub:
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
gem "mysql_genius", github: "antarr/mysql_genius"
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Then run:
|
|
90
|
-
|
|
91
70
|
```bash
|
|
92
71
|
bundle install
|
|
72
|
+
rails generate mysql_genius:install
|
|
93
73
|
```
|
|
94
74
|
|
|
95
|
-
|
|
75
|
+
Visit `/mysql_genius` in your browser.
|
|
96
76
|
|
|
97
|
-
|
|
77
|
+
For detailed setup, see the [Installation guide](https://github.com/antarr/mysql_genius/wiki/Installation).
|
|
98
78
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```ruby
|
|
102
|
-
Rails.application.routes.draw do
|
|
103
|
-
mount MysqlGenius::Engine, at: "/mysql_genius"
|
|
104
|
-
end
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
To restrict access at the route level:
|
|
108
|
-
|
|
109
|
-
```ruby
|
|
110
|
-
# Using a session constraint
|
|
111
|
-
constraints ->(req) { req.session[:admin] } do
|
|
112
|
-
mount MysqlGenius::Engine, at: "/mysql_genius"
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Or using Devise
|
|
116
|
-
authenticate :user, ->(u) { u.admin? } do
|
|
117
|
-
mount MysqlGenius::Engine, at: "/mysql_genius"
|
|
118
|
-
end
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### 2. Configure
|
|
122
|
-
|
|
123
|
-
Create `config/initializers/mysql_genius.rb`:
|
|
79
|
+
## Configuration
|
|
124
80
|
|
|
125
81
|
```ruby
|
|
126
82
|
MysqlGenius.configure do |config|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# Default: allows everyone. Use route constraints for most cases.
|
|
130
|
-
config.authenticate = ->(controller) { true }
|
|
131
|
-
|
|
132
|
-
# To use current_user or other app helpers, inherit from ApplicationController:
|
|
133
|
-
# config.base_controller = "ApplicationController"
|
|
134
|
-
# config.authenticate = ->(controller) { controller.current_user&.admin? }
|
|
135
|
-
|
|
136
|
-
# --- Tables ---
|
|
137
|
-
# Tables featured at the top of the visual builder dropdown (optional)
|
|
138
|
-
config.featured_tables = %w[users posts comments]
|
|
139
|
-
|
|
140
|
-
# Tables blocked from querying (defaults: sessions, schema_migrations, ar_internal_metadata)
|
|
83
|
+
config.base_controller = "ApplicationController"
|
|
84
|
+
config.authenticate = ->(controller) { controller.current_user&.admin? }
|
|
141
85
|
config.blocked_tables += %w[oauth_tokens api_keys]
|
|
142
|
-
|
|
143
|
-
# Column patterns to redact with [REDACTED] in results (case-insensitive substring match)
|
|
144
|
-
config.masked_column_patterns = %w[password secret digest token ssn]
|
|
145
|
-
|
|
146
|
-
# Default columns checked in the visual builder per table (optional).
|
|
147
|
-
# When empty for a table, all columns are checked by default.
|
|
148
|
-
config.default_columns = {
|
|
149
|
-
"users" => %w[id name email created_at],
|
|
150
|
-
"posts" => %w[id title user_id published_at]
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
# --- Query Safety ---
|
|
154
|
-
config.max_row_limit = 1000 # Hard cap on rows returned
|
|
155
|
-
config.default_row_limit = 25 # Default when no limit specified
|
|
156
|
-
config.query_timeout_ms = 30_000 # 30 second timeout (uses MariaDB or MySQL hints)
|
|
157
|
-
|
|
158
|
-
# --- Slow Query Monitoring ---
|
|
159
|
-
# Requires Redis. Set to nil to disable.
|
|
160
|
-
config.redis_url = ENV["REDIS_URL"].presence || "redis://127.0.0.1:6379/0"
|
|
161
|
-
config.slow_query_threshold_ms = 250
|
|
162
|
-
|
|
163
|
-
# --- Audit Logging ---
|
|
164
|
-
# Set to nil to disable. Logs query executions, rejections, and errors.
|
|
165
|
-
config.audit_logger = Logger.new(Rails.root.join("log", "mysql_genius.log"))
|
|
166
86
|
end
|
|
167
87
|
```
|
|
168
88
|
|
|
169
|
-
|
|
89
|
+
For full configuration options, see the [Configuration guide](https://github.com/antarr/mysql_genius/wiki/Configuration).
|
|
170
90
|
|
|
171
|
-
|
|
91
|
+
## AI Features (optional)
|
|
92
|
+
|
|
93
|
+
Works with OpenAI, Azure OpenAI, Ollama Cloud, local Ollama, or any OpenAI-compatible API.
|
|
172
94
|
|
|
173
95
|
```ruby
|
|
174
96
|
MysqlGenius.configure do |config|
|
|
175
|
-
# --- Option A: OpenAI ---
|
|
176
97
|
config.ai_endpoint = "https://api.openai.com/v1/chat/completions"
|
|
177
98
|
config.ai_api_key = ENV["OPENAI_API_KEY"]
|
|
178
99
|
config.ai_model = "gpt-4o"
|
|
179
100
|
config.ai_auth_style = :bearer
|
|
180
|
-
|
|
181
|
-
# --- Option B: Azure OpenAI ---
|
|
182
|
-
config.ai_endpoint = ENV["AZURE_OPENAI_ENDPOINT"] # Your deployment URL
|
|
183
|
-
config.ai_api_key = ENV["AZURE_OPENAI_API_KEY"]
|
|
184
|
-
config.ai_auth_style = :api_key # Default, uses api-key header
|
|
185
|
-
|
|
186
|
-
# --- Option C: Ollama Cloud ---
|
|
187
|
-
config.ai_endpoint = "https://api.ollama.com/v1/chat/completions"
|
|
188
|
-
config.ai_api_key = ENV["OLLAMA_API_KEY"]
|
|
189
|
-
config.ai_model = "gemma3:27b"
|
|
190
|
-
config.ai_auth_style = :bearer
|
|
191
|
-
|
|
192
|
-
# --- Option D: Local Ollama ---
|
|
193
|
-
config.ai_endpoint = "http://localhost:11434/v1/chat/completions"
|
|
194
|
-
config.ai_api_key = "ollama" # Any non-empty string
|
|
195
|
-
config.ai_model = "llama3"
|
|
196
|
-
config.ai_auth_style = :bearer
|
|
197
|
-
|
|
198
|
-
# --- Option E: Custom client ---
|
|
199
|
-
# Any callable that accepts messages: and temperature: kwargs
|
|
200
|
-
# and returns an OpenAI-compatible response hash.
|
|
201
|
-
config.ai_client = ->(messages:, temperature:) {
|
|
202
|
-
MyAiService.chat(messages, temperature: temperature)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
# --- Domain Context ---
|
|
206
|
-
# Helps the AI understand your schema and generate better queries.
|
|
207
|
-
config.ai_system_context = <<~CONTEXT
|
|
208
|
-
This is an e-commerce database.
|
|
209
|
-
- `users` stores customer accounts. Primary key is `id`.
|
|
210
|
-
- `orders` tracks purchases. Linked to users via `user_id`.
|
|
211
|
-
- `products` contains the product catalog.
|
|
212
|
-
- Soft-deleted records have `deleted_at IS NOT NULL`.
|
|
213
|
-
CONTEXT
|
|
214
101
|
end
|
|
215
102
|
```
|
|
216
103
|
|
|
217
|
-
|
|
218
|
-
|--------|-----------------|------------|-------|
|
|
219
|
-
| OpenAI | `:bearer` | Required (e.g. `gpt-4o`) | |
|
|
220
|
-
| Azure OpenAI | `:api_key` (default) | Optional (uses deployment default) | |
|
|
221
|
-
| Ollama Cloud | `:bearer` | Required (e.g. `gemma3:27b`) | Follows redirects automatically |
|
|
222
|
-
| Local Ollama | `:bearer` | Required | No API key validation |
|
|
223
|
-
| Custom client | N/A | N/A | You handle everything |
|
|
224
|
-
|
|
225
|
-
When AI is not configured, the AI Assistant panel and optimization buttons are hidden automatically.
|
|
226
|
-
|
|
227
|
-
## Usage
|
|
228
|
-
|
|
229
|
-
Visit `/mysql_genius` in your browser. The dashboard loads automatically with an overview of your database health.
|
|
230
|
-
|
|
231
|
-
### Dashboard
|
|
232
|
-
|
|
233
|
-
The default landing page shows server health cards, top 5 slow queries, top 5 most expensive queries (from performance_schema), and index alert badges for duplicate and unused indexes. Each section links to its detailed tab.
|
|
234
|
-
|
|
235
|
-
### Query Explorer
|
|
236
|
-
|
|
237
|
-
Combines the visual query builder and raw SQL editor in one tab. Toggle between Visual mode (point-and-click with column selection, filters, and ordering) and SQL mode (raw SQL with optional AI assistant). Generated SQL syncs between modes.
|
|
238
|
-
|
|
239
|
-
### Monitoring Tabs
|
|
240
|
-
|
|
241
|
-
- **Slow Queries** -- slow SELECT queries captured from your application in real time, with Explain and Optimize actions
|
|
242
|
-
- **Query Stats** -- top queries from `performance_schema` sorted by total time, avg time, calls, or rows examined
|
|
243
|
-
- **Server** -- connections, InnoDB buffer pool, query activity, with AI-powered diagnostics
|
|
244
|
-
- **Table Sizes** -- row counts, data size, index size, fragmentation for all tables
|
|
245
|
-
- **Unused Indexes** -- indexes with zero reads since server restart
|
|
246
|
-
- **Duplicate Indexes** -- redundant indexes with ready-to-run DROP statements
|
|
247
|
-
|
|
248
|
-
## Configuration Reference
|
|
249
|
-
|
|
250
|
-
| Option | Type | Default | Description |
|
|
251
|
-
|--------|------|---------|-------------|
|
|
252
|
-
| `authenticate` | Proc | `->(_) { true }` | Authorization check |
|
|
253
|
-
| `base_controller` | String | `"ActionController::Base"` | Parent controller class |
|
|
254
|
-
| `featured_tables` | Array | `[]` | Tables shown in Featured group |
|
|
255
|
-
| `blocked_tables` | Array | `[sessions, ...]` | Tables that cannot be queried |
|
|
256
|
-
| `masked_column_patterns` | Array | `[password, secret, ...]` | Column patterns to redact |
|
|
257
|
-
| `default_columns` | Hash | `{}` | Default checked columns per table |
|
|
258
|
-
| `max_row_limit` | Integer | `1000` | Maximum rows returned |
|
|
259
|
-
| `default_row_limit` | Integer | `25` | Default row limit |
|
|
260
|
-
| `query_timeout_ms` | Integer | `30000` | Query timeout in ms |
|
|
261
|
-
| `redis_url` | String | `nil` | Redis URL for slow query monitoring |
|
|
262
|
-
| `slow_query_threshold_ms` | Integer | `250` | Slow query threshold |
|
|
263
|
-
| `audit_logger` | Logger | `nil` | Logger for query audit trail |
|
|
264
|
-
| `ai_endpoint` | String | `nil` | AI API endpoint URL |
|
|
265
|
-
| `ai_api_key` | String | `nil` | AI API key |
|
|
266
|
-
| `ai_model` | String | `nil` | AI model name |
|
|
267
|
-
| `ai_auth_style` | Symbol | `:api_key` | `:bearer` or `:api_key` |
|
|
268
|
-
| `ai_client` | Proc | `nil` | Custom AI client callable |
|
|
269
|
-
| `ai_system_context` | String | `nil` | Domain context for AI prompts |
|
|
104
|
+
For all provider examples, see the [AI Features guide](https://github.com/antarr/mysql_genius/wiki/AI-Features).
|
|
270
105
|
|
|
271
106
|
## Compatibility
|
|
272
107
|
|
|
273
|
-
Tested against:
|
|
274
|
-
|
|
275
108
|
| Rails | Ruby |
|
|
276
109
|
|-------|------|
|
|
277
110
|
| 5.2 | 2.7, 3.0 |
|
|
@@ -283,6 +116,10 @@ Tested against:
|
|
|
283
116
|
| 8.0 | 3.2, 3.3, 3.4 |
|
|
284
117
|
| 8.1 | 3.2, 3.3, 3.4 |
|
|
285
118
|
|
|
119
|
+
## Documentation
|
|
120
|
+
|
|
121
|
+
Full documentation is available on the [Wiki](https://github.com/antarr/mysql_genius/wiki).
|
|
122
|
+
|
|
286
123
|
## Development
|
|
287
124
|
|
|
288
125
|
```bash
|
|
@@ -292,12 +129,6 @@ bin/setup
|
|
|
292
129
|
bundle exec rspec
|
|
293
130
|
```
|
|
294
131
|
|
|
295
|
-
To test against a specific Rails version:
|
|
296
|
-
|
|
297
|
-
```bash
|
|
298
|
-
RAILS_VERSION=6.1 bundle update && bundle exec rspec
|
|
299
|
-
```
|
|
300
|
-
|
|
301
132
|
## Contributing
|
|
302
133
|
|
|
303
134
|
Bug reports and pull requests are welcome on GitHub at https://github.com/antarr/mysql_genius.
|
|
@@ -128,6 +128,14 @@ module MysqlGenius
|
|
|
128
128
|
WHERE SCHEMA_NAME = #{connection.quote(connection.current_database)}
|
|
129
129
|
AND DIGEST_TEXT IS NOT NULL
|
|
130
130
|
AND DIGEST_TEXT NOT LIKE 'EXPLAIN%'
|
|
131
|
+
AND DIGEST_TEXT NOT LIKE '%`information_schema`%'
|
|
132
|
+
AND DIGEST_TEXT NOT LIKE '%`performance_schema`%'
|
|
133
|
+
AND DIGEST_TEXT NOT LIKE '%information_schema.%'
|
|
134
|
+
AND DIGEST_TEXT NOT LIKE '%performance_schema.%'
|
|
135
|
+
AND DIGEST_TEXT NOT LIKE 'SHOW %'
|
|
136
|
+
AND DIGEST_TEXT NOT LIKE 'SET STATEMENT %'
|
|
137
|
+
AND DIGEST_TEXT NOT LIKE 'SELECT VERSION ( )%'
|
|
138
|
+
AND DIGEST_TEXT NOT LIKE 'SELECT @@%'
|
|
131
139
|
ORDER BY #{order_clause}
|
|
132
140
|
LIMIT #{limit}
|
|
133
141
|
SQL
|
|
@@ -79,7 +79,12 @@
|
|
|
79
79
|
/* Table */
|
|
80
80
|
.mg-table-wrap { overflow-x: auto; }
|
|
81
81
|
table.mg-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
|
|
82
|
-
.mg-table th { padding: 10px 12px; text-align: left; white-space: nowrap; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #5a6770; background: #f1f3f5; border-bottom: 2px solid #d0d7de; }
|
|
82
|
+
.mg-table th { padding: 10px 12px; text-align: left; white-space: nowrap; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: #5a6770; background: #f1f3f5; border-bottom: 2px solid #d0d7de; user-select: none; }
|
|
83
|
+
.mg-table th.mg-sortable { cursor: pointer; position: relative; padding-right: 20px; }
|
|
84
|
+
.mg-table th.mg-sortable:hover { color: #333; }
|
|
85
|
+
.mg-table th.mg-sortable::after { content: '\2195'; position: absolute; right: 6px; opacity: 0.3; font-size: 10px; }
|
|
86
|
+
.mg-table th.mg-sort-asc::after { content: '\2191'; opacity: 0.8; }
|
|
87
|
+
.mg-table th.mg-sort-desc::after { content: '\2193'; opacity: 0.8; }
|
|
83
88
|
.mg-table th:first-child { border-top-left-radius: 6px; }
|
|
84
89
|
.mg-table th:last-child { border-top-right-radius: 6px; }
|
|
85
90
|
.mg-table td { padding: 8px 12px; border-bottom: 1px solid #eaecef; vertical-align: top; }
|
|
@@ -200,6 +205,7 @@
|
|
|
200
205
|
|
|
201
206
|
/* Tables */
|
|
202
207
|
[data-theme="dark"] .mg-table th { background: #161b22; color: #8b949e; border-bottom-color: #30363d; }
|
|
208
|
+
[data-theme="dark"] .mg-table th.mg-sortable:hover { color: #c9d1d9; }
|
|
203
209
|
[data-theme="dark"] .mg-table td { border-bottom-color: #21262d; }
|
|
204
210
|
[data-theme="dark"] .mg-table tbody tr:hover { background: #1c2128; }
|
|
205
211
|
[data-theme="dark"] .mg-table tbody tr:nth-child(even) { background: #0d1117; }
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="mg-tab-content" id="tab-qstats">
|
|
3
3
|
<div class="mg-row" style="justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
4
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
|
-
|
|
5
|
+
<!--div>
|
|
6
6
|
<label style="display:inline;font-size:12px;margin-right:4px;">Sort by:</label>
|
|
7
7
|
<select id="qstats-sort" style="width:auto;display:inline-block;padding:3px 6px;font-size:12px;">
|
|
8
8
|
<option value="total_time">Total Time</option>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<option value="rows_examined">Rows Examined</option>
|
|
12
12
|
</select>
|
|
13
13
|
<button id="qstats-refresh" class="mg-btn mg-btn-outline-secondary mg-btn-sm" style="margin-left:4px;">↻ Refresh</button>
|
|
14
|
-
</div
|
|
14
|
+
</div-->
|
|
15
15
|
</div>
|
|
16
16
|
<div id="qstats-loading" class="mg-text-center mg-hidden"><span class="mg-spinner"></span> Loading...</div>
|
|
17
17
|
<div id="qstats-error" class="mg-hidden"></div>
|
|
@@ -76,6 +76,60 @@
|
|
|
76
76
|
function hide(e) { e.classList.add('mg-hidden'); }
|
|
77
77
|
function escHtml(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
78
78
|
|
|
79
|
+
// --- Table Sorting ---
|
|
80
|
+
|
|
81
|
+
function parseSortValue(text) {
|
|
82
|
+
var m = text.match(/([\d.]+)\s*(s|ms|KB|MB|GB)/i);
|
|
83
|
+
if (m) {
|
|
84
|
+
var n = parseFloat(m[1]);
|
|
85
|
+
var unit = m[2].toLowerCase();
|
|
86
|
+
if (unit === 's') return n * 1000;
|
|
87
|
+
if (unit === 'gb') return n * 1024;
|
|
88
|
+
if (unit === 'mb') return n;
|
|
89
|
+
if (unit === 'kb') return n / 1024;
|
|
90
|
+
return n;
|
|
91
|
+
}
|
|
92
|
+
return parseFloat(text.replace(/[^0-9.\-]/g, '')) || 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function makeSortable(table) {
|
|
96
|
+
if (table.dataset.sortable) return;
|
|
97
|
+
table.dataset.sortable = '1';
|
|
98
|
+
|
|
99
|
+
var headers = Array.from(table.querySelectorAll('th'));
|
|
100
|
+
headers.forEach(function(th, colIdx) {
|
|
101
|
+
if (th.dataset.noSort || th.textContent.trim() === '' || th.textContent.trim() === 'Actions') return;
|
|
102
|
+
th.classList.add('mg-sortable');
|
|
103
|
+
th.addEventListener('click', function() {
|
|
104
|
+
var tbody = table.querySelector('tbody');
|
|
105
|
+
if (!tbody) return;
|
|
106
|
+
var rows = Array.from(tbody.querySelectorAll('tr'));
|
|
107
|
+
if (rows.length === 0) return;
|
|
108
|
+
|
|
109
|
+
var asc = !th.classList.contains('mg-sort-asc');
|
|
110
|
+
headers.forEach(function(h) { h.classList.remove('mg-sort-asc', 'mg-sort-desc'); });
|
|
111
|
+
th.classList.add(asc ? 'mg-sort-asc' : 'mg-sort-desc');
|
|
112
|
+
|
|
113
|
+
var isNum = th.style.textAlign === 'right' || th.classList.contains('mg-num');
|
|
114
|
+
|
|
115
|
+
rows.sort(function(a, b) {
|
|
116
|
+
var cellA = a.children[colIdx];
|
|
117
|
+
var cellB = b.children[colIdx];
|
|
118
|
+
if (!cellA || !cellB) return 0;
|
|
119
|
+
var valA = cellA.textContent.trim();
|
|
120
|
+
var valB = cellB.textContent.trim();
|
|
121
|
+
|
|
122
|
+
if (isNum) {
|
|
123
|
+
return asc ? parseSortValue(valA) - parseSortValue(valB) : parseSortValue(valB) - parseSortValue(valA);
|
|
124
|
+
}
|
|
125
|
+
return asc ? valA.localeCompare(valB) : valB.localeCompare(valA);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
rows.forEach(function(row) { tbody.appendChild(row); });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
79
133
|
// --- SQL Syntax Highlighting (single-pass tokenizer) ---
|
|
80
134
|
|
|
81
135
|
var SQL_KW_SET = {};
|
|
@@ -793,6 +847,7 @@
|
|
|
793
847
|
'<button class="mg-btn mg-btn-outline-secondary mg-btn-sm slow-use-btn" data-sql="' + escHtml(q.sql).replace(/"/g, '"') + '">Use</button></td></tr>';
|
|
794
848
|
}).join('');
|
|
795
849
|
show(el('slow-table-wrapper'));
|
|
850
|
+
makeSortable(qs('#slow-table-wrapper .mg-table'));
|
|
796
851
|
}, function() {
|
|
797
852
|
hide(el('slow-loading'));
|
|
798
853
|
el('slow-empty').textContent = 'Failed to load slow queries.';
|
|
@@ -841,6 +896,7 @@
|
|
|
841
896
|
'</tr>';
|
|
842
897
|
}).join('');
|
|
843
898
|
show(el('dup-table-wrapper'));
|
|
899
|
+
makeSortable(qs('#dup-table-wrapper .mg-table'));
|
|
844
900
|
|
|
845
901
|
// Generate migration
|
|
846
902
|
var ts = migrationTimestamp();
|
|
@@ -965,6 +1021,7 @@
|
|
|
965
1021
|
'</tr>';
|
|
966
1022
|
}).join('');
|
|
967
1023
|
show(el('sizes-table-wrapper'));
|
|
1024
|
+
makeSortable(qs('#sizes-table-wrapper .mg-table'));
|
|
968
1025
|
}, function() {
|
|
969
1026
|
hide(el('sizes-loading'));
|
|
970
1027
|
el('sizes-total').textContent = 'Failed to load';
|
|
@@ -1003,6 +1060,7 @@
|
|
|
1003
1060
|
'</tr>';
|
|
1004
1061
|
}).join('');
|
|
1005
1062
|
show(el('qstats-table-wrapper'));
|
|
1063
|
+
makeSortable(qs('#qstats-table-wrapper .mg-table'));
|
|
1006
1064
|
}, function(json) {
|
|
1007
1065
|
hide(el('qstats-loading'));
|
|
1008
1066
|
el('qstats-error').innerHTML = '<div class="mg-alert mg-alert-warning">' + escHtml((json && json.error) || 'Failed to load query stats.') + '</div>';
|
|
@@ -1045,6 +1103,7 @@
|
|
|
1045
1103
|
'</tr>';
|
|
1046
1104
|
}).join('');
|
|
1047
1105
|
show(el('unused-table-wrapper'));
|
|
1106
|
+
makeSortable(qs('#unused-table-wrapper .mg-table'));
|
|
1048
1107
|
|
|
1049
1108
|
// Generate migration
|
|
1050
1109
|
var ts = migrationTimestamp();
|
data/lib/mysql_genius/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mysql_genius
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antarr Byrd
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-04-10 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: activerecord
|
|
@@ -61,6 +62,7 @@ extra_rdoc_files: []
|
|
|
61
62
|
files:
|
|
62
63
|
- ".github/FUNDING.yml"
|
|
63
64
|
- ".github/workflows/ci.yml"
|
|
65
|
+
- ".github/workflows/publish.yml"
|
|
64
66
|
- ".gitignore"
|
|
65
67
|
- ".rspec"
|
|
66
68
|
- ".rubocop.yml"
|
|
@@ -117,6 +119,7 @@ metadata:
|
|
|
117
119
|
homepage_uri: https://github.com/antarr/mysql_genius
|
|
118
120
|
source_code_uri: https://github.com/antarr/mysql_genius
|
|
119
121
|
changelog_uri: https://github.com/antarr/mysql_genius/blob/main/CHANGELOG.md
|
|
122
|
+
post_install_message:
|
|
120
123
|
rdoc_options: []
|
|
121
124
|
require_paths:
|
|
122
125
|
- lib
|
|
@@ -131,7 +134,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
131
134
|
- !ruby/object:Gem::Version
|
|
132
135
|
version: '0'
|
|
133
136
|
requirements: []
|
|
134
|
-
rubygems_version:
|
|
137
|
+
rubygems_version: 3.5.22
|
|
138
|
+
signing_key:
|
|
135
139
|
specification_version: 4
|
|
136
140
|
summary: A MySQL performance dashboard and query explorer for Rails — like PgHero,
|
|
137
141
|
but for MySQL.
|