sqlite-hub 0.9.3 → 0.9.4
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/.github/workflows/ci.yml +36 -0
- package/README.md +2 -2
- package/bin/sqlite-hub.js +1 -1
- package/frontend/index.html +2 -158
- package/frontend/js/app.js +5 -1
- package/frontend/js/components/connectionCard.js +62 -87
- package/frontend/js/components/emptyState.js +20 -23
- package/frontend/js/components/modal.js +145 -195
- package/frontend/js/components/pageHeader.js +1 -1
- package/frontend/js/components/queryEditor.js +16 -30
- package/frontend/js/components/queryHistoryDetail.js +93 -164
- package/frontend/js/components/queryHistoryPanel.js +81 -99
- package/frontend/js/components/queryResults.js +3 -1
- package/frontend/js/components/rowEditorPanel.js +28 -31
- package/frontend/js/components/tableDesignerEditor.js +91 -116
- package/frontend/js/store.js +39 -3
- package/frontend/js/utils/tableDesigner.js +8 -2
- package/frontend/js/views/charts.js +23 -43
- package/frontend/js/views/data.js +116 -132
- package/frontend/js/views/mediaTagging.js +131 -164
- package/frontend/js/views/structure.js +52 -48
- package/frontend/styles/tailwind.css +81 -0
- package/frontend/styles/tailwind.generated.css +1 -0
- package/frontend/styles/tokens.css +3 -3
- package/package.json +16 -3
- package/server/routes/mediaTagging.js +2 -10
- package/server/routes/sql.js +35 -10
- package/server/server.js +24 -0
- package/server/services/sqlite/dataBrowserService.js +25 -5
- package/server/services/sqlite/exportService.js +4 -2
- package/server/services/sqlite/introspection.js +2 -2
- package/server/services/sqlite/mediaTaggingService.js +166 -53
- package/server/services/sqlite/structureService.js +2 -2
- package/server/services/sqlite/tableDesigner/sql.js +19 -3
- package/server/services/storage/appStateStore.js +227 -87
- package/server/utils/appPaths.js +55 -19
- package/server/utils/fileValidation.js +94 -8
- package/tailwind.config.cjs +73 -0
- package/tests/security-paths.test.js +84 -0
- package/tests/sql-identifier-safety.test.js +66 -0
- package/.npmingnore +0 -4
- package/changelog.md +0 -84
- package/docs/DESIGN_GUIDELINES.md +0 -36
- package/scripts/publish_brew.sh +0 -466
- package/scripts/publish_npm.sh +0 -241
- package/shortkeys.md +0 -5
|
@@ -126,169 +126,98 @@ export function renderQueryHistoryDetail({
|
|
|
126
126
|
`;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<div class="custom-scrollbar min-h-0 flex-1 overflow-auto px-5 py-5">
|
|
157
|
-
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
158
|
-
${renderDetailMetaItem("Last Used", formatDateTime(item.lastUsedAt))}
|
|
159
|
-
${renderDetailMetaItem("First Executed", formatDateTime(item.firstExecutedAt))}
|
|
160
|
-
${renderDetailMetaItem("Use Count", formatNumber(item.useCount))}
|
|
161
|
-
${renderDetailMetaItem(
|
|
162
|
-
"Tables",
|
|
163
|
-
item.tablesDetected?.length ? item.tablesDetected.join(", ") : "None detected"
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
129
|
+
const chartButton = canOpenQueryHistoryInCharts(item)
|
|
130
|
+
? [
|
|
131
|
+
'<button class="standard-button" data-action="navigate" data-to="/charts/',
|
|
132
|
+
encodeURIComponent(item.id),
|
|
133
|
+
'" type="button"><span class="material-symbols-outlined text-sm">bar_chart</span>Open In Charts</button>',
|
|
134
|
+
].join("")
|
|
135
|
+
: "";
|
|
136
|
+
const statusMarkup = [
|
|
137
|
+
renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType)),
|
|
138
|
+
item.isSaved ? renderStatusBadge("saved", "primary") : "",
|
|
139
|
+
item.isDestructive ? renderStatusBadge("destructive", "warning") : "",
|
|
140
|
+
item.lastRun
|
|
141
|
+
? renderStatusBadge(item.lastRun.status, item.lastRun.status === "error" ? "alert" : "success")
|
|
142
|
+
: "",
|
|
143
|
+
].join("");
|
|
144
|
+
const metaMarkup = [
|
|
145
|
+
renderDetailMetaItem("Last Used", formatDateTime(item.lastUsedAt)),
|
|
146
|
+
renderDetailMetaItem("First Executed", formatDateTime(item.firstExecutedAt)),
|
|
147
|
+
renderDetailMetaItem("Use Count", formatNumber(item.useCount)),
|
|
148
|
+
renderDetailMetaItem(
|
|
149
|
+
"Tables",
|
|
150
|
+
item.tablesDetected?.length ? item.tablesDetected.join(", ") : "None detected"
|
|
151
|
+
),
|
|
152
|
+
].join("");
|
|
153
|
+
const runsMarkup = runs.length
|
|
154
|
+
? runs.map((run) => renderRunItem(run)).join("")
|
|
155
|
+
: '<div class="border border-outline-variant/10 bg-surface-container px-3 py-4 text-sm text-on-surface-variant/65">No execution runs recorded yet.</div>';
|
|
166
156
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<button
|
|
234
|
-
class="delete-button"
|
|
235
|
-
data-action="open-delete-query-history-modal"
|
|
236
|
-
data-history-id="${escapeHtml(item.id)}"
|
|
237
|
-
type="button"
|
|
238
|
-
>
|
|
239
|
-
Delete
|
|
240
|
-
</button>
|
|
241
|
-
</div>
|
|
242
|
-
|
|
243
|
-
<div class="mt-6">
|
|
244
|
-
<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
245
|
-
SQL
|
|
246
|
-
</div>
|
|
247
|
-
<pre class="query-history-detail-sql custom-scrollbar mt-2 overflow-auto border border-outline-variant/10 bg-surface-container-lowest p-4 font-mono text-sm leading-6 text-on-surface"><code>${highlightSql(
|
|
248
|
-
item.rawSql
|
|
249
|
-
)}</code></pre>
|
|
250
|
-
</div>
|
|
251
|
-
|
|
252
|
-
<form class="mt-6" data-form="save-query-history-notes">
|
|
253
|
-
<input name="historyId" type="hidden" value="${escapeHtml(item.id)}" />
|
|
254
|
-
<label class="block text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
255
|
-
Notes
|
|
256
|
-
</label>
|
|
257
|
-
<textarea class="custom-scrollbar mt-2 min-h-[120px] w-full resize-y border border-outline-variant/20 bg-surface-container px-3 py-3 text-sm leading-6 text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container" name="notes" placeholder="Add context, caveats, or why this query matters...">${escapeHtml(
|
|
258
|
-
item.notes ?? ""
|
|
259
|
-
)}</textarea>
|
|
260
|
-
<div class="mt-2 flex justify-end">
|
|
261
|
-
<button
|
|
262
|
-
class="standard-button"
|
|
263
|
-
type="submit"
|
|
264
|
-
>
|
|
265
|
-
Save Notes
|
|
266
|
-
</button>
|
|
267
|
-
</div>
|
|
268
|
-
</form>
|
|
269
|
-
|
|
270
|
-
<div class="mt-6">
|
|
271
|
-
<div class="flex items-center justify-between gap-3">
|
|
272
|
-
<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
273
|
-
Latest Runs
|
|
274
|
-
</div>
|
|
275
|
-
<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/40">
|
|
276
|
-
${escapeHtml(formatNumber(runs.length))}
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
<div class="mt-3 space-y-3">
|
|
280
|
-
${
|
|
281
|
-
runs.length
|
|
282
|
-
? runs.map((run) => renderRunItem(run)).join("")
|
|
283
|
-
: `
|
|
284
|
-
<div class="border border-outline-variant/10 bg-surface-container px-3 py-4 text-sm text-on-surface-variant/65">
|
|
285
|
-
No execution runs recorded yet.
|
|
286
|
-
</div>
|
|
287
|
-
`
|
|
288
|
-
}
|
|
289
|
-
</div>
|
|
290
|
-
</div>
|
|
291
|
-
</div>
|
|
292
|
-
</section>
|
|
293
|
-
`;
|
|
157
|
+
return [
|
|
158
|
+
'<section class="flex h-full min-h-0 flex-col bg-surface-low">',
|
|
159
|
+
'<div class="border-b border-outline-variant/10 px-5 py-4">',
|
|
160
|
+
'<div class="flex items-center justify-between gap-3"><div>',
|
|
161
|
+
'<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">Query Detail</div>',
|
|
162
|
+
'<h2 class="mt-1 font-headline text-lg font-black uppercase tracking-tight text-on-surface">',
|
|
163
|
+
escapeHtml(item.displayTitle),
|
|
164
|
+
"</h2></div>",
|
|
165
|
+
'<button class="query-history-icon-button" data-action="clear-query-history-selection" type="button"><span class="material-symbols-outlined text-[18px]">close</span></button>',
|
|
166
|
+
'</div><div class="mt-4 flex flex-wrap gap-2">',
|
|
167
|
+
statusMarkup,
|
|
168
|
+
"</div></div>",
|
|
169
|
+
'<div class="custom-scrollbar min-h-0 flex-1 overflow-auto px-5 py-5">',
|
|
170
|
+
'<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">',
|
|
171
|
+
metaMarkup,
|
|
172
|
+
"</div>",
|
|
173
|
+
'<form class="mt-5" data-form="save-query-history-title">',
|
|
174
|
+
'<input name="historyId" type="hidden" value="',
|
|
175
|
+
escapeHtml(item.id),
|
|
176
|
+
'" />',
|
|
177
|
+
'<label class="block text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">Custom Title</label>',
|
|
178
|
+
'<div class="mt-2 flex gap-2"><input 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" name="title" placeholder="',
|
|
179
|
+
escapeHtml(item.displayTitle),
|
|
180
|
+
'" type="text" value="',
|
|
181
|
+
escapeHtml(item.title ?? ""),
|
|
182
|
+
'" /><button class="standard-button" type="submit">Save</button></div>',
|
|
183
|
+
'<p class="mt-2 text-xs text-on-surface-variant/60">Leave empty to fall back to the auto title generated from the query.</p>',
|
|
184
|
+
"</form>",
|
|
185
|
+
'<div class="mt-5 flex flex-wrap gap-2">',
|
|
186
|
+
'<button class="standard-button" data-action="open-query-history" data-history-id="',
|
|
187
|
+
escapeHtml(item.id),
|
|
188
|
+
'" type="button">Open In Editor</button>',
|
|
189
|
+
chartButton,
|
|
190
|
+
'<button class="standard-button" data-action="run-query-history" data-history-id="',
|
|
191
|
+
escapeHtml(item.id),
|
|
192
|
+
'" type="button">Run Now</button>',
|
|
193
|
+
'<button class="standard-button" data-action="toggle-query-history-saved" data-history-id="',
|
|
194
|
+
escapeHtml(item.id),
|
|
195
|
+
'" data-next-value="',
|
|
196
|
+
item.isSaved ? "false" : "true",
|
|
197
|
+
'" type="button">',
|
|
198
|
+
item.isSaved ? "Unsave" : "Save",
|
|
199
|
+
"</button>",
|
|
200
|
+
'<button class="delete-button" data-action="open-delete-query-history-modal" data-history-id="',
|
|
201
|
+
escapeHtml(item.id),
|
|
202
|
+
'" type="button">Delete</button></div>',
|
|
203
|
+
'<div class="mt-6"><div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">SQL</div>',
|
|
204
|
+
'<pre class="query-history-detail-sql custom-scrollbar mt-2 overflow-auto border border-outline-variant/10 bg-surface-container-lowest p-4 font-mono text-sm leading-6 text-on-surface"><code>',
|
|
205
|
+
highlightSql(item.rawSql),
|
|
206
|
+
"</code></pre></div>",
|
|
207
|
+
'<form class="mt-6" data-form="save-query-history-notes">',
|
|
208
|
+
'<input name="historyId" type="hidden" value="',
|
|
209
|
+
escapeHtml(item.id),
|
|
210
|
+
'" />',
|
|
211
|
+
'<label class="block text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">Notes</label>',
|
|
212
|
+
'<textarea class="custom-scrollbar mt-2 min-h-[120px] w-full resize-y border border-outline-variant/20 bg-surface-container px-3 py-3 text-sm leading-6 text-on-surface outline-none placeholder:text-on-surface-variant/35 focus:border-primary-container" name="notes" placeholder="Add context, caveats, or why this query matters...">',
|
|
213
|
+
escapeHtml(item.notes ?? ""),
|
|
214
|
+
'</textarea><div class="mt-2 flex justify-end"><button class="standard-button" type="submit">Save Notes</button></div></form>',
|
|
215
|
+
'<div class="mt-6"><div class="flex items-center justify-between gap-3">',
|
|
216
|
+
'<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">Latest Runs</div>',
|
|
217
|
+
'<div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/40">',
|
|
218
|
+
escapeHtml(formatNumber(runs.length)),
|
|
219
|
+
'</div></div><div class="mt-3 space-y-3">',
|
|
220
|
+
runsMarkup,
|
|
221
|
+
"</div></div></div></section>",
|
|
222
|
+
].join("");
|
|
294
223
|
}
|
|
@@ -20,85 +20,69 @@ export function renderQueryHistoryListItem(item, activeHistoryId, selectedHistor
|
|
|
20
20
|
const isActive = Number(activeHistoryId) === Number(item.id);
|
|
21
21
|
const isSelected = Number(selectedHistoryId) === Number(item.id);
|
|
22
22
|
const visibleTables = (item.tablesDetected ?? []).slice(0, 3);
|
|
23
|
+
const itemClasses = [
|
|
24
|
+
"query-history-item",
|
|
25
|
+
isActive ? "is-active" : "",
|
|
26
|
+
item.lastRun?.status === "error" ? "is-error" : "",
|
|
27
|
+
]
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.join(" ");
|
|
30
|
+
const tableMarkup = visibleTables
|
|
31
|
+
.map((tableName) =>
|
|
32
|
+
[
|
|
33
|
+
'<span class="border border-outline-variant/20 bg-surface-highest px-2 py-1">',
|
|
34
|
+
escapeHtml(tableName),
|
|
35
|
+
"</span>",
|
|
36
|
+
].join("")
|
|
37
|
+
)
|
|
38
|
+
.join("");
|
|
23
39
|
|
|
24
|
-
return
|
|
25
|
-
<article class="
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
data-action="run-query-history"
|
|
71
|
-
data-history-id="${escapeHtml(item.id)}"
|
|
72
|
-
title="Run query"
|
|
73
|
-
type="button"
|
|
74
|
-
>
|
|
75
|
-
<span class="material-symbols-outlined text-[18px]">play_arrow</span>
|
|
76
|
-
</button>
|
|
77
|
-
<button
|
|
78
|
-
class="query-history-icon-button ${item.isSaved ? "is-active" : ""}"
|
|
79
|
-
data-action="toggle-query-history-saved"
|
|
80
|
-
data-history-id="${escapeHtml(item.id)}"
|
|
81
|
-
data-next-value="${item.isSaved ? "false" : "true"}"
|
|
82
|
-
title="${item.isSaved ? "Remove from saved" : "Save query"}"
|
|
83
|
-
type="button"
|
|
84
|
-
>
|
|
85
|
-
<span class="material-symbols-outlined text-[18px]">
|
|
86
|
-
${item.isSaved ? "bookmark" : "bookmark_add"}
|
|
87
|
-
</span>
|
|
88
|
-
</button>
|
|
89
|
-
<button
|
|
90
|
-
class="query-history-icon-button ${isSelected ? "is-active" : ""}"
|
|
91
|
-
data-action="select-query-history-item"
|
|
92
|
-
data-history-id="${escapeHtml(item.id)}"
|
|
93
|
-
title="Open query detail"
|
|
94
|
-
type="button"
|
|
95
|
-
>
|
|
96
|
-
<span class="material-symbols-outlined text-[18px]">info</span>
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
</article>
|
|
101
|
-
`;
|
|
40
|
+
return [
|
|
41
|
+
'<article class="',
|
|
42
|
+
itemClasses,
|
|
43
|
+
'"><button class="query-history-item-hit ',
|
|
44
|
+
isActive ? "is-active" : "",
|
|
45
|
+
'" data-action="select-query-history-item" data-history-id="',
|
|
46
|
+
escapeHtml(item.id),
|
|
47
|
+
'" type="button">',
|
|
48
|
+
'<div class="flex flex-wrap items-center gap-2"><span class="truncate font-headline text-sm font-bold uppercase tracking-tight text-on-surface">',
|
|
49
|
+
escapeHtml(item.displayTitle),
|
|
50
|
+
"</span>",
|
|
51
|
+
renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType)),
|
|
52
|
+
item.isSaved ? renderStatusBadge("saved", "primary") : "",
|
|
53
|
+
item.isDestructive ? renderStatusBadge("destructive", "warning") : "",
|
|
54
|
+
"</div>",
|
|
55
|
+
'<p class="query-history-sql-preview mt-2 text-left font-mono text-xs leading-5 text-on-surface-variant/75">',
|
|
56
|
+
escapeHtml(item.previewSql),
|
|
57
|
+
"</p></button>",
|
|
58
|
+
'<div class="flex items-center justify-between gap-3 border-t border-outline-variant/10 px-3 pb-3 pt-2">',
|
|
59
|
+
'<div class="min-w-0 flex flex-wrap gap-2 text-[10px] font-mono uppercase tracking-[0.14em] text-on-surface-variant/55">',
|
|
60
|
+
tableMarkup,
|
|
61
|
+
'</div><div class="flex items-center gap-1">',
|
|
62
|
+
'<button class="query-history-icon-button" data-action="open-query-history" data-history-id="',
|
|
63
|
+
escapeHtml(item.id),
|
|
64
|
+
'" title="Open in editor" type="button"><span class="material-symbols-outlined text-[18px]">edit_note</span></button>',
|
|
65
|
+
'<button class="query-history-icon-button" data-action="run-query-history" data-history-id="',
|
|
66
|
+
escapeHtml(item.id),
|
|
67
|
+
'" title="Run query" type="button"><span class="material-symbols-outlined text-[18px]">play_arrow</span></button>',
|
|
68
|
+
'<button class="query-history-icon-button ',
|
|
69
|
+
item.isSaved ? "is-active" : "",
|
|
70
|
+
'" data-action="toggle-query-history-saved" data-history-id="',
|
|
71
|
+
escapeHtml(item.id),
|
|
72
|
+
'" data-next-value="',
|
|
73
|
+
item.isSaved ? "false" : "true",
|
|
74
|
+
'" title="',
|
|
75
|
+
item.isSaved ? "Remove from saved" : "Save query",
|
|
76
|
+
'" type="button"><span class="material-symbols-outlined text-[18px]">',
|
|
77
|
+
item.isSaved ? "bookmark" : "bookmark_add",
|
|
78
|
+
"</span></button>",
|
|
79
|
+
'<button class="query-history-icon-button ',
|
|
80
|
+
isSelected ? "is-active" : "",
|
|
81
|
+
'" data-action="select-query-history-item" data-history-id="',
|
|
82
|
+
escapeHtml(item.id),
|
|
83
|
+
'" title="Open query detail" type="button"><span class="material-symbols-outlined text-[18px]">info</span></button>',
|
|
84
|
+
"</div></div></article>",
|
|
85
|
+
].join("");
|
|
102
86
|
}
|
|
103
87
|
|
|
104
88
|
function renderQueryHistoryTabs(activeTab, historyTotal) {
|
|
@@ -109,27 +93,25 @@ function renderQueryHistoryTabs(activeTab, historyTotal) {
|
|
|
109
93
|
{ id: "failed", label: "Failed" },
|
|
110
94
|
];
|
|
111
95
|
|
|
112
|
-
return
|
|
113
|
-
<div class="flex items-center gap-2">
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
</div>
|
|
132
|
-
`;
|
|
96
|
+
return [
|
|
97
|
+
'<div class="flex items-center gap-2">',
|
|
98
|
+
tabs
|
|
99
|
+
.map((tab) =>
|
|
100
|
+
[
|
|
101
|
+
'<button class="query-history-tab ',
|
|
102
|
+
activeTab === tab.id ? "is-active" : "",
|
|
103
|
+
'" data-action="set-query-history-tab" data-tab="',
|
|
104
|
+
tab.id,
|
|
105
|
+
'" type="button">',
|
|
106
|
+
escapeHtml(tab.label),
|
|
107
|
+
"</button>",
|
|
108
|
+
].join("")
|
|
109
|
+
)
|
|
110
|
+
.join(""),
|
|
111
|
+
'<span class="ml-auto text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/50">',
|
|
112
|
+
escapeHtml(formatNumber(historyTotal)),
|
|
113
|
+
"</span></div>",
|
|
114
|
+
].join("");
|
|
133
115
|
}
|
|
134
116
|
|
|
135
117
|
export function renderQueryHistoryPanel({
|
|
@@ -76,7 +76,9 @@ export function renderQueryResultsPane(
|
|
|
76
76
|
editable ? "cursor-pointer hover:bg-surface-bright" : "hover:bg-surface-bright"
|
|
77
77
|
}`,
|
|
78
78
|
getRowAttrs: (_, index) =>
|
|
79
|
-
editable
|
|
79
|
+
editable
|
|
80
|
+
? ['data-action="select-editor-row" data-row-index="', index, '"'].join("")
|
|
81
|
+
: "",
|
|
80
82
|
})
|
|
81
83
|
: `
|
|
82
84
|
<div class="flex h-full flex-col items-center justify-center text-center text-on-surface-variant/35">
|
|
@@ -149,43 +149,40 @@ export function renderRowEditorPanel({
|
|
|
149
149
|
}) {
|
|
150
150
|
const canSubmit = !disabledMessage && editableFields.length > 0;
|
|
151
151
|
const canDelete = !disabledMessage && deleteEnabled;
|
|
152
|
-
const formId =
|
|
152
|
+
const formId = [formName, "-panel-form"].join("");
|
|
153
153
|
const headerActions = [
|
|
154
154
|
reloadAction
|
|
155
|
-
?
|
|
156
|
-
<button
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
>
|
|
161
|
-
Reload
|
|
162
|
-
</button>
|
|
163
|
-
`
|
|
155
|
+
? [
|
|
156
|
+
'<button class="standard-button" data-action="',
|
|
157
|
+
escapeHtml(reloadAction),
|
|
158
|
+
'" type="button">Reload</button>',
|
|
159
|
+
].join("")
|
|
164
160
|
: "",
|
|
165
161
|
canSubmit
|
|
166
|
-
?
|
|
167
|
-
<button
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`
|
|
162
|
+
? [
|
|
163
|
+
'<button class="standard-button" form="',
|
|
164
|
+
escapeHtml(formId),
|
|
165
|
+
'" type="submit" ',
|
|
166
|
+
saving || deleting ? "disabled" : "",
|
|
167
|
+
">",
|
|
168
|
+
escapeHtml(saving ? "Saving..." : submitLabel),
|
|
169
|
+
"</button>",
|
|
170
|
+
].join("")
|
|
176
171
|
: "",
|
|
177
172
|
canDelete
|
|
178
|
-
?
|
|
179
|
-
<button
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
173
|
+
? [
|
|
174
|
+
'<button class="delete-button" data-action="',
|
|
175
|
+
escapeHtml(deleteAction),
|
|
176
|
+
'" ',
|
|
177
|
+
deleteRowIndex === null
|
|
178
|
+
? ""
|
|
179
|
+
: ['data-row-index="', escapeHtml(String(deleteRowIndex)), '"'].join(""),
|
|
180
|
+
' type="button" ',
|
|
181
|
+
saving || deleting ? "disabled" : "",
|
|
182
|
+
">",
|
|
183
|
+
escapeHtml(deleting ? "Deleting..." : deleteLabel),
|
|
184
|
+
"</button>",
|
|
185
|
+
].join("")
|
|
189
186
|
: "",
|
|
190
187
|
]
|
|
191
188
|
.filter(Boolean)
|