dbviewer 0.6.6 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -36
- data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +553 -0
- data/app/assets/javascripts/dbviewer/home.js +287 -0
- data/app/assets/javascripts/dbviewer/layout.js +194 -0
- data/app/assets/javascripts/dbviewer/query.js +277 -0
- data/app/assets/javascripts/dbviewer/table.js +1563 -0
- data/app/assets/stylesheets/dbviewer/application.css +1460 -21
- data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +181 -0
- data/app/assets/stylesheets/dbviewer/home.css +229 -0
- data/app/assets/stylesheets/dbviewer/logs.css +64 -0
- data/app/assets/stylesheets/dbviewer/query.css +171 -0
- data/app/assets/stylesheets/dbviewer/table.css +1144 -0
- data/app/views/dbviewer/connections/index.html.erb +0 -30
- data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +14 -713
- data/app/views/dbviewer/home/index.html.erb +9 -499
- data/app/views/dbviewer/logs/index.html.erb +22 -221
- data/app/views/dbviewer/tables/index.html.erb +0 -65
- data/app/views/dbviewer/tables/query.html.erb +129 -565
- data/app/views/dbviewer/tables/show.html.erb +4 -2429
- data/app/views/layouts/dbviewer/application.html.erb +13 -1544
- data/lib/dbviewer/version.rb +1 -1
- metadata +12 -7
- data/app/assets/javascripts/dbviewer/connections.js +0 -70
- data/app/assets/stylesheets/dbviewer/dbviewer.css +0 -0
- data/app/assets/stylesheets/dbviewer/enhanced.css +0 -0
- data/app/views/dbviewer/connections/new.html.erb +0 -79
- data/app/views/dbviewer/tables/mini_erd.html.erb +0 -517
@@ -0,0 +1,277 @@
|
|
1
|
+
import * as monaco from "https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/+esm";
|
2
|
+
|
3
|
+
// Helper function to decode HTML entities
|
4
|
+
function decodeHTMLEntities(text) {
|
5
|
+
const textarea = document.createElement("textarea");
|
6
|
+
textarea.innerHTML = text;
|
7
|
+
return textarea.value;
|
8
|
+
}
|
9
|
+
|
10
|
+
// Get initial query value from a data attribute to avoid string escaping issues
|
11
|
+
const initialQueryEncoded = document
|
12
|
+
.getElementById("monaco-editor")
|
13
|
+
.getAttribute("data-initial-query");
|
14
|
+
const initialQuery = decodeHTMLEntities(initialQueryEncoded);
|
15
|
+
|
16
|
+
// Determine initial theme based on document theme
|
17
|
+
const initialTheme =
|
18
|
+
document.documentElement.getAttribute("data-bs-theme") === "dark"
|
19
|
+
? "vs-dark"
|
20
|
+
: "vs";
|
21
|
+
|
22
|
+
// Initialize Monaco Editor with SQL syntax highlighting
|
23
|
+
const editor = monaco.editor.create(document.getElementById("monaco-editor"), {
|
24
|
+
value: initialQuery || "",
|
25
|
+
language: "sql",
|
26
|
+
theme: initialTheme,
|
27
|
+
automaticLayout: true, // Resize automatically
|
28
|
+
minimap: { enabled: true },
|
29
|
+
scrollBeyondLastLine: false,
|
30
|
+
lineNumbers: "on",
|
31
|
+
renderLineHighlight: "all",
|
32
|
+
tabSize: 2,
|
33
|
+
wordWrap: "on",
|
34
|
+
formatOnPaste: true,
|
35
|
+
formatOnType: true,
|
36
|
+
autoIndent: "full",
|
37
|
+
folding: true,
|
38
|
+
glyphMargin: false,
|
39
|
+
suggestOnTriggerCharacters: true,
|
40
|
+
fixedOverflowWidgets: true,
|
41
|
+
quickSuggestions: {
|
42
|
+
other: true,
|
43
|
+
comments: true,
|
44
|
+
strings: true,
|
45
|
+
},
|
46
|
+
suggest: {
|
47
|
+
showKeywords: true,
|
48
|
+
showSnippets: true,
|
49
|
+
preview: true,
|
50
|
+
showIcons: true,
|
51
|
+
maxVisibleSuggestions: 12,
|
52
|
+
},
|
53
|
+
});
|
54
|
+
|
55
|
+
// Theme change listener
|
56
|
+
document.addEventListener("dbviewerThemeChanged", (event) => {
|
57
|
+
const newTheme = event.detail.theme === "dark" ? "vs-dark" : "vs";
|
58
|
+
monaco.editor.setTheme(newTheme);
|
59
|
+
|
60
|
+
// Update editor container border color
|
61
|
+
const editorContainer = document.querySelector(".monaco-editor-container");
|
62
|
+
if (editorContainer) {
|
63
|
+
editorContainer.style.borderColor =
|
64
|
+
event.detail.theme === "dark" ? "#495057" : "#ced4da";
|
65
|
+
}
|
66
|
+
|
67
|
+
// Update status bar styling based on theme
|
68
|
+
updateStatusBarTheme(event.detail.theme);
|
69
|
+
|
70
|
+
// Update example query buttons
|
71
|
+
const exampleQueries = document.querySelectorAll(".example-query");
|
72
|
+
exampleQueries.forEach((query) => {
|
73
|
+
if (event.detail.theme === "dark") {
|
74
|
+
query.style.borderColor = "#495057";
|
75
|
+
if (!query.classList.contains("btn-primary")) {
|
76
|
+
query.style.color = "#f8f9fa";
|
77
|
+
}
|
78
|
+
} else {
|
79
|
+
query.style.borderColor = "#ced4da";
|
80
|
+
if (!query.classList.contains("btn-primary")) {
|
81
|
+
query.style.color = "";
|
82
|
+
}
|
83
|
+
}
|
84
|
+
});
|
85
|
+
});
|
86
|
+
|
87
|
+
function updateStatusBarTheme(theme) {
|
88
|
+
const statusBar = document.querySelector(".monaco-status-bar");
|
89
|
+
if (!statusBar) return;
|
90
|
+
|
91
|
+
if (theme === "dark") {
|
92
|
+
statusBar.style.backgroundColor = "#343a40";
|
93
|
+
statusBar.style.borderColor = "#495057";
|
94
|
+
statusBar.style.color = "#adb5bd";
|
95
|
+
} else {
|
96
|
+
statusBar.style.backgroundColor = "#f8f9fa";
|
97
|
+
statusBar.style.borderColor = "#ced4da";
|
98
|
+
statusBar.style.color = "#6c757d";
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
// Set up SQL intellisense with table/column completions
|
103
|
+
const tableName = document.getElementById("table_name").value;
|
104
|
+
const columns = JSON.parse(document.getElementById("columns_data").value);
|
105
|
+
|
106
|
+
// Register SQL completion providers
|
107
|
+
monaco.languages.registerCompletionItemProvider("sql", {
|
108
|
+
provideCompletionItems: function (model, position) {
|
109
|
+
const textUntilPosition = model.getValueInRange({
|
110
|
+
startLineNumber: position.lineNumber,
|
111
|
+
startColumn: 1,
|
112
|
+
endLineNumber: position.lineNumber,
|
113
|
+
endColumn: position.column,
|
114
|
+
});
|
115
|
+
|
116
|
+
const suggestions = [];
|
117
|
+
|
118
|
+
// Add table name suggestion
|
119
|
+
suggestions.push({
|
120
|
+
label: tableName,
|
121
|
+
kind: monaco.languages.CompletionItemKind.Class,
|
122
|
+
insertText: tableName,
|
123
|
+
detail: "Table name",
|
124
|
+
});
|
125
|
+
|
126
|
+
// Add column name suggestions
|
127
|
+
columns.forEach((col) => {
|
128
|
+
suggestions.push({
|
129
|
+
label: col.name,
|
130
|
+
kind: monaco.languages.CompletionItemKind.Field,
|
131
|
+
insertText: col.name,
|
132
|
+
detail: `Column (${col.type})`,
|
133
|
+
});
|
134
|
+
});
|
135
|
+
|
136
|
+
// Add common SQL keywords
|
137
|
+
const keywords = [
|
138
|
+
{ label: "SELECT", insertText: "SELECT " },
|
139
|
+
{ label: "FROM", insertText: "FROM " },
|
140
|
+
{ label: "WHERE", insertText: "WHERE " },
|
141
|
+
{ label: "ORDER BY", insertText: "ORDER BY " },
|
142
|
+
{ label: "GROUP BY", insertText: "GROUP BY " },
|
143
|
+
{ label: "HAVING", insertText: "HAVING " },
|
144
|
+
{ label: "LIMIT", insertText: "LIMIT " },
|
145
|
+
{ label: "JOIN", insertText: "JOIN " },
|
146
|
+
{ label: "LEFT JOIN", insertText: "LEFT JOIN " },
|
147
|
+
{ label: "INNER JOIN", insertText: "INNER JOIN " },
|
148
|
+
];
|
149
|
+
|
150
|
+
keywords.forEach((kw) => {
|
151
|
+
suggestions.push({
|
152
|
+
label: kw.label,
|
153
|
+
kind: monaco.languages.CompletionItemKind.Keyword,
|
154
|
+
insertText: kw.insertText,
|
155
|
+
});
|
156
|
+
});
|
157
|
+
|
158
|
+
return { suggestions };
|
159
|
+
},
|
160
|
+
});
|
161
|
+
|
162
|
+
// Handle form submission - transfer content to hidden input before submitting
|
163
|
+
document
|
164
|
+
.getElementById("sql-query-form")
|
165
|
+
.addEventListener("submit", function (event) {
|
166
|
+
// Stop the form from submitting immediately
|
167
|
+
event.preventDefault();
|
168
|
+
|
169
|
+
// Get the query value from the editor and set it to the hidden input
|
170
|
+
const queryValue = editor.getValue();
|
171
|
+
document.getElementById("query-input").value = queryValue;
|
172
|
+
|
173
|
+
// Now manually submit the form
|
174
|
+
this.submit();
|
175
|
+
});
|
176
|
+
|
177
|
+
// Make example queries clickable
|
178
|
+
document.querySelectorAll(".example-query").forEach((example) => {
|
179
|
+
example.style.cursor = "pointer";
|
180
|
+
example.addEventListener("click", () => {
|
181
|
+
const query = decodeHTMLEntities(example.textContent);
|
182
|
+
editor.setValue(query);
|
183
|
+
editor.focus();
|
184
|
+
});
|
185
|
+
});
|
186
|
+
|
187
|
+
// Setup editor keybindings
|
188
|
+
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () {
|
189
|
+
// Get the query value from the editor and set it to the hidden input
|
190
|
+
const queryValue = editor.getValue();
|
191
|
+
document.getElementById("query-input").value = queryValue;
|
192
|
+
|
193
|
+
// Submit the form
|
194
|
+
document.getElementById("sql-query-form").submit();
|
195
|
+
});
|
196
|
+
|
197
|
+
// Add keyboard shortcuts for common SQL statements
|
198
|
+
editor.addAction({
|
199
|
+
id: "insert-select-all",
|
200
|
+
label: "Insert SELECT * FROM statement",
|
201
|
+
keybindings: [
|
202
|
+
monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyS,
|
203
|
+
],
|
204
|
+
run: function () {
|
205
|
+
editor.trigger("keyboard", "type", {
|
206
|
+
text: `SELECT * FROM ${tableName} LIMIT 100`,
|
207
|
+
});
|
208
|
+
return null;
|
209
|
+
},
|
210
|
+
});
|
211
|
+
|
212
|
+
editor.addAction({
|
213
|
+
id: "insert-where",
|
214
|
+
label: "Insert WHERE clause",
|
215
|
+
keybindings: [
|
216
|
+
monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyW,
|
217
|
+
],
|
218
|
+
run: function () {
|
219
|
+
editor.trigger("keyboard", "type", { text: " WHERE " });
|
220
|
+
return null;
|
221
|
+
},
|
222
|
+
});
|
223
|
+
|
224
|
+
editor.addAction({
|
225
|
+
id: "toggle-table-structure",
|
226
|
+
label: "Toggle Table Structure Reference",
|
227
|
+
keybindings: [
|
228
|
+
monaco.KeyMod.CtrlCmd | monaco.KeyMod.Alt | monaco.KeyCode.KeyT,
|
229
|
+
],
|
230
|
+
run: function () {
|
231
|
+
// Use Bootstrap's collapse API to toggle
|
232
|
+
bootstrap.Collapse.getOrCreateInstance(
|
233
|
+
document.getElementById("tableStructureContent")
|
234
|
+
).toggle();
|
235
|
+
return null;
|
236
|
+
},
|
237
|
+
});
|
238
|
+
|
239
|
+
// Create a status bar showing cursor position and columns info
|
240
|
+
const statusBarDiv = document.createElement("div");
|
241
|
+
statusBarDiv.className = "monaco-status-bar";
|
242
|
+
statusBarDiv.innerHTML = `<div class="status-info">Ready</div>
|
243
|
+
<div class="column-info">Table: ${tableName} (${columns.length} columns)</div>`;
|
244
|
+
document.getElementById("monaco-editor").after(statusBarDiv);
|
245
|
+
|
246
|
+
// Apply initial theme to status bar
|
247
|
+
const currentTheme =
|
248
|
+
document.documentElement.getAttribute("data-bs-theme") || "light";
|
249
|
+
updateStatusBarTheme(currentTheme);
|
250
|
+
|
251
|
+
// Update status bar with cursor position
|
252
|
+
editor.onDidChangeCursorPosition((e) => {
|
253
|
+
const position = `Ln ${e.position.lineNumber}, Col ${e.position.column}`;
|
254
|
+
statusBarDiv.querySelector(".status-info").textContent = position;
|
255
|
+
});
|
256
|
+
|
257
|
+
// Focus the editor when page loads
|
258
|
+
window.addEventListener("load", () => {
|
259
|
+
editor.focus();
|
260
|
+
});
|
261
|
+
|
262
|
+
// Toggle icon when table structure collapses or expands
|
263
|
+
document
|
264
|
+
.getElementById("tableStructureContent")
|
265
|
+
.addEventListener("show.bs.collapse", function () {
|
266
|
+
document
|
267
|
+
.querySelector("#tableStructureHeader button i")
|
268
|
+
.classList.replace("bi-chevron-down", "bi-chevron-up");
|
269
|
+
});
|
270
|
+
|
271
|
+
document
|
272
|
+
.getElementById("tableStructureContent")
|
273
|
+
.addEventListener("hide.bs.collapse", function () {
|
274
|
+
document
|
275
|
+
.querySelector("#tableStructureHeader button i")
|
276
|
+
.classList.replace("bi-chevron-up", "bi-chevron-down");
|
277
|
+
});
|