sql_genius 0.9.0
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 +7 -0
- data/CHANGELOG.md +195 -0
- data/LICENSE.txt +65 -0
- data/README.md +178 -0
- data/Rakefile +8 -0
- data/app/controllers/concerns/sql_genius/ai_features.rb +332 -0
- data/app/controllers/concerns/sql_genius/database_analysis.rb +67 -0
- data/app/controllers/concerns/sql_genius/query_execution.rb +87 -0
- data/app/controllers/concerns/sql_genius/shared_view_helpers.rb +76 -0
- data/app/controllers/sql_genius/base_controller.rb +29 -0
- data/app/controllers/sql_genius/queries_controller.rb +94 -0
- data/app/views/layouts/sql_genius/application.html.erb +285 -0
- data/config/routes.rb +34 -0
- data/docs/guides/ai-features.md +115 -0
- data/docs/guides/getting-started-rails.md +118 -0
- data/docs/guides/ssh-tunnel-connections.md +151 -0
- data/docs/screenshots/ai_tools.png +0 -0
- data/docs/screenshots/dashboard.png +0 -0
- data/docs/screenshots/duplicate_indexes.png +0 -0
- data/docs/screenshots/query_explore.png +0 -0
- data/docs/screenshots/query_stats.png +0 -0
- data/docs/screenshots/server.png +0 -0
- data/docs/screenshots/table_sizes.png +0 -0
- data/lib/generators/sql_genius/install/install_generator.rb +19 -0
- data/lib/generators/sql_genius/install/templates/initializer.rb +56 -0
- data/lib/sql_genius/configuration.rb +114 -0
- data/lib/sql_genius/core/ai/client.rb +155 -0
- data/lib/sql_genius/core/ai/config.rb +47 -0
- data/lib/sql_genius/core/ai/connection_advisor.rb +96 -0
- data/lib/sql_genius/core/ai/describe_query.rb +41 -0
- data/lib/sql_genius/core/ai/dialect_hints.rb +35 -0
- data/lib/sql_genius/core/ai/index_advisor.rb +43 -0
- data/lib/sql_genius/core/ai/index_planner.rb +91 -0
- data/lib/sql_genius/core/ai/innodb_interpreter.rb +78 -0
- data/lib/sql_genius/core/ai/migration_risk.rb +51 -0
- data/lib/sql_genius/core/ai/optimization.rb +81 -0
- data/lib/sql_genius/core/ai/pattern_grouper.rb +94 -0
- data/lib/sql_genius/core/ai/rewrite_query.rb +51 -0
- data/lib/sql_genius/core/ai/schema_context_builder.rb +82 -0
- data/lib/sql_genius/core/ai/schema_review.rb +46 -0
- data/lib/sql_genius/core/ai/suggestion.rb +74 -0
- data/lib/sql_genius/core/ai/variable_reviewer.rb +113 -0
- data/lib/sql_genius/core/ai/workload_digest.rb +86 -0
- data/lib/sql_genius/core/analysis/columns.rb +63 -0
- data/lib/sql_genius/core/analysis/duplicate_indexes.rb +85 -0
- data/lib/sql_genius/core/analysis/query_history.rb +50 -0
- data/lib/sql_genius/core/analysis/query_stats.rb +76 -0
- data/lib/sql_genius/core/analysis/server_overview.rb +294 -0
- data/lib/sql_genius/core/analysis/stats_collector.rb +118 -0
- data/lib/sql_genius/core/analysis/stats_history.rb +42 -0
- data/lib/sql_genius/core/analysis/table_sizes.rb +52 -0
- data/lib/sql_genius/core/analysis/unused_indexes.rb +62 -0
- data/lib/sql_genius/core/column_definition.rb +30 -0
- data/lib/sql_genius/core/connection/active_record_adapter.rb +75 -0
- data/lib/sql_genius/core/connection/fake_adapter.rb +114 -0
- data/lib/sql_genius/core/connection.rb +37 -0
- data/lib/sql_genius/core/execution_result.rb +27 -0
- data/lib/sql_genius/core/index_definition.rb +23 -0
- data/lib/sql_genius/core/query_builders/mysql.rb +169 -0
- data/lib/sql_genius/core/query_builders/postgresql.rb +185 -0
- data/lib/sql_genius/core/query_builders.rb +27 -0
- data/lib/sql_genius/core/query_explainer.rb +113 -0
- data/lib/sql_genius/core/query_runner/config.rb +21 -0
- data/lib/sql_genius/core/query_runner.rb +123 -0
- data/lib/sql_genius/core/result.rb +43 -0
- data/lib/sql_genius/core/server_info.rb +54 -0
- data/lib/sql_genius/core/sql_validator.rb +149 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_shared_results.html.erb +59 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_ai_tools.html.erb +43 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_dashboard.html.erb +97 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_duplicate_indexes.html.erb +35 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_query_explorer.html.erb +110 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_query_stats.html.erb +43 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_server.html.erb +59 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_slow_queries.html.erb +17 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_table_sizes.html.erb +33 -0
- data/lib/sql_genius/core/views/sql_genius/queries/_tab_unused_indexes.html.erb +54 -0
- data/lib/sql_genius/core/views/sql_genius/queries/dashboard.html.erb +1826 -0
- data/lib/sql_genius/core/views/sql_genius/queries/query_detail.html.erb +465 -0
- data/lib/sql_genius/core.rb +72 -0
- data/lib/sql_genius/engine.rb +31 -0
- data/lib/sql_genius/slow_query_monitor.rb +43 -0
- data/lib/sql_genius/version.rb +5 -0
- data/lib/sql_genius.rb +29 -0
- data/sql_genius.gemspec +47 -0
- metadata +171 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>SQLGenius</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="csrf-token" content="<%= form_authenticity_token %>">
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
9
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #f5f5f5; color: #333; margin: 0; font-size: 14px; line-height: 1.5; }
|
|
10
|
+
.mg-container { max-width: 1400px; margin: 0 auto; padding: 20px; }
|
|
11
|
+
h4 { margin: 0 0 16px; font-size: 18px; }
|
|
12
|
+
|
|
13
|
+
/* Tabs */
|
|
14
|
+
.mg-tabs { display: flex; border-bottom: 2px solid #dee2e6; margin-bottom: 0; }
|
|
15
|
+
.mg-tab { padding: 8px 16px; cursor: pointer; border: 1px solid transparent; border-bottom: none; margin-bottom: -2px; border-radius: 4px 4px 0 0; background: none; font-size: 14px; color: #555; }
|
|
16
|
+
.mg-tab:hover { color: #000; }
|
|
17
|
+
.mg-tab.active { background: #fff; border-color: #dee2e6; border-bottom-color: #fff; color: #000; font-weight: 500; }
|
|
18
|
+
.mg-tab-content { display: none; background: #fff; border: 1px solid #dee2e6; border-top: none; padding: 16px; }
|
|
19
|
+
.mg-tab-content.active { display: block; }
|
|
20
|
+
|
|
21
|
+
/* Forms */
|
|
22
|
+
label { display: block; font-weight: 500; margin-bottom: 4px; font-size: 13px; }
|
|
23
|
+
select, input[type="number"], input[type="text"], textarea { border: 1px solid #ced4da; border-radius: 4px; padding: 6px 10px; font-size: 13px; width: 100%; font-family: inherit; }
|
|
24
|
+
select:focus, input:focus, textarea:focus { outline: none; border-color: #80bdff; box-shadow: 0 0 0 2px rgba(0,123,255,.15); }
|
|
25
|
+
textarea { font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; resize: vertical; }
|
|
26
|
+
textarea[readonly] { background: #f8f9fa; }
|
|
27
|
+
|
|
28
|
+
/* Grid helpers */
|
|
29
|
+
.mg-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
|
|
30
|
+
.mg-col-1 { flex: 0 0 80px; }
|
|
31
|
+
.mg-col-2 { flex: 0 0 160px; }
|
|
32
|
+
.mg-col-3 { flex: 0 0 240px; }
|
|
33
|
+
.mg-col-4 { flex: 0 0 320px; }
|
|
34
|
+
.mg-col-grow { flex: 1 1 200px; }
|
|
35
|
+
.mg-field { margin-bottom: 12px; }
|
|
36
|
+
|
|
37
|
+
/* Buttons */
|
|
38
|
+
.mg-btn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; border: 1px solid transparent; border-radius: 4px; font-size: 13px; cursor: pointer; font-family: inherit; white-space: nowrap; }
|
|
39
|
+
.mg-btn:disabled { opacity: .5; cursor: not-allowed; }
|
|
40
|
+
.mg-btn-primary { background: #007bff; color: #fff; border-color: #007bff; }
|
|
41
|
+
.mg-btn-primary:hover:not(:disabled) { background: #0069d9; }
|
|
42
|
+
.mg-btn-outline { background: #fff; color: #007bff; border-color: #007bff; }
|
|
43
|
+
.mg-btn-outline:hover:not(:disabled) { background: #e7f1ff; }
|
|
44
|
+
.mg-btn-outline-secondary { background: #fff; color: #6c757d; border-color: #6c757d; }
|
|
45
|
+
.mg-btn-outline-secondary:hover:not(:disabled) { background: #f8f9fa; }
|
|
46
|
+
.mg-btn-outline-danger { background: #fff; color: #dc3545; border-color: #dc3545; padding: 4px 8px; }
|
|
47
|
+
.mg-btn-outline-danger:hover:not(:disabled) { background: #ffeef0; }
|
|
48
|
+
.mg-btn-sm { padding: 3px 8px; font-size: 12px; }
|
|
49
|
+
|
|
50
|
+
/* Badge */
|
|
51
|
+
.mg-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 500; }
|
|
52
|
+
.mg-badge-info { background: #d1ecf1; color: #0c5460; }
|
|
53
|
+
.mg-badge-warning { background: #fff3cd; color: #856404; }
|
|
54
|
+
.mg-badge-danger { background: #f8d7da; color: #721c24; }
|
|
55
|
+
.mg-badge-secondary { background: #e2e3e5; color: #383d41; }
|
|
56
|
+
.mg-badge-success { background: #d4edda; color: #155724; }
|
|
57
|
+
|
|
58
|
+
/* Alert */
|
|
59
|
+
.mg-alert { padding: 10px 14px; border-radius: 4px; margin-bottom: 12px; font-size: 13px; }
|
|
60
|
+
.mg-alert-danger { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
|
61
|
+
.mg-alert-warning { background: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
|
|
62
|
+
.mg-alert-info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
|
|
63
|
+
|
|
64
|
+
/* Card */
|
|
65
|
+
.mg-card { border: 1px solid #dee2e6; border-radius: 4px; margin-bottom: 12px; }
|
|
66
|
+
.mg-card-header { padding: 8px 12px; background: #f8f9fa; border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; font-size: 13px; }
|
|
67
|
+
.mg-card-body { padding: 12px; }
|
|
68
|
+
.mg-card-toggle { cursor: pointer; color: #007bff; text-decoration: none; font-size: 13px; }
|
|
69
|
+
.mg-card-toggle:hover { text-decoration: underline; }
|
|
70
|
+
|
|
71
|
+
/* Stat grid */
|
|
72
|
+
.mg-stat-grid { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; font-size: 13px; }
|
|
73
|
+
.mg-stat-grid .mg-stat-label { color: #666; }
|
|
74
|
+
.mg-stat-grid .mg-stat-value { text-align: right; font-weight: 500; }
|
|
75
|
+
.mg-usage-bar { background: #e9ecef; border-radius: 4px; height: 20px; position: relative; overflow: hidden; }
|
|
76
|
+
.mg-usage-bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
|
|
77
|
+
.mg-usage-bar-text { position: absolute; top: 0; left: 0; right: 0; text-align: center; line-height: 20px; font-size: 11px; font-weight: 500; }
|
|
78
|
+
|
|
79
|
+
/* Table */
|
|
80
|
+
.mg-table-wrap { overflow-x: auto; }
|
|
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; 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; }
|
|
88
|
+
.mg-table th:first-child { border-top-left-radius: 6px; }
|
|
89
|
+
.mg-table th:last-child { border-top-right-radius: 6px; }
|
|
90
|
+
.mg-table td { padding: 8px 12px; border-bottom: 1px solid #eaecef; vertical-align: top; }
|
|
91
|
+
.mg-table tbody tr { transition: background-color 0.15s ease; }
|
|
92
|
+
.mg-table tbody tr:hover { background: #f0f6ff; }
|
|
93
|
+
.mg-table tbody tr:nth-child(even) { background: #fafbfc; }
|
|
94
|
+
.mg-table tbody tr:nth-child(even):hover { background: #f0f6ff; }
|
|
95
|
+
.mg-table tbody tr:last-child td { border-bottom: none; }
|
|
96
|
+
.mg-table em.null { color: #999; font-style: italic; }
|
|
97
|
+
.mg-table .redacted { color: #999; }
|
|
98
|
+
|
|
99
|
+
/* Numeric table cells */
|
|
100
|
+
.mg-table td.mg-num { text-align: right; font-variant-numeric: tabular-nums; font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; font-size: 12px; white-space: nowrap; }
|
|
101
|
+
|
|
102
|
+
/* SQL code blocks in tables */
|
|
103
|
+
.mg-sql-block { display: block; padding: 6px 8px; background: #1e1e2e; color: #cdd6f4; border-radius: 5px; font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; font-size: 11.5px; line-height: 1.5; word-break: break-word; white-space: pre-wrap; max-height: 120px; overflow-y: auto; border: 1px solid #313244; }
|
|
104
|
+
.mg-sql-block::-webkit-scrollbar { width: 4px; }
|
|
105
|
+
.mg-sql-block::-webkit-scrollbar-track { background: transparent; }
|
|
106
|
+
.mg-sql-block::-webkit-scrollbar-thumb { background: #585b70; border-radius: 2px; }
|
|
107
|
+
|
|
108
|
+
/* SQL syntax colors (Catppuccin Mocha inspired) */
|
|
109
|
+
.mg-sql-kw { color: #cba6f7; font-weight: 600; }
|
|
110
|
+
.mg-sql-fn { color: #89b4fa; }
|
|
111
|
+
.mg-sql-str { color: #a6e3a1; }
|
|
112
|
+
.mg-sql-num { color: #fab387; }
|
|
113
|
+
.mg-sql-op { color: #89dceb; }
|
|
114
|
+
.mg-sql-comment { color: #6c7086; font-style: italic; }
|
|
115
|
+
.mg-sql-tbl { color: #f9e2af; }
|
|
116
|
+
.mg-sql-star { color: #f38ba8; font-weight: 700; }
|
|
117
|
+
.mg-sql-punc { color: #9399b2; }
|
|
118
|
+
.mg-sql-placeholder { color: #74c7ec; font-style: italic; }
|
|
119
|
+
|
|
120
|
+
/* Duration color coding */
|
|
121
|
+
.mg-dur-fast { color: #1a7f37; }
|
|
122
|
+
.mg-dur-moderate { color: #9a6700; }
|
|
123
|
+
.mg-dur-slow { color: #cf222e; font-weight: 600; }
|
|
124
|
+
|
|
125
|
+
/* Checkbox grid */
|
|
126
|
+
.mg-checks { display: flex; flex-wrap: wrap; gap: 4px 16px; }
|
|
127
|
+
.mg-check { display: flex; align-items: center; gap: 4px; font-size: 13px; cursor: pointer; }
|
|
128
|
+
.mg-check input { margin: 0; }
|
|
129
|
+
.mg-check .type-hint { color: #999; font-size: 11px; }
|
|
130
|
+
|
|
131
|
+
/* Inline links */
|
|
132
|
+
.mg-link { color: #007bff; cursor: pointer; text-decoration: none; font-size: 12px; margin-left: 8px; }
|
|
133
|
+
.mg-link:hover { text-decoration: underline; }
|
|
134
|
+
|
|
135
|
+
/* Spinner */
|
|
136
|
+
@keyframes mg-spin { to { transform: rotate(360deg); } }
|
|
137
|
+
.mg-spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid #ccc; border-top-color: #007bff; border-radius: 50%; animation: mg-spin .6s linear infinite; }
|
|
138
|
+
|
|
139
|
+
/* Utilities */
|
|
140
|
+
.mg-hidden { display: none !important; }
|
|
141
|
+
.mg-mt { margin-top: 16px; }
|
|
142
|
+
.mg-mb { margin-bottom: 12px; }
|
|
143
|
+
.mg-text-muted { color: #888; font-size: 13px; }
|
|
144
|
+
.mg-text-center { text-align: center; padding: 24px 0; }
|
|
145
|
+
code { font-size: 12px; word-break: break-all; background: #f0f1f3; padding: 2px 6px; border-radius: 3px; color: #24292f; }
|
|
146
|
+
pre.mg-pre { background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
|
|
147
|
+
|
|
148
|
+
/* AI result sections */
|
|
149
|
+
.mg-ai-section { border-radius: 4px; border: 1px solid #dee2e6; border-left-width: 4px; margin-bottom: 12px; }
|
|
150
|
+
.mg-ai-section-header { padding: 8px 12px; border-bottom: 1px solid #dee2e6; font-size: 13px; }
|
|
151
|
+
.mg-ai-section-body { padding: 12px; font-size: 13px; }
|
|
152
|
+
.mg-ai-danger { border-left-color: #dc3545; background: #fff5f5; }
|
|
153
|
+
.mg-ai-danger .mg-ai-section-header { border-bottom-color: #f5c6cb; }
|
|
154
|
+
.mg-ai-warning { border-left-color: #ffc107; background: #fffbeb; }
|
|
155
|
+
.mg-ai-warning .mg-ai-section-header { border-bottom-color: #ffeeba; }
|
|
156
|
+
.mg-ai-info { border-left-color: #17a2b8; background: #f0f9ff; }
|
|
157
|
+
.mg-ai-info .mg-ai-section-header { border-bottom-color: #bee5eb; }
|
|
158
|
+
|
|
159
|
+
/* Theme toggle */
|
|
160
|
+
.mg-theme-toggle { background: none; border: 1px solid #ced4da; border-radius: 4px; padding: 4px 8px; cursor: pointer; font-size: 14px; line-height: 1; color: inherit; }
|
|
161
|
+
.mg-theme-toggle:hover { background: #e9ecef; }
|
|
162
|
+
|
|
163
|
+
/* --- Dark Theme --- */
|
|
164
|
+
[data-theme="dark"] body,
|
|
165
|
+
[data-theme="dark"] { background: #161b22; color: #c9d1d9; }
|
|
166
|
+
[data-theme="dark"] .mg-container { color: #c9d1d9; }
|
|
167
|
+
|
|
168
|
+
/* Tabs */
|
|
169
|
+
[data-theme="dark"] .mg-tabs { border-bottom-color: #30363d; }
|
|
170
|
+
[data-theme="dark"] .mg-tab { color: #8b949e; }
|
|
171
|
+
[data-theme="dark"] .mg-tab:hover { color: #c9d1d9; }
|
|
172
|
+
[data-theme="dark"] .mg-tab.active { background: #0d1117; border-color: #30363d; border-bottom-color: #0d1117; color: #c9d1d9; }
|
|
173
|
+
[data-theme="dark"] .mg-tab-content { background: #0d1117; border-color: #30363d; }
|
|
174
|
+
|
|
175
|
+
/* Forms */
|
|
176
|
+
[data-theme="dark"] select,
|
|
177
|
+
[data-theme="dark"] input[type="number"],
|
|
178
|
+
[data-theme="dark"] input[type="text"],
|
|
179
|
+
[data-theme="dark"] textarea { background: #0d1117; color: #c9d1d9; border-color: #30363d; }
|
|
180
|
+
[data-theme="dark"] select:focus,
|
|
181
|
+
[data-theme="dark"] input:focus,
|
|
182
|
+
[data-theme="dark"] textarea:focus { border-color: #58a6ff; box-shadow: 0 0 0 2px rgba(88,166,255,.15); }
|
|
183
|
+
[data-theme="dark"] textarea[readonly] { background: #161b22; }
|
|
184
|
+
[data-theme="dark"] label { color: #c9d1d9; }
|
|
185
|
+
|
|
186
|
+
/* Buttons */
|
|
187
|
+
[data-theme="dark"] .mg-btn-primary { background: #238636; border-color: #238636; }
|
|
188
|
+
[data-theme="dark"] .mg-btn-primary:hover:not(:disabled) { background: #2ea043; }
|
|
189
|
+
[data-theme="dark"] .mg-btn-outline { background: #0d1117; color: #58a6ff; border-color: #30363d; }
|
|
190
|
+
[data-theme="dark"] .mg-btn-outline:hover:not(:disabled) { background: #161b22; border-color: #58a6ff; }
|
|
191
|
+
[data-theme="dark"] .mg-btn-outline-secondary { background: #0d1117; color: #8b949e; border-color: #30363d; }
|
|
192
|
+
[data-theme="dark"] .mg-btn-outline-secondary:hover:not(:disabled) { background: #161b22; }
|
|
193
|
+
[data-theme="dark"] .mg-btn-outline-danger { background: #0d1117; color: #f85149; border-color: #30363d; }
|
|
194
|
+
[data-theme="dark"] .mg-btn-outline-danger:hover:not(:disabled) { background: #1c0d0d; }
|
|
195
|
+
|
|
196
|
+
/* Badges */
|
|
197
|
+
[data-theme="dark"] .mg-badge-info { background: #0d2a3a; color: #58a6ff; }
|
|
198
|
+
[data-theme="dark"] .mg-badge-warning { background: #2d2000; color: #d29922; }
|
|
199
|
+
[data-theme="dark"] .mg-badge-danger { background: #2d0d0d; color: #f85149; }
|
|
200
|
+
[data-theme="dark"] .mg-badge-secondary { background: #21262d; color: #8b949e; }
|
|
201
|
+
[data-theme="dark"] .mg-badge-success { background: #0d2d1a; color: #3fb950; }
|
|
202
|
+
|
|
203
|
+
/* Alerts */
|
|
204
|
+
[data-theme="dark"] .mg-alert-danger { background: #2d0d0d; color: #f85149; border-color: #3d1414; }
|
|
205
|
+
[data-theme="dark"] .mg-alert-warning { background: #2d2000; color: #d29922; border-color: #3d2e00; }
|
|
206
|
+
[data-theme="dark"] .mg-alert-info { background: #0d2a3a; color: #58a6ff; border-color: #0d3a5a; }
|
|
207
|
+
|
|
208
|
+
/* Cards */
|
|
209
|
+
[data-theme="dark"] .mg-card { border-color: #30363d; }
|
|
210
|
+
[data-theme="dark"] .mg-card-header { background: #161b22; border-bottom-color: #30363d; color: #c9d1d9; }
|
|
211
|
+
[data-theme="dark"] .mg-card-toggle { color: #58a6ff; }
|
|
212
|
+
|
|
213
|
+
/* Stat grid */
|
|
214
|
+
[data-theme="dark"] .mg-stat-grid .mg-stat-label { color: #8b949e; }
|
|
215
|
+
[data-theme="dark"] .mg-usage-bar { background: #21262d; }
|
|
216
|
+
|
|
217
|
+
/* Tables */
|
|
218
|
+
[data-theme="dark"] .mg-table th { background: #161b22; color: #8b949e; border-bottom-color: #30363d; }
|
|
219
|
+
[data-theme="dark"] .mg-table th.mg-sortable:hover { color: #c9d1d9; }
|
|
220
|
+
[data-theme="dark"] .mg-table td { border-bottom-color: #21262d; }
|
|
221
|
+
[data-theme="dark"] .mg-table tbody tr:hover { background: #1c2128; }
|
|
222
|
+
[data-theme="dark"] .mg-table tbody tr:nth-child(even) { background: #0d1117; }
|
|
223
|
+
[data-theme="dark"] .mg-table tbody tr:nth-child(even):hover { background: #1c2128; }
|
|
224
|
+
[data-theme="dark"] .mg-table em.null { color: #484f58; }
|
|
225
|
+
[data-theme="dark"] .mg-table .redacted { color: #484f58; }
|
|
226
|
+
|
|
227
|
+
/* Duration colors (dark) */
|
|
228
|
+
[data-theme="dark"] .mg-dur-fast { color: #3fb950; }
|
|
229
|
+
[data-theme="dark"] .mg-dur-moderate { color: #d29922; }
|
|
230
|
+
[data-theme="dark"] .mg-dur-slow { color: #f85149; }
|
|
231
|
+
|
|
232
|
+
/* Inline code */
|
|
233
|
+
[data-theme="dark"] code { background: #21262d; color: #c9d1d9; }
|
|
234
|
+
[data-theme="dark"] pre { background: #161b22; color: #c9d1d9; border-color: #30363d; }
|
|
235
|
+
[data-theme="dark"] pre.mg-pre { background: #161b22; color: #c9d1d9; }
|
|
236
|
+
[data-theme="dark"] .mg-card-body { color: #c9d1d9; }
|
|
237
|
+
[data-theme="dark"] .mg-card-body h1, [data-theme="dark"] .mg-card-body h2, [data-theme="dark"] .mg-card-body h3, [data-theme="dark"] .mg-card-body h4 { color: #c9d1d9; }
|
|
238
|
+
[data-theme="dark"] .mg-card-body p, [data-theme="dark"] .mg-card-body li, [data-theme="dark"] .mg-card-body td { color: #c9d1d9; }
|
|
239
|
+
[data-theme="dark"] .mg-card-body strong { color: #e6edf3; }
|
|
240
|
+
[data-theme="dark"] .mg-card-body a { color: #58a6ff; }
|
|
241
|
+
|
|
242
|
+
/* Links */
|
|
243
|
+
[data-theme="dark"] .mg-link { color: #58a6ff; }
|
|
244
|
+
|
|
245
|
+
/* Spinner */
|
|
246
|
+
[data-theme="dark"] .mg-spinner { border-color: #30363d; border-top-color: #58a6ff; }
|
|
247
|
+
|
|
248
|
+
/* Text */
|
|
249
|
+
[data-theme="dark"] .mg-text-muted { color: #8b949e; }
|
|
250
|
+
|
|
251
|
+
/* Checkboxes */
|
|
252
|
+
[data-theme="dark"] .mg-check .type-hint { color: #484f58; }
|
|
253
|
+
|
|
254
|
+
/* AI result sections (dark) */
|
|
255
|
+
[data-theme="dark"] .mg-ai-section { border-color: #30363d; }
|
|
256
|
+
[data-theme="dark"] .mg-ai-danger { background: #2d0d0d; border-left-color: #f85149; }
|
|
257
|
+
[data-theme="dark"] .mg-ai-danger .mg-ai-section-header { border-bottom-color: #3d1414; color: #f85149; }
|
|
258
|
+
[data-theme="dark"] .mg-ai-warning { background: #2d2000; border-left-color: #d29922; }
|
|
259
|
+
[data-theme="dark"] .mg-ai-warning .mg-ai-section-header { border-bottom-color: #3d2e00; color: #d29922; }
|
|
260
|
+
[data-theme="dark"] .mg-ai-info { background: #0d2a3a; border-left-color: #58a6ff; }
|
|
261
|
+
[data-theme="dark"] .mg-ai-info .mg-ai-section-header { border-bottom-color: #0d3a5a; color: #58a6ff; }
|
|
262
|
+
[data-theme="dark"] .mg-ai-section-body { color: #c9d1d9; }
|
|
263
|
+
|
|
264
|
+
/* Theme toggle (dark) */
|
|
265
|
+
[data-theme="dark"] .mg-theme-toggle { border-color: #30363d; color: #c9d1d9; }
|
|
266
|
+
[data-theme="dark"] .mg-theme-toggle:hover { background: #21262d; }
|
|
267
|
+
|
|
268
|
+
/* Database switcher (dark) */
|
|
269
|
+
[data-theme="dark"] .mg-db-switcher select { background: #0d1117; color: #c9d1d9; border-color: #30363d; }
|
|
270
|
+
[data-theme="dark"] .mg-db-badge { background: #0d2a3a; color: #58a6ff; }
|
|
271
|
+
</style>
|
|
272
|
+
</head>
|
|
273
|
+
<body>
|
|
274
|
+
<script>
|
|
275
|
+
(function() {
|
|
276
|
+
var saved = localStorage.getItem('mg-theme');
|
|
277
|
+
var theme = saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
278
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
279
|
+
})();
|
|
280
|
+
</script>
|
|
281
|
+
<div class="mg-container">
|
|
282
|
+
<%= yield %>
|
|
283
|
+
</div>
|
|
284
|
+
</body>
|
|
285
|
+
</html>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
SqlGenius::Engine.routes.draw do
|
|
4
|
+
root to: "queries#index"
|
|
5
|
+
|
|
6
|
+
get "columns", to: "queries#columns"
|
|
7
|
+
post "execute", to: "queries#execute"
|
|
8
|
+
post "explain", to: "queries#explain"
|
|
9
|
+
get "queries/:digest", to: "queries#query_detail", as: "query_detail"
|
|
10
|
+
get "api/query_history/:digest", to: "queries#query_history", as: "query_history"
|
|
11
|
+
post "suggest", to: "queries#suggest"
|
|
12
|
+
post "optimize", to: "queries#optimize"
|
|
13
|
+
get "slow_queries", to: "queries#slow_queries"
|
|
14
|
+
get "duplicate_indexes", to: "queries#duplicate_indexes"
|
|
15
|
+
get "table_sizes", to: "queries#table_sizes"
|
|
16
|
+
get "query_stats", to: "queries#query_stats"
|
|
17
|
+
get "unused_indexes", to: "queries#unused_indexes"
|
|
18
|
+
get "server_overview", to: "queries#server_overview"
|
|
19
|
+
|
|
20
|
+
# AI features
|
|
21
|
+
post "describe_query", to: "queries#describe_query"
|
|
22
|
+
post "schema_review", to: "queries#schema_review"
|
|
23
|
+
post "rewrite_query", to: "queries#rewrite_query"
|
|
24
|
+
post "index_advisor", to: "queries#index_advisor"
|
|
25
|
+
post "anomaly_detection", to: "queries#anomaly_detection"
|
|
26
|
+
post "root_cause", to: "queries#root_cause"
|
|
27
|
+
post "migration_risk", to: "queries#migration_risk"
|
|
28
|
+
post "variable_review", to: "queries#variable_review"
|
|
29
|
+
post "connection_advisor", to: "queries#connection_advisor"
|
|
30
|
+
post "workload_digest", to: "queries#workload_digest"
|
|
31
|
+
post "innodb_health", to: "queries#innodb_health"
|
|
32
|
+
post "index_planner", to: "queries#index_planner"
|
|
33
|
+
post "pattern_grouper", to: "queries#pattern_grouper"
|
|
34
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# AI Features Guide
|
|
2
|
+
|
|
3
|
+
SqlGenius integrates with OpenAI-compatible LLM APIs to provide AI-powered database analysis. All AI features are optional — the dashboard works fully without them.
|
|
4
|
+
|
|
5
|
+
## Supported providers
|
|
6
|
+
|
|
7
|
+
| Provider | Endpoint | Auth Style |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| OpenAI | `https://api.openai.com/v1/chat/completions` | Bearer |
|
|
10
|
+
| Anthropic | `https://api.anthropic.com/v1/messages` | x-api-key |
|
|
11
|
+
| Google Gemini | `https://generativelanguage.googleapis.com/v1beta/openai/chat/completions` | Bearer |
|
|
12
|
+
| Azure OpenAI | `https://YOUR-RESOURCE.openai.azure.com/...` | api-key |
|
|
13
|
+
| DeepSeek | `https://api.deepseek.com/chat/completions` | Bearer |
|
|
14
|
+
| Groq | `https://api.groq.com/openai/v1/chat/completions` | Bearer |
|
|
15
|
+
| Ollama (local) | `http://localhost:11434/v1/chat/completions` | Bearer |
|
|
16
|
+
| OpenRouter | `https://openrouter.ai/api/v1/chat/completions` | Bearer |
|
|
17
|
+
| Perplexity | `https://api.perplexity.ai/chat/completions` | Bearer |
|
|
18
|
+
| Any OpenAI-compatible | Your endpoint URL | Bearer or api-key |
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
In your Rails initializer (`config/initializers/sql_genius.rb`):
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
SqlGenius.configure do |config|
|
|
26
|
+
config.ai_endpoint = "https://api.openai.com/v1/chat/completions"
|
|
27
|
+
config.ai_api_key = ENV["OPENAI_API_KEY"]
|
|
28
|
+
config.ai_model = "gpt-4o-mini"
|
|
29
|
+
config.ai_auth_style = :bearer # or :api_key for Azure, :x_api_key for Anthropic
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Available features
|
|
34
|
+
|
|
35
|
+
### Query Explorer AI
|
|
36
|
+
|
|
37
|
+
| Feature | What it does | Where |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| **AI Suggest** | Generate SQL from natural language | Query Explorer tab |
|
|
40
|
+
| **AI Optimization** | Analyze EXPLAIN output, suggest improvements | After running EXPLAIN |
|
|
41
|
+
| **Index Advisor** | Recommend indexes for a specific query | After running EXPLAIN |
|
|
42
|
+
| **Describe Query** | Explain what a SQL query does in plain English | Query Explorer tab |
|
|
43
|
+
| **Rewrite Query** | Suggest an optimized version of the SQL | Query Explorer tab |
|
|
44
|
+
|
|
45
|
+
### Schema & Migration
|
|
46
|
+
|
|
47
|
+
| Feature | What it does | Where |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| **Schema Review** | Find anti-patterns across your schema | AI Tools tab |
|
|
50
|
+
| **Migration Risk** | Assess safety of a DDL/migration before deploying | AI Tools tab |
|
|
51
|
+
| **AI Optimize** | Review a specific table's schema (appears on fragmented tables) | Tables tab |
|
|
52
|
+
|
|
53
|
+
### Server Analysis
|
|
54
|
+
|
|
55
|
+
| Feature | What it does | Where |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| **Variable Config Review** | Review my.cnf settings against your workload | Server tab |
|
|
58
|
+
| **Connection Advisor** | Diagnose connection pool issues | Server tab |
|
|
59
|
+
| **InnoDB Health** | Interpret SHOW ENGINE INNODB STATUS | Server tab |
|
|
60
|
+
|
|
61
|
+
### Workload Analysis
|
|
62
|
+
|
|
63
|
+
| Feature | What it does | Where |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| **Workload Digest** | Executive summary of your query workload | Query Stats tab |
|
|
66
|
+
| **Pattern Grouper** | Group slow queries by shared root cause | Query Stats tab |
|
|
67
|
+
| **Index Planner** | Holistic index optimization plan | Indexes tabs |
|
|
68
|
+
|
|
69
|
+
## Settings
|
|
70
|
+
|
|
71
|
+
### Max Tokens
|
|
72
|
+
|
|
73
|
+
Controls the maximum response length from the LLM. Default: 4096. Adjust with the slider in AI Configuration.
|
|
74
|
+
|
|
75
|
+
- **Lower (256-1024)** — faster responses, may truncate complex analyses
|
|
76
|
+
- **Higher (4096-16384)** — complete analyses, slower and more expensive
|
|
77
|
+
|
|
78
|
+
### System Prompt
|
|
79
|
+
|
|
80
|
+
Optional context injected into every AI request. Use it to describe your application:
|
|
81
|
+
|
|
82
|
+
> "This is an e-commerce platform with 50M orders. The database handles 5000 QPS during peak hours."
|
|
83
|
+
|
|
84
|
+
### Domain Prompt
|
|
85
|
+
|
|
86
|
+
Optional instructions for the AI's recommendations:
|
|
87
|
+
|
|
88
|
+
> "Prefer window functions over correlated subqueries. Don't recommend foreign keys — we handle referential integrity in the application layer."
|
|
89
|
+
|
|
90
|
+
## Using with Ollama (free, local)
|
|
91
|
+
|
|
92
|
+
1. Install Ollama: `brew install ollama`
|
|
93
|
+
2. Pull a model: `ollama pull llama3.2`
|
|
94
|
+
3. Start Ollama: `ollama serve`
|
|
95
|
+
4. In SqlGenius, select **Ollama (local)** as the provider
|
|
96
|
+
5. The endpoint and model auto-fill — just click **Save**
|
|
97
|
+
|
|
98
|
+
No API key needed. All data stays on your machine.
|
|
99
|
+
|
|
100
|
+
## Copying AI responses
|
|
101
|
+
|
|
102
|
+
Every AI response has a **Copy response** button at the bottom right. Click it to copy the plain text to your clipboard — useful for sharing with team members or pasting into tickets.
|
|
103
|
+
|
|
104
|
+
## Cost considerations
|
|
105
|
+
|
|
106
|
+
Each AI feature makes one API call per invocation. Typical costs with OpenAI gpt-4o-mini:
|
|
107
|
+
|
|
108
|
+
| Feature | ~Input tokens | ~Output tokens | ~Cost |
|
|
109
|
+
|---|---|---|---|
|
|
110
|
+
| Schema Review (all tables) | 2000-5000 | 500-2000 | $0.001-0.005 |
|
|
111
|
+
| Migration Risk | 500-1000 | 500-1000 | $0.001 |
|
|
112
|
+
| Query Optimization | 1000-2000 | 500-1000 | $0.001 |
|
|
113
|
+
| Workload Digest | 3000-5000 | 1000-2000 | $0.003 |
|
|
114
|
+
|
|
115
|
+
Using Ollama or other local models eliminates API costs entirely.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Getting Started with SqlGenius (Rails)
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
Add to your Gemfile:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem "sql_genius"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bundle install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Mount the engine
|
|
16
|
+
|
|
17
|
+
In `config/routes.rb`:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
Rails.application.routes.draw do
|
|
21
|
+
mount SqlGenius::Engine, at: "/sql_genius"
|
|
22
|
+
# ... your other routes
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
Create an initializer at `config/initializers/sql_genius.rb`:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
SqlGenius.configure do |config|
|
|
32
|
+
# Authentication (required in production)
|
|
33
|
+
config.authenticate = ->(controller) {
|
|
34
|
+
# Example: restrict to admin users
|
|
35
|
+
controller.current_user&.admin?
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Tables to hide from the dashboard
|
|
39
|
+
config.blocked_tables = %w[
|
|
40
|
+
schema_migrations
|
|
41
|
+
ar_internal_metadata
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# Columns to mask in query results
|
|
45
|
+
config.masked_column_patterns = %w[
|
|
46
|
+
password
|
|
47
|
+
token
|
|
48
|
+
secret
|
|
49
|
+
ssn
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Query limits
|
|
53
|
+
config.default_row_limit = 100
|
|
54
|
+
config.max_row_limit = 10_000
|
|
55
|
+
config.query_timeout_ms = 10_000
|
|
56
|
+
|
|
57
|
+
# AI features (optional)
|
|
58
|
+
config.ai_endpoint = ENV["SQL_GENIUS_AI_ENDPOINT"]
|
|
59
|
+
config.ai_api_key = ENV["SQL_GENIUS_AI_KEY"]
|
|
60
|
+
config.ai_model = "gpt-4o-mini"
|
|
61
|
+
config.ai_auth_style = :bearer
|
|
62
|
+
|
|
63
|
+
# Stats collection (background thread, default: true)
|
|
64
|
+
config.stats_collection = true
|
|
65
|
+
|
|
66
|
+
# Slow query monitoring via Redis (optional)
|
|
67
|
+
# config.redis_url = ENV["REDIS_URL"]
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Visit the dashboard
|
|
72
|
+
|
|
73
|
+
Start your Rails server and navigate to:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
http://localhost:3000/sql_genius
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Features
|
|
80
|
+
|
|
81
|
+
### Dashboard
|
|
82
|
+
Overview of server health, top slow queries, expensive queries, duplicate/unused index counts.
|
|
83
|
+
|
|
84
|
+
### Query Explorer
|
|
85
|
+
Run SELECT queries against your database with syntax highlighting, EXPLAIN output, and AI-powered suggestions.
|
|
86
|
+
|
|
87
|
+
### Query Stats
|
|
88
|
+
Top queries from `performance_schema.events_statements_summary_by_digest`. Click any query to see a detail page with time-series performance charts.
|
|
89
|
+
|
|
90
|
+
### Server
|
|
91
|
+
Server status, connections, InnoDB buffer pool, query activity.
|
|
92
|
+
|
|
93
|
+
### Tables
|
|
94
|
+
Table sizes with fragmentation detection. Tables needing optimization get an "AI Optimize" button.
|
|
95
|
+
|
|
96
|
+
### Indexes
|
|
97
|
+
Duplicate and unused index detection.
|
|
98
|
+
|
|
99
|
+
### AI Tools (when configured)
|
|
100
|
+
- **Schema Review** — find anti-patterns in your schema
|
|
101
|
+
- **Migration Risk** — assess DDL safety before deploying
|
|
102
|
+
- **Query Description** — explain what a query does in plain English
|
|
103
|
+
- **Query Rewrite** — suggest optimized versions of slow queries
|
|
104
|
+
- **Index Advisor** — recommend indexes for specific queries
|
|
105
|
+
|
|
106
|
+
## Security
|
|
107
|
+
|
|
108
|
+
**Always configure authentication in production.** Without it, anyone who can reach the mounted path can view your database schema and run SELECT queries.
|
|
109
|
+
|
|
110
|
+
The `blocked_tables` and `masked_column_patterns` settings provide defense-in-depth but are not a substitute for authentication.
|
|
111
|
+
|
|
112
|
+
## Supported databases
|
|
113
|
+
|
|
114
|
+
- MySQL 5.7+
|
|
115
|
+
- MySQL 8.0+
|
|
116
|
+
- MariaDB 10.3+
|
|
117
|
+
|
|
118
|
+
Some features (query stats, unused indexes) require `performance_schema` to be enabled.
|