Skip to content

Commit 1d90eba

Browse files
committed
Add HTML layout with CSS for Sinatra dashboard rendering
1 parent a80e4ab commit 1d90eba

2 files changed

Lines changed: 230 additions & 2 deletions

File tree

gems/mysql_genius-desktop/lib/mysql_genius/desktop/app.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,12 @@ def capability?(name)
308308

309309
private
310310

311+
LAYOUT_PATH = File.expand_path("layout.html.erb", __dir__).freeze
312+
311313
def render_dashboard
312-
path = File.join(MysqlGenius::Core.views_path, "mysql_genius/queries/dashboard.html.erb")
313-
Tilt.new(path).render(self)
314+
dashboard_path = File.join(MysqlGenius::Core.views_path, "mysql_genius/queries/dashboard.html.erb")
315+
content = Tilt.new(dashboard_path).render(self)
316+
Tilt.new(LAYOUT_PATH).render(self) { content }
314317
end
315318

316319
def json_response(obj)
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>MySQLGenius</title>
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<style>
7+
*, *::before, *::after { box-sizing: border-box; }
8+
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; }
9+
.mg-container { max-width: 1400px; margin: 0 auto; padding: 20px; }
10+
h4 { margin: 0 0 16px; font-size: 18px; }
11+
12+
/* Tabs */
13+
.mg-tabs { display: flex; border-bottom: 2px solid #dee2e6; margin-bottom: 0; }
14+
.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; }
15+
.mg-tab:hover { color: #000; }
16+
.mg-tab.active { background: #fff; border-color: #dee2e6; border-bottom-color: #fff; color: #000; font-weight: 500; }
17+
.mg-tab-content { display: none; background: #fff; border: 1px solid #dee2e6; border-top: none; padding: 16px; }
18+
.mg-tab-content.active { display: block; }
19+
20+
/* Forms */
21+
label { display: block; font-weight: 500; margin-bottom: 4px; font-size: 13px; }
22+
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; }
23+
select:focus, input:focus, textarea:focus { outline: none; border-color: #80bdff; box-shadow: 0 0 0 2px rgba(0,123,255,.15); }
24+
textarea { font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas, monospace; resize: vertical; }
25+
textarea[readonly] { background: #f8f9fa; }
26+
27+
/* Grid helpers */
28+
.mg-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
29+
.mg-col-1 { flex: 0 0 80px; }
30+
.mg-col-2 { flex: 0 0 160px; }
31+
.mg-col-3 { flex: 0 0 240px; }
32+
.mg-col-4 { flex: 0 0 320px; }
33+
.mg-col-grow { flex: 1 1 200px; }
34+
.mg-field { margin-bottom: 12px; }
35+
36+
/* Buttons */
37+
.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; }
38+
.mg-btn:disabled { opacity: .5; cursor: not-allowed; }
39+
.mg-btn-primary { background: #007bff; color: #fff; border-color: #007bff; }
40+
.mg-btn-primary:hover:not(:disabled) { background: #0069d9; }
41+
.mg-btn-outline { background: #fff; color: #007bff; border-color: #007bff; }
42+
.mg-btn-outline:hover:not(:disabled) { background: #e7f1ff; }
43+
.mg-btn-outline-secondary { background: #fff; color: #6c757d; border-color: #6c757d; }
44+
.mg-btn-outline-secondary:hover:not(:disabled) { background: #f8f9fa; }
45+
.mg-btn-outline-danger { background: #fff; color: #dc3545; border-color: #dc3545; padding: 4px 8px; }
46+
.mg-btn-outline-danger:hover:not(:disabled) { background: #ffeef0; }
47+
.mg-btn-sm { padding: 3px 8px; font-size: 12px; }
48+
49+
/* Badge */
50+
.mg-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 500; }
51+
.mg-badge-info { background: #d1ecf1; color: #0c5460; }
52+
.mg-badge-warning { background: #fff3cd; color: #856404; }
53+
.mg-badge-danger { background: #f8d7da; color: #721c24; }
54+
.mg-badge-secondary { background: #e2e3e5; color: #383d41; }
55+
.mg-badge-success { background: #d4edda; color: #155724; }
56+
57+
/* Alert */
58+
.mg-alert { padding: 10px 14px; border-radius: 4px; margin-bottom: 12px; font-size: 13px; }
59+
.mg-alert-danger { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
60+
.mg-alert-warning { background: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
61+
.mg-alert-info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
62+
63+
/* Card */
64+
.mg-card { border: 1px solid #dee2e6; border-radius: 4px; margin-bottom: 12px; }
65+
.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; }
66+
.mg-card-body { padding: 12px; }
67+
.mg-card-toggle { cursor: pointer; color: #007bff; text-decoration: none; font-size: 13px; }
68+
.mg-card-toggle:hover { text-decoration: underline; }
69+
70+
/* Stat grid */
71+
.mg-stat-grid { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; font-size: 13px; }
72+
.mg-stat-grid .mg-stat-label { color: #666; }
73+
.mg-stat-grid .mg-stat-value { text-align: right; font-weight: 500; }
74+
.mg-usage-bar { background: #e9ecef; border-radius: 4px; height: 20px; position: relative; overflow: hidden; }
75+
.mg-usage-bar-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
76+
.mg-usage-bar-text { position: absolute; top: 0; left: 0; right: 0; text-align: center; line-height: 20px; font-size: 11px; font-weight: 500; }
77+
78+
/* Table */
79+
.mg-table-wrap { overflow-x: auto; }
80+
table.mg-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; }
81+
.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; }
82+
.mg-table th.mg-sortable { cursor: pointer; position: relative; padding-right: 20px; }
83+
.mg-table th.mg-sortable:hover { color: #333; }
84+
.mg-table th.mg-sortable::after { content: '\2195'; position: absolute; right: 6px; opacity: 0.3; font-size: 10px; }
85+
.mg-table th.mg-sort-asc::after { content: '\2191'; opacity: 0.8; }
86+
.mg-table th.mg-sort-desc::after { content: '\2193'; opacity: 0.8; }
87+
.mg-table th:first-child { border-top-left-radius: 6px; }
88+
.mg-table th:last-child { border-top-right-radius: 6px; }
89+
.mg-table td { padding: 8px 12px; border-bottom: 1px solid #eaecef; vertical-align: top; }
90+
.mg-table tbody tr { transition: background-color 0.15s ease; }
91+
.mg-table tbody tr:hover { background: #f0f6ff; }
92+
.mg-table tbody tr:nth-child(even) { background: #fafbfc; }
93+
.mg-table tbody tr:nth-child(even):hover { background: #f0f6ff; }
94+
.mg-table tbody tr:last-child td { border-bottom: none; }
95+
.mg-table em.null { color: #999; font-style: italic; }
96+
.mg-table .redacted { color: #999; }
97+
98+
/* Numeric table cells */
99+
.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; }
100+
101+
/* SQL code blocks in tables */
102+
.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; }
103+
.mg-sql-block::-webkit-scrollbar { width: 4px; }
104+
.mg-sql-block::-webkit-scrollbar-track { background: transparent; }
105+
.mg-sql-block::-webkit-scrollbar-thumb { background: #585b70; border-radius: 2px; }
106+
107+
/* SQL syntax colors (Catppuccin Mocha inspired) */
108+
.mg-sql-kw { color: #cba6f7; font-weight: 600; }
109+
.mg-sql-fn { color: #89b4fa; }
110+
.mg-sql-str { color: #a6e3a1; }
111+
.mg-sql-num { color: #fab387; }
112+
.mg-sql-op { color: #89dceb; }
113+
.mg-sql-comment { color: #6c7086; font-style: italic; }
114+
.mg-sql-tbl { color: #f9e2af; }
115+
.mg-sql-star { color: #f38ba8; font-weight: 700; }
116+
.mg-sql-punc { color: #9399b2; }
117+
.mg-sql-placeholder { color: #74c7ec; font-style: italic; }
118+
119+
/* Duration color coding */
120+
.mg-dur-fast { color: #1a7f37; }
121+
.mg-dur-moderate { color: #9a6700; }
122+
.mg-dur-slow { color: #cf222e; font-weight: 600; }
123+
124+
/* Checkbox grid */
125+
.mg-checks { display: flex; flex-wrap: wrap; gap: 4px 16px; }
126+
.mg-check { display: flex; align-items: center; gap: 4px; font-size: 13px; cursor: pointer; }
127+
.mg-check input { margin: 0; }
128+
.mg-check .type-hint { color: #999; font-size: 11px; }
129+
130+
/* Inline links */
131+
.mg-link { color: #007bff; cursor: pointer; text-decoration: none; font-size: 12px; margin-left: 8px; }
132+
.mg-link:hover { text-decoration: underline; }
133+
134+
/* Spinner */
135+
@keyframes mg-spin { to { transform: rotate(360deg); } }
136+
.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; }
137+
138+
/* Utilities */
139+
.mg-hidden { display: none !important; }
140+
.mg-mt { margin-top: 16px; }
141+
.mg-mb { margin-bottom: 12px; }
142+
.mg-text-muted { color: #888; font-size: 13px; }
143+
.mg-text-center { text-align: center; padding: 24px 0; }
144+
code { font-size: 12px; word-break: break-all; background: #f0f1f3; padding: 2px 6px; border-radius: 3px; color: #24292f; }
145+
pre.mg-pre { background: #f4f4f4; padding: 10px; border-radius: 4px; overflow-x: auto; font-size: 12px; }
146+
147+
/* Theme toggle */
148+
.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; }
149+
.mg-theme-toggle:hover { background: #e9ecef; }
150+
151+
/* --- Dark Theme --- */
152+
[data-theme="dark"] body,
153+
[data-theme="dark"] { background: #161b22; color: #c9d1d9; }
154+
[data-theme="dark"] .mg-container { color: #c9d1d9; }
155+
[data-theme="dark"] .mg-tabs { border-bottom-color: #30363d; }
156+
[data-theme="dark"] .mg-tab { color: #8b949e; }
157+
[data-theme="dark"] .mg-tab:hover { color: #c9d1d9; }
158+
[data-theme="dark"] .mg-tab.active { background: #0d1117; border-color: #30363d; border-bottom-color: #0d1117; color: #c9d1d9; }
159+
[data-theme="dark"] .mg-tab-content { background: #0d1117; border-color: #30363d; }
160+
[data-theme="dark"] select,
161+
[data-theme="dark"] input[type="number"],
162+
[data-theme="dark"] input[type="text"],
163+
[data-theme="dark"] textarea { background: #0d1117; color: #c9d1d9; border-color: #30363d; }
164+
[data-theme="dark"] select:focus,
165+
[data-theme="dark"] input:focus,
166+
[data-theme="dark"] textarea:focus { border-color: #58a6ff; box-shadow: 0 0 0 2px rgba(88,166,255,.15); }
167+
[data-theme="dark"] textarea[readonly] { background: #161b22; }
168+
[data-theme="dark"] label { color: #c9d1d9; }
169+
[data-theme="dark"] .mg-btn-primary { background: #238636; border-color: #238636; }
170+
[data-theme="dark"] .mg-btn-primary:hover:not(:disabled) { background: #2ea043; }
171+
[data-theme="dark"] .mg-btn-outline { background: #0d1117; color: #58a6ff; border-color: #30363d; }
172+
[data-theme="dark"] .mg-btn-outline:hover:not(:disabled) { background: #161b22; border-color: #58a6ff; }
173+
[data-theme="dark"] .mg-btn-outline-secondary { background: #0d1117; color: #8b949e; border-color: #30363d; }
174+
[data-theme="dark"] .mg-btn-outline-secondary:hover:not(:disabled) { background: #161b22; }
175+
[data-theme="dark"] .mg-btn-outline-danger { background: #0d1117; color: #f85149; border-color: #30363d; }
176+
[data-theme="dark"] .mg-btn-outline-danger:hover:not(:disabled) { background: #1c0d0d; }
177+
[data-theme="dark"] .mg-badge-info { background: #0d2a3a; color: #58a6ff; }
178+
[data-theme="dark"] .mg-badge-warning { background: #2d2000; color: #d29922; }
179+
[data-theme="dark"] .mg-badge-danger { background: #2d0d0d; color: #f85149; }
180+
[data-theme="dark"] .mg-badge-secondary { background: #21262d; color: #8b949e; }
181+
[data-theme="dark"] .mg-badge-success { background: #0d2d1a; color: #3fb950; }
182+
[data-theme="dark"] .mg-alert-danger { background: #2d0d0d; color: #f85149; border-color: #3d1414; }
183+
[data-theme="dark"] .mg-alert-warning { background: #2d2000; color: #d29922; border-color: #3d2e00; }
184+
[data-theme="dark"] .mg-alert-info { background: #0d2a3a; color: #58a6ff; border-color: #0d3a5a; }
185+
[data-theme="dark"] .mg-card { border-color: #30363d; }
186+
[data-theme="dark"] .mg-card-header { background: #161b22; border-bottom-color: #30363d; color: #c9d1d9; }
187+
[data-theme="dark"] .mg-card-toggle { color: #58a6ff; }
188+
[data-theme="dark"] .mg-stat-grid .mg-stat-label { color: #8b949e; }
189+
[data-theme="dark"] .mg-usage-bar { background: #21262d; }
190+
[data-theme="dark"] .mg-table th { background: #161b22; color: #8b949e; border-bottom-color: #30363d; }
191+
[data-theme="dark"] .mg-table th.mg-sortable:hover { color: #c9d1d9; }
192+
[data-theme="dark"] .mg-table td { border-bottom-color: #21262d; }
193+
[data-theme="dark"] .mg-table tbody tr:hover { background: #1c2128; }
194+
[data-theme="dark"] .mg-table tbody tr:nth-child(even) { background: #0d1117; }
195+
[data-theme="dark"] .mg-table tbody tr:nth-child(even):hover { background: #1c2128; }
196+
[data-theme="dark"] .mg-table em.null { color: #484f58; }
197+
[data-theme="dark"] .mg-table .redacted { color: #484f58; }
198+
[data-theme="dark"] .mg-dur-fast { color: #3fb950; }
199+
[data-theme="dark"] .mg-dur-moderate { color: #d29922; }
200+
[data-theme="dark"] .mg-dur-slow { color: #f85149; }
201+
[data-theme="dark"] code { background: #21262d; color: #c9d1d9; }
202+
[data-theme="dark"] pre.mg-pre { background: #161b22; color: #c9d1d9; }
203+
[data-theme="dark"] .mg-link { color: #58a6ff; }
204+
[data-theme="dark"] .mg-spinner { border-color: #30363d; border-top-color: #58a6ff; }
205+
[data-theme="dark"] .mg-text-muted { color: #8b949e; }
206+
[data-theme="dark"] .mg-check .type-hint { color: #484f58; }
207+
[data-theme="dark"] .mg-theme-toggle { border-color: #30363d; color: #c9d1d9; }
208+
[data-theme="dark"] .mg-theme-toggle:hover { background: #21262d; }
209+
[data-theme="dark"] .mg-db-switcher select { background: #0d1117; color: #c9d1d9; border-color: #30363d; }
210+
[data-theme="dark"] .mg-db-badge { background: #0d2a3a; color: #58a6ff; }
211+
</style>
212+
</head>
213+
<body>
214+
<script>
215+
(function() {
216+
var saved = localStorage.getItem('mg-theme');
217+
var theme = saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
218+
document.documentElement.setAttribute('data-theme', theme);
219+
})();
220+
</script>
221+
<div class="mg-container">
222+
<%= yield %>
223+
</div>
224+
</body>
225+
</html>

0 commit comments

Comments
 (0)