sqlite-hub 0.6.0 → 0.8.7
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.
- package/README.md +107 -0
- package/bin/sqlite-hub.js +285 -4
- package/changelog.md +21 -0
- package/docs/DESIGN_GUIDELINES.md +36 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/index.html +80 -0
- package/frontend/js/api.js +34 -0
- package/frontend/js/app.js +188 -10
- package/frontend/js/components/connectionCard.js +3 -4
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/modal.js +341 -24
- package/frontend/js/components/queryChartRenderer.js +125 -0
- package/frontend/js/components/queryEditor.js +33 -6
- package/frontend/js/components/queryHistoryDetail.js +16 -7
- package/frontend/js/components/queryHistoryPanel.js +18 -7
- package/frontend/js/components/rowEditorPanel.js +59 -54
- package/frontend/js/components/sidebar.js +32 -32
- package/frontend/js/components/structureGraph.js +54 -122
- package/frontend/js/components/tableDesignerEditor.js +9 -8
- package/frontend/js/components/tableDesignerSidebar.js +52 -48
- package/frontend/js/components/tableDesignerSqlPreview.js +60 -28
- package/frontend/js/lib/queryChartOptions.js +283 -0
- package/frontend/js/lib/queryCharts.js +560 -0
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +641 -0
- package/frontend/js/views/charts.js +499 -0
- package/frontend/js/views/connections.js +5 -17
- package/frontend/js/views/data.js +40 -27
- package/frontend/js/views/editor.js +19 -13
- package/frontend/js/views/overview.js +149 -10
- package/frontend/js/views/settings.js +2 -2
- package/frontend/js/views/structure.js +105 -59
- package/frontend/js/views/tableDesigner.js +7 -2
- package/frontend/styles/base.css +62 -0
- package/frontend/styles/components.css +68 -118
- package/frontend/styles/structure-graph.css +19 -82
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +293 -66
- package/package.json +2 -1
- package/server/routes/charts.js +153 -0
- package/server/server.js +6 -0
- package/server/services/sqlite/overviewService.js +68 -0
- package/server/services/storage/appStateStore.js +400 -1
- package/server/services/storage/queryHistoryChartUtils.js +145 -0
|
@@ -52,7 +52,10 @@ export function renderQueryEditor({
|
|
|
52
52
|
exporting = false,
|
|
53
53
|
historyLoading = false,
|
|
54
54
|
historyTotal = 0,
|
|
55
|
+
editorVisible = true,
|
|
56
|
+
historyVisible = true,
|
|
55
57
|
}) {
|
|
58
|
+
const secondaryButtonClass = "standard-button";
|
|
56
59
|
const left = `
|
|
57
60
|
<div class="flex items-center gap-2 bg-surface-container-lowest px-3 py-1">
|
|
58
61
|
<span class="material-symbols-outlined text-xs text-[#FCE300]">database</span>
|
|
@@ -68,21 +71,39 @@ export function renderQueryEditor({
|
|
|
68
71
|
|
|
69
72
|
const right = `
|
|
70
73
|
<button
|
|
71
|
-
class="
|
|
74
|
+
class="${secondaryButtonClass}"
|
|
75
|
+
data-action="toggle-editor-panel"
|
|
76
|
+
data-next-value="${editorVisible ? "false" : "true"}"
|
|
77
|
+
type="button"
|
|
78
|
+
>
|
|
79
|
+
<span class="material-symbols-outlined text-sm">${editorVisible ? "keyboard_arrow_down" : "terminal"}</span>
|
|
80
|
+
${editorVisible ? "Hide Editor" : "Show Editor"}
|
|
81
|
+
</button>
|
|
82
|
+
<button
|
|
83
|
+
class="${secondaryButtonClass}"
|
|
84
|
+
data-action="toggle-query-history-panel"
|
|
85
|
+
data-next-value="${historyVisible ? "false" : "true"}"
|
|
86
|
+
type="button"
|
|
87
|
+
>
|
|
88
|
+
<span class="material-symbols-outlined text-sm">${historyVisible ? "visibility_off" : "history"}</span>
|
|
89
|
+
${historyVisible ? "Hide History" : "Show History"}
|
|
90
|
+
</button>
|
|
91
|
+
<button
|
|
92
|
+
class="${secondaryButtonClass}"
|
|
72
93
|
data-action="clear-query"
|
|
73
94
|
type="button"
|
|
74
95
|
>
|
|
75
96
|
Clear
|
|
76
97
|
</button>
|
|
77
98
|
<button
|
|
78
|
-
class="
|
|
99
|
+
class="${secondaryButtonClass}"
|
|
79
100
|
data-action="export-query-csv"
|
|
80
101
|
type="button"
|
|
81
102
|
>
|
|
82
103
|
${exporting ? "Exporting..." : "Export CSV"}
|
|
83
104
|
</button>
|
|
84
105
|
<button
|
|
85
|
-
class="
|
|
106
|
+
class="signature-button"
|
|
86
107
|
data-action="execute-query"
|
|
87
108
|
type="button"
|
|
88
109
|
>
|
|
@@ -99,9 +120,15 @@ export function renderQueryEditor({
|
|
|
99
120
|
className: "flex-wrap",
|
|
100
121
|
})}
|
|
101
122
|
</div>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
${
|
|
124
|
+
editorVisible
|
|
125
|
+
? `
|
|
126
|
+
<div class="flex min-h-0 flex-1 flex-col">
|
|
127
|
+
${renderEditorSurface({ query })}
|
|
128
|
+
</div>
|
|
129
|
+
`
|
|
130
|
+
: ""
|
|
131
|
+
}
|
|
105
132
|
</div>
|
|
106
133
|
`;
|
|
107
134
|
}
|
|
@@ -155,14 +155,14 @@ export function renderQueryHistoryDetail({
|
|
|
155
155
|
</label>
|
|
156
156
|
<div class="mt-2 flex gap-2">
|
|
157
157
|
<input
|
|
158
|
-
class="flex-1 border border-outline-variant/20 bg-surface-container
|
|
158
|
+
class="control-input flex-1 border border-outline-variant/20 bg-surface-container text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
|
|
159
159
|
name="title"
|
|
160
160
|
placeholder="${escapeHtml(item.displayTitle)}"
|
|
161
161
|
type="text"
|
|
162
162
|
value="${escapeHtml(item.title ?? "")}"
|
|
163
163
|
/>
|
|
164
164
|
<button
|
|
165
|
-
class="
|
|
165
|
+
class="standard-button"
|
|
166
166
|
type="submit"
|
|
167
167
|
>
|
|
168
168
|
Save
|
|
@@ -175,7 +175,7 @@ export function renderQueryHistoryDetail({
|
|
|
175
175
|
|
|
176
176
|
<div class="mt-5 flex flex-wrap gap-2">
|
|
177
177
|
<button
|
|
178
|
-
class="
|
|
178
|
+
class="standard-button"
|
|
179
179
|
data-action="open-query-history"
|
|
180
180
|
data-history-id="${escapeHtml(item.id)}"
|
|
181
181
|
type="button"
|
|
@@ -183,7 +183,16 @@ export function renderQueryHistoryDetail({
|
|
|
183
183
|
Open In Editor
|
|
184
184
|
</button>
|
|
185
185
|
<button
|
|
186
|
-
class="
|
|
186
|
+
class="standard-button"
|
|
187
|
+
data-action="navigate"
|
|
188
|
+
data-to="/charts/${encodeURIComponent(item.id)}"
|
|
189
|
+
type="button"
|
|
190
|
+
>
|
|
191
|
+
<span class="material-symbols-outlined text-sm">bar_chart</span>
|
|
192
|
+
Open In Charts
|
|
193
|
+
</button>
|
|
194
|
+
<button
|
|
195
|
+
class="standard-button"
|
|
187
196
|
data-action="run-query-history"
|
|
188
197
|
data-history-id="${escapeHtml(item.id)}"
|
|
189
198
|
type="button"
|
|
@@ -191,7 +200,7 @@ export function renderQueryHistoryDetail({
|
|
|
191
200
|
Run Now
|
|
192
201
|
</button>
|
|
193
202
|
<button
|
|
194
|
-
class="
|
|
203
|
+
class="standard-button"
|
|
195
204
|
data-action="toggle-query-history-saved"
|
|
196
205
|
data-history-id="${escapeHtml(item.id)}"
|
|
197
206
|
data-next-value="${item.isSaved ? "false" : "true"}"
|
|
@@ -200,7 +209,7 @@ export function renderQueryHistoryDetail({
|
|
|
200
209
|
${item.isSaved ? "Unsave" : "Save"}
|
|
201
210
|
</button>
|
|
202
211
|
<button
|
|
203
|
-
class="
|
|
212
|
+
class="delete-button"
|
|
204
213
|
data-action="delete-query-history"
|
|
205
214
|
data-history-id="${escapeHtml(item.id)}"
|
|
206
215
|
type="button"
|
|
@@ -228,7 +237,7 @@ export function renderQueryHistoryDetail({
|
|
|
228
237
|
)}</textarea>
|
|
229
238
|
<div class="mt-2 flex justify-end">
|
|
230
239
|
<button
|
|
231
|
-
class="
|
|
240
|
+
class="standard-button"
|
|
232
241
|
type="submit"
|
|
233
242
|
>
|
|
234
243
|
Save Notes
|
|
@@ -150,17 +150,28 @@ export function renderQueryHistoryPanel({
|
|
|
150
150
|
return `
|
|
151
151
|
<aside class="query-history-panel border-l border-outline-variant/10 bg-surface-container-lowest">
|
|
152
152
|
<div class="border-b border-outline-variant/10 px-4 py-4">
|
|
153
|
-
<div class="flex items-center gap-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
<div class="flex items-center justify-between gap-3">
|
|
154
|
+
<div class="flex items-center gap-2">
|
|
155
|
+
<span class="material-symbols-outlined text-[18px] text-primary-container">history</span>
|
|
156
|
+
<span class="font-headline text-xs font-black uppercase tracking-[0.18em] text-primary-container">
|
|
157
|
+
Query History
|
|
158
|
+
</span>
|
|
159
|
+
</div>
|
|
160
|
+
<button
|
|
161
|
+
class="query-history-icon-button"
|
|
162
|
+
data-action="toggle-query-history-panel"
|
|
163
|
+
data-next-value="false"
|
|
164
|
+
title="Hide query history"
|
|
165
|
+
type="button"
|
|
166
|
+
>
|
|
167
|
+
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
168
|
+
</button>
|
|
158
169
|
</div>
|
|
159
170
|
<div class="mt-4">${renderQueryHistoryTabs(activeTab, total)}</div>
|
|
160
171
|
<label class="mt-4 block">
|
|
161
172
|
<span class="sr-only">Search query history</span>
|
|
162
173
|
<input
|
|
163
|
-
class="w-full border border-outline-variant/20 bg-surface-container
|
|
174
|
+
class="control-input w-full border border-outline-variant/20 bg-surface-container text-sm text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container"
|
|
164
175
|
data-bind="query-history-search"
|
|
165
176
|
placeholder="Search SQL, titles, notes..."
|
|
166
177
|
type="search"
|
|
@@ -212,7 +223,7 @@ export function renderQueryHistoryPanel({
|
|
|
212
223
|
? `
|
|
213
224
|
<div class="mt-4 flex justify-center">
|
|
214
225
|
<button
|
|
215
|
-
class="
|
|
226
|
+
class="standard-button"
|
|
216
227
|
data-action="load-more-query-history"
|
|
217
228
|
type="button"
|
|
218
229
|
>
|
|
@@ -106,12 +106,52 @@ export function renderRowEditorPanel({
|
|
|
106
106
|
const canSubmit = !disabledMessage && editableFields.length > 0;
|
|
107
107
|
const canDelete = !disabledMessage && deleteEnabled;
|
|
108
108
|
const formId = `${formName}-panel-form`;
|
|
109
|
+
const headerActions = [
|
|
110
|
+
reloadAction
|
|
111
|
+
? `
|
|
112
|
+
<button
|
|
113
|
+
class="standard-button"
|
|
114
|
+
data-action="${escapeHtml(reloadAction)}"
|
|
115
|
+
type="button"
|
|
116
|
+
>
|
|
117
|
+
Reload
|
|
118
|
+
</button>
|
|
119
|
+
`
|
|
120
|
+
: "",
|
|
121
|
+
canSubmit
|
|
122
|
+
? `
|
|
123
|
+
<button
|
|
124
|
+
class="standard-button"
|
|
125
|
+
form="${escapeHtml(formId)}"
|
|
126
|
+
type="submit"
|
|
127
|
+
${saving || deleting ? "disabled" : ""}
|
|
128
|
+
>
|
|
129
|
+
${escapeHtml(saving ? "Saving..." : submitLabel)}
|
|
130
|
+
</button>
|
|
131
|
+
`
|
|
132
|
+
: "",
|
|
133
|
+
canDelete
|
|
134
|
+
? `
|
|
135
|
+
<button
|
|
136
|
+
class="delete-button"
|
|
137
|
+
data-action="${escapeHtml(deleteAction)}"
|
|
138
|
+
data-row-index="${escapeHtml(String(deleteRowIndex ?? ""))}"
|
|
139
|
+
type="button"
|
|
140
|
+
${saving || deleting ? "disabled" : ""}
|
|
141
|
+
>
|
|
142
|
+
${escapeHtml(deleting ? "Deleting..." : deleteLabel)}
|
|
143
|
+
</button>
|
|
144
|
+
`
|
|
145
|
+
: "",
|
|
146
|
+
]
|
|
147
|
+
.filter(Boolean)
|
|
148
|
+
.join("");
|
|
109
149
|
|
|
110
150
|
return `
|
|
111
151
|
<section class="flex h-full min-h-0 flex-col bg-surface-low">
|
|
112
152
|
<header class="border-b border-outline-variant/10 bg-surface-container px-6 py-5">
|
|
113
|
-
<div class="
|
|
114
|
-
<div>
|
|
153
|
+
<div class="flex items-start justify-between gap-4">
|
|
154
|
+
<div class="min-w-0 flex-1">
|
|
115
155
|
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
|
|
116
156
|
${escapeHtml(sectionLabel)}
|
|
117
157
|
</div>
|
|
@@ -128,59 +168,24 @@ export function renderRowEditorPanel({
|
|
|
128
168
|
: ""
|
|
129
169
|
}
|
|
130
170
|
</div>
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
>
|
|
140
|
-
Reload
|
|
141
|
-
</button>
|
|
142
|
-
`
|
|
143
|
-
: ""
|
|
144
|
-
}
|
|
145
|
-
${
|
|
146
|
-
canSubmit
|
|
147
|
-
? `
|
|
148
|
-
<button
|
|
149
|
-
class="bg-primary-container px-5 py-3 text-[10px] font-black uppercase tracking-[0.16em] text-on-primary disabled:cursor-default disabled:opacity-40"
|
|
150
|
-
form="${escapeHtml(formId)}"
|
|
151
|
-
type="submit"
|
|
152
|
-
${saving || deleting ? "disabled" : ""}
|
|
153
|
-
>
|
|
154
|
-
${escapeHtml(saving ? "Saving..." : submitLabel)}
|
|
155
|
-
</button>
|
|
156
|
-
`
|
|
157
|
-
: ""
|
|
158
|
-
}
|
|
159
|
-
${
|
|
160
|
-
canDelete
|
|
161
|
-
? `
|
|
162
|
-
<button
|
|
163
|
-
class="border border-error/25 bg-error-container/10 px-4 py-3 text-[10px] font-bold uppercase tracking-[0.16em] text-error transition-colors hover:bg-error-container/20 disabled:cursor-default disabled:opacity-40"
|
|
164
|
-
data-action="${escapeHtml(deleteAction)}"
|
|
165
|
-
data-row-index="${escapeHtml(String(deleteRowIndex ?? ""))}"
|
|
166
|
-
type="button"
|
|
167
|
-
${saving || deleting ? "disabled" : ""}
|
|
168
|
-
>
|
|
169
|
-
${escapeHtml(deleting ? "Deleting..." : deleteLabel)}
|
|
170
|
-
</button>
|
|
171
|
-
`
|
|
172
|
-
: ""
|
|
173
|
-
}
|
|
174
|
-
<button
|
|
175
|
-
aria-label="Close panel"
|
|
176
|
-
class="flex h-11 w-11 items-center justify-center border border-outline-variant/20 text-on-surface hover:bg-surface-container-highest"
|
|
177
|
-
data-action="${escapeHtml(closeAction)}"
|
|
178
|
-
type="button"
|
|
179
|
-
>
|
|
180
|
-
<span class="material-symbols-outlined text-base">close</span>
|
|
181
|
-
</button>
|
|
182
|
-
</div>
|
|
171
|
+
<button
|
|
172
|
+
aria-label="Close panel"
|
|
173
|
+
class="query-history-icon-button shrink-0"
|
|
174
|
+
data-action="${escapeHtml(closeAction)}"
|
|
175
|
+
type="button"
|
|
176
|
+
>
|
|
177
|
+
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
178
|
+
</button>
|
|
183
179
|
</div>
|
|
180
|
+
${
|
|
181
|
+
headerActions
|
|
182
|
+
? `
|
|
183
|
+
<div class="mt-4 flex flex-wrap items-center justify-end gap-2">
|
|
184
|
+
${headerActions}
|
|
185
|
+
</div>
|
|
186
|
+
`
|
|
187
|
+
: ""
|
|
188
|
+
}
|
|
184
189
|
</header>
|
|
185
190
|
<div class="custom-scrollbar flex-1 overflow-auto px-6 py-6">
|
|
186
191
|
${
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import { escapeHtml } from
|
|
2
|
-
import { renderConnectionLogo } from
|
|
1
|
+
import { escapeHtml } from '../utils/format.js';
|
|
2
|
+
import { renderConnectionLogo } from './connectionLogo.js';
|
|
3
3
|
|
|
4
4
|
const sidebarItems = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
{ label: 'Connections', href: '#/connections', key: 'connections', icon: 'database' },
|
|
6
|
+
{ label: 'Overview', href: '#/overview', key: 'overview', icon: 'dashboard' },
|
|
7
|
+
{ label: 'Data', href: '#/data', key: 'data', icon: 'table_rows' },
|
|
8
|
+
{ label: 'SQL Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
|
|
9
|
+
{ label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
|
|
10
|
+
{ label: 'Charts', href: '#/charts', key: 'charts', icon: 'bar_chart' },
|
|
11
|
+
{ label: 'Table Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
|
|
12
|
+
{ label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },
|
|
12
13
|
];
|
|
13
14
|
|
|
14
15
|
function getActiveSidebarKey(routeName) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
if (routeName === 'landing') {
|
|
17
|
+
return 'connections';
|
|
18
|
+
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if (routeName === 'editorResults') {
|
|
21
|
+
return 'editor';
|
|
22
|
+
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
return routeName;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export function renderSidebar(state) {
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
const activeKey = getActiveSidebarKey(state.route.name);
|
|
29
|
+
const activeConnection = state.connections.active;
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
return `
|
|
31
32
|
<nav class="sidebar-links">
|
|
32
33
|
${sidebarItems
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<a class="sidebar-link ${item.key === activeKey ?
|
|
34
|
+
.map(
|
|
35
|
+
item => `
|
|
36
|
+
<a class="sidebar-link ${item.key === activeKey ? 'is-active' : ''}" href="${item.href}">
|
|
36
37
|
<span class="material-symbols-outlined">${item.icon}</span>
|
|
37
38
|
<span>${item.label}</span>
|
|
38
39
|
</a>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
`,
|
|
41
|
+
)
|
|
42
|
+
.join('')}
|
|
42
43
|
</nav>
|
|
43
44
|
<div class="sidebar-footer">
|
|
44
45
|
<div class="sidebar-footer-card">
|
|
45
46
|
${renderConnectionLogo(activeConnection, {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
icon: "memory",
|
|
47
|
+
containerClass: 'sidebar-footer-mark overflow-hidden bg-primary-container text-on-primary',
|
|
48
|
+
imageClassName: 'h-full w-full object-cover',
|
|
49
|
+
iconClassName: 'text-[15px]',
|
|
50
|
+
icon: 'memory',
|
|
51
51
|
})}
|
|
52
52
|
<div class="min-w-0">
|
|
53
53
|
<p class="truncate text-[10px] font-bold text-on-surface">
|
|
54
|
-
${escapeHtml(activeConnection?.label ??
|
|
54
|
+
${escapeHtml(activeConnection?.label ?? 'NO_ACTIVE_DATABASE')}
|
|
55
55
|
</p>
|
|
56
56
|
<p class="text-[8px] text-on-surface-variant/60">
|
|
57
|
-
${activeConnection?.readOnly ?
|
|
57
|
+
${activeConnection?.readOnly ? 'READ_ONLY' : 'READ_WRITE'}
|
|
58
58
|
</p>
|
|
59
59
|
</div>
|
|
60
60
|
</div>
|