sqlite-hub 1.1.0 → 1.1.2
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 +17 -196
- package/bin/sqlite-hub.js +7 -2
- package/frontend/js/api.js +60 -0
- package/frontend/js/app.js +140 -10
- package/frontend/js/components/dropdownButton.js +92 -0
- package/frontend/js/components/modal.js +991 -876
- package/frontend/js/components/queryEditor.js +29 -18
- package/frontend/js/components/queryHistoryDetail.js +20 -1
- package/frontend/js/components/queryHistoryHeader.js +3 -2
- package/frontend/js/components/rowEditorPanel.js +1 -0
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/components/structureGraph.js +1 -0
- package/frontend/js/components/tableDesignerEditor.js +23 -42
- package/frontend/js/components/tableDesignerSidebar.js +7 -31
- package/frontend/js/components/tableDesignerSqlPreview.js +2 -1
- package/frontend/js/router.js +2 -0
- package/frontend/js/store.js +407 -38
- package/frontend/js/utils/riskySql.js +165 -0
- package/frontend/js/views/backups.js +176 -0
- package/frontend/js/views/charts.js +12 -11
- package/frontend/js/views/data.js +37 -35
- package/frontend/js/views/documents.js +231 -162
- package/frontend/js/views/mediaTagging.js +6 -5
- package/frontend/js/views/structure.js +47 -43
- package/frontend/js/views/tableDesigner.js +45 -1
- package/frontend/styles/components.css +237 -59
- package/frontend/styles/structure-graph.css +10 -2
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +2 -0
- package/frontend/styles/views.css +84 -41
- package/package.json +2 -1
- package/server/routes/backups.js +120 -0
- package/server/routes/connections.js +26 -3
- package/server/routes/externalApi.js +6 -2
- package/server/server.js +3 -1
- package/server/services/databaseCommandService.js +4 -3
- package/server/services/sqlite/backupService.js +497 -66
- package/server/services/sqlite/connectionManager.js +2 -2
- package/server/services/sqlite/dataBrowserService.js +11 -3
- package/server/services/sqlite/importService.js +25 -0
- package/server/services/sqlite/sqlExecutor.js +2 -0
- package/server/services/storage/appStateStore.js +379 -88
- package/tests/api-token-auth.test.js +45 -2
- package/tests/backup-manager.test.js +140 -0
- package/tests/backups-view.test.js +64 -0
- package/tests/charts-height-preset-storage.test.js +60 -0
- package/tests/charts-route-state.test.js +144 -0
- package/tests/cli-service-delegation.test.js +97 -0
- package/tests/connection-removal.test.js +52 -0
- package/tests/database-command-service.test.js +38 -3
- package/tests/documents-view.test.js +132 -0
- package/tests/dropdown-button.test.js +75 -0
- package/tests/query-editor.test.js +28 -0
- package/tests/query-history-detail.test.js +37 -0
- package/tests/query-history-header.test.js +30 -0
- package/tests/risky-sql.test.js +30 -0
- package/tests/row-editor-null-values.test.js +24 -0
- package/tests/structure-view.test.js +56 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderDropdownButton } from "./dropdownButton.js";
|
|
1
2
|
import { escapeHtml, highlightSql } from "../utils/format.js";
|
|
2
3
|
|
|
3
4
|
function renderLineNumbers(query) {
|
|
@@ -45,7 +46,8 @@ export function renderQueryEditor({
|
|
|
45
46
|
const secondaryButtonClass = "standard-button";
|
|
46
47
|
const left = `
|
|
47
48
|
<button
|
|
48
|
-
class="${secondaryButtonClass}"
|
|
49
|
+
class="${secondaryButtonClass} panel-toggle-button ${editorVisible ? "" : "is-active"}"
|
|
50
|
+
aria-pressed="${editorVisible ? "false" : "true"}"
|
|
49
51
|
data-action="toggle-editor-panel"
|
|
50
52
|
data-next-value="${editorVisible ? "false" : "true"}"
|
|
51
53
|
type="button"
|
|
@@ -56,21 +58,29 @@ export function renderQueryEditor({
|
|
|
56
58
|
`;
|
|
57
59
|
|
|
58
60
|
const center = `
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
${renderDropdownButton({
|
|
62
|
+
icon: "edit_note",
|
|
63
|
+
label: "Editor",
|
|
64
|
+
title: "Editor actions",
|
|
65
|
+
items: [
|
|
66
|
+
{
|
|
67
|
+
action: "format-current-query",
|
|
68
|
+
icon: "format_align_left",
|
|
69
|
+
label: "Format",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
action: "clear-query",
|
|
73
|
+
icon: "delete_sweep",
|
|
74
|
+
label: "Clear",
|
|
75
|
+
danger: true,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
action: "copy-current-query",
|
|
79
|
+
icon: "content_copy",
|
|
80
|
+
label: "Copy to Clipboard",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
})}
|
|
74
84
|
<button
|
|
75
85
|
class="${secondaryButtonClass}"
|
|
76
86
|
data-action="open-query-export-modal"
|
|
@@ -90,7 +100,8 @@ export function renderQueryEditor({
|
|
|
90
100
|
|
|
91
101
|
const right = `
|
|
92
102
|
<button
|
|
93
|
-
class="${secondaryButtonClass}"
|
|
103
|
+
class="${secondaryButtonClass} panel-toggle-button ${historyVisible ? "" : "is-active"}"
|
|
104
|
+
aria-pressed="${historyVisible ? "false" : "true"}"
|
|
94
105
|
data-action="toggle-query-history-panel"
|
|
95
106
|
data-next-value="${historyVisible ? "false" : "true"}"
|
|
96
107
|
type="button"
|
|
@@ -102,7 +113,7 @@ export function renderQueryEditor({
|
|
|
102
113
|
|
|
103
114
|
return `
|
|
104
115
|
<div class="flex h-full min-h-0 flex-col">
|
|
105
|
-
<div class="
|
|
116
|
+
<div class="workspace-header workspace-header--low">
|
|
106
117
|
<div class="query-editor-toolbar">
|
|
107
118
|
<div class="query-editor-toolbar__left">${left}</div>
|
|
108
119
|
<div class="query-editor-toolbar__center">${center}</div>
|
|
@@ -35,11 +35,30 @@ function getQueryTypeTone(queryType) {
|
|
|
35
35
|
return "muted";
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function getExecutionSourceTone(source) {
|
|
39
|
+
if (source === "api") {
|
|
40
|
+
return "primary";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (source === "cli") {
|
|
44
|
+
return "warning";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (source === "mcp") {
|
|
48
|
+
return "muted";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return "success";
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
function renderRunItem(run) {
|
|
55
|
+
const executedBy = String(run.executedBy ?? "user").trim().toLowerCase() || "user";
|
|
56
|
+
|
|
39
57
|
return `
|
|
40
58
|
<div class="border border-outline-variant/10 bg-surface-container px-3 py-3">
|
|
41
59
|
<div class="flex flex-wrap items-center gap-2">
|
|
42
60
|
${renderStatusBadge(run.status, run.status === "error" ? "alert" : "success")}
|
|
61
|
+
${renderStatusBadge(executedBy, getExecutionSourceTone(executedBy))}
|
|
43
62
|
<span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
44
63
|
${escapeHtml(formatCompactDateTime(run.executedAt))}
|
|
45
64
|
</span>
|
|
@@ -199,7 +218,7 @@ export function renderQueryHistoryDetail({
|
|
|
199
218
|
"</button>",
|
|
200
219
|
'<button class="delete-button" data-action="open-delete-query-history-modal" data-history-id="',
|
|
201
220
|
escapeHtml(item.id),
|
|
202
|
-
'" type="button">Delete</button></div>',
|
|
221
|
+
'" type="button"><span class="material-symbols-outlined text-sm">delete</span>Delete</button></div>',
|
|
203
222
|
'<div class="mt-6"><div class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">SQL</div>',
|
|
204
223
|
'<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
224
|
highlightSql(item.rawSql),
|
|
@@ -34,10 +34,11 @@ export function renderQueryHistorySearch({
|
|
|
34
34
|
placeholder = 'Search SQL, titles, notes...',
|
|
35
35
|
} = {}) {
|
|
36
36
|
return `
|
|
37
|
-
<label class="
|
|
37
|
+
<label class="query-history-search">
|
|
38
38
|
<span class="sr-only">Search query history</span>
|
|
39
|
+
<span class="material-symbols-outlined query-history-search__icon" aria-hidden="true">search</span>
|
|
39
40
|
<input
|
|
40
|
-
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"
|
|
41
|
+
class="control-input query-history-search__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"
|
|
41
42
|
data-bind="${escapeHtml(bind)}"
|
|
42
43
|
placeholder="${escapeHtml(placeholder)}"
|
|
43
44
|
type="search"
|
|
@@ -521,6 +521,7 @@ export function renderRowEditorPanel({
|
|
|
521
521
|
' type="button" ',
|
|
522
522
|
saving || deleting ? "disabled" : "",
|
|
523
523
|
">",
|
|
524
|
+
'<span class="material-symbols-outlined text-sm">delete</span>',
|
|
524
525
|
escapeHtml(deleting ? "Deleting..." : deleteLabel),
|
|
525
526
|
"</button>",
|
|
526
527
|
].join("")
|
|
@@ -19,6 +19,7 @@ const sidebarItems = [
|
|
|
19
19
|
{ label: 'TAGGING_QUEUE', href: '#/media-tagging/queue', key: 'mediaTaggingQueue' },
|
|
20
20
|
],
|
|
21
21
|
},
|
|
22
|
+
{ label: 'Backups', href: '#/backups', key: 'backups', icon: 'inventory_2' },
|
|
22
23
|
{ label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },
|
|
23
24
|
];
|
|
24
25
|
|
|
@@ -966,6 +966,7 @@ function updateInspectorToggleButton() {
|
|
|
966
966
|
}
|
|
967
967
|
|
|
968
968
|
currentGraph.inspectorToggleButton.classList.toggle('is-active', currentGraph.inspectorHidden);
|
|
969
|
+
currentGraph.inspectorToggleButton.setAttribute('aria-pressed', currentGraph.inspectorHidden ? 'true' : 'false');
|
|
969
970
|
const icon = document.createElement('span');
|
|
970
971
|
const label = currentGraph.inspectorHidden ? 'Show Inspector' : 'Hide Inspector';
|
|
971
972
|
|
|
@@ -323,7 +323,6 @@ export function renderTableDesignerEditor(state) {
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
const catalogTables = state.tableDesigner.tables ?? [];
|
|
326
|
-
const visibleColumns = draft.columns.filter((column) => !column.deleted);
|
|
327
326
|
const saveLabel =
|
|
328
327
|
draft.mode === "create"
|
|
329
328
|
? state.tableDesigner.saving
|
|
@@ -335,11 +334,12 @@ export function renderTableDesignerEditor(state) {
|
|
|
335
334
|
|
|
336
335
|
return `
|
|
337
336
|
<section class="table-designer-main shell-section">
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
337
|
+
<div data-table-designer-feedback>
|
|
338
|
+
${renderTableDesignerFeedback(draft, state.tableDesigner.saveError)}
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<section class="table-designer-main__section">
|
|
342
|
+
<div class="table-designer-main__section-header">
|
|
343
343
|
<div class="table-designer-main__title-row">
|
|
344
344
|
<input
|
|
345
345
|
class="table-designer-main__name"
|
|
@@ -356,42 +356,6 @@ export function renderTableDesignerEditor(state) {
|
|
|
356
356
|
: ""
|
|
357
357
|
}
|
|
358
358
|
</div>
|
|
359
|
-
<div class="table-designer-main__subtitle">
|
|
360
|
-
${escapeHtml(String(visibleColumns.length))} visible column${
|
|
361
|
-
visibleColumns.length === 1 ? "" : "s"
|
|
362
|
-
} // Table Designer v2 // SQLite-safe operations only
|
|
363
|
-
</div>
|
|
364
|
-
</div>
|
|
365
|
-
<div class="table-designer-main__actions">
|
|
366
|
-
${renderFillToggle(draft)}
|
|
367
|
-
<button
|
|
368
|
-
class="standard-button"
|
|
369
|
-
data-action="refresh-view"
|
|
370
|
-
type="button"
|
|
371
|
-
>
|
|
372
|
-
Reload Schema
|
|
373
|
-
</button>
|
|
374
|
-
<button
|
|
375
|
-
class="standard-button"
|
|
376
|
-
data-action="save-table-designer"
|
|
377
|
-
data-table-designer-save-button
|
|
378
|
-
${draft.canSave ? "" : "disabled"}
|
|
379
|
-
type="button"
|
|
380
|
-
>
|
|
381
|
-
${escapeHtml(saveLabel)}
|
|
382
|
-
</button>
|
|
383
|
-
</div>
|
|
384
|
-
</header>
|
|
385
|
-
|
|
386
|
-
<div data-table-designer-feedback>
|
|
387
|
-
${renderTableDesignerFeedback(draft, state.tableDesigner.saveError)}
|
|
388
|
-
</div>
|
|
389
|
-
|
|
390
|
-
<section class="table-designer-main__section">
|
|
391
|
-
<div class="table-designer-main__section-header">
|
|
392
|
-
<div>
|
|
393
|
-
<div class="table-designer-main__section-title">Columns</div>
|
|
394
|
-
</div>
|
|
395
359
|
<div class="table-designer-main__section-actions">
|
|
396
360
|
<button
|
|
397
361
|
class="standard-button"
|
|
@@ -400,6 +364,23 @@ export function renderTableDesignerEditor(state) {
|
|
|
400
364
|
>
|
|
401
365
|
+ Add Column
|
|
402
366
|
</button>
|
|
367
|
+
<button
|
|
368
|
+
class="standard-button"
|
|
369
|
+
data-action="refresh-view"
|
|
370
|
+
type="button"
|
|
371
|
+
>
|
|
372
|
+
Reload Schema
|
|
373
|
+
</button>
|
|
374
|
+
<button
|
|
375
|
+
class="standard-button"
|
|
376
|
+
data-action="save-table-designer"
|
|
377
|
+
data-table-designer-save-button
|
|
378
|
+
${draft.canSave ? "" : "disabled"}
|
|
379
|
+
type="button"
|
|
380
|
+
>
|
|
381
|
+
${escapeHtml(saveLabel)}
|
|
382
|
+
</button>
|
|
383
|
+
${renderFillToggle(draft)}
|
|
403
384
|
</div>
|
|
404
385
|
</div>
|
|
405
386
|
${renderColumnGrid(draft, catalogTables)}
|
|
@@ -18,38 +18,14 @@ export function renderTableDesignerSidebar(state) {
|
|
|
18
18
|
const isNewDraft = state.tableDesigner.draft?.mode === 'create';
|
|
19
19
|
|
|
20
20
|
return `
|
|
21
|
-
<aside class="table-designer-sidebar">
|
|
22
|
-
<div class="table-designer-sidebar__header">
|
|
21
|
+
<aside class="table-designer-sidebar subnavi-panel">
|
|
22
|
+
<div class="table-designer-sidebar__header subnavi-header">
|
|
23
23
|
<div>
|
|
24
|
-
<div class="
|
|
25
|
-
<div class="
|
|
24
|
+
<div class="subnavi-header-title">Table Designer</div>
|
|
25
|
+
<div class="subnavi-header-details">
|
|
26
26
|
${escapeHtml(formatNumber(tables.length))} table${tables.length === 1 ? '' : 's'}
|
|
27
27
|
</div>
|
|
28
28
|
</div>
|
|
29
|
-
<div class="table-designer-sidebar__header-actions">
|
|
30
|
-
<button
|
|
31
|
-
class="standard-button"
|
|
32
|
-
data-action="import-table-designer-csv"
|
|
33
|
-
type="button"
|
|
34
|
-
>
|
|
35
|
-
Import CSV
|
|
36
|
-
</button>
|
|
37
|
-
<input
|
|
38
|
-
accept=".csv,text/csv"
|
|
39
|
-
class="table-designer-sidebar__file-input"
|
|
40
|
-
data-bind="table-designer-import-file"
|
|
41
|
-
type="file"
|
|
42
|
-
/>
|
|
43
|
-
<button
|
|
44
|
-
class="signature-button"
|
|
45
|
-
data-action="navigate"
|
|
46
|
-
data-to="/table-designer/new"
|
|
47
|
-
type="button"
|
|
48
|
-
>
|
|
49
|
-
+ New Table
|
|
50
|
-
</button>
|
|
51
|
-
|
|
52
|
-
</div>
|
|
53
29
|
</div>
|
|
54
30
|
|
|
55
31
|
<label class="table-designer-sidebar__search">
|
|
@@ -64,7 +40,7 @@ export function renderTableDesignerSidebar(state) {
|
|
|
64
40
|
/>
|
|
65
41
|
</label>
|
|
66
42
|
|
|
67
|
-
<div class="table-designer-sidebar__list custom-scrollbar">
|
|
43
|
+
<div class="table-designer-sidebar__list subnavi-list custom-scrollbar">
|
|
68
44
|
${
|
|
69
45
|
state.tableDesigner.loading && !tables.length
|
|
70
46
|
? `
|
|
@@ -76,7 +52,7 @@ export function renderTableDesignerSidebar(state) {
|
|
|
76
52
|
: isNewDraft
|
|
77
53
|
? `
|
|
78
54
|
<button
|
|
79
|
-
class="table-designer-sidebar__item is-active
|
|
55
|
+
class="table-designer-sidebar__item subnavi-item is-active border border-primary-container/30 bg-surface-container-high px-4 py-3 text-left transition-colors"
|
|
80
56
|
data-action="navigate"
|
|
81
57
|
data-to="/table-designer/new"
|
|
82
58
|
type="button"
|
|
@@ -102,7 +78,7 @@ export function renderTableDesignerSidebar(state) {
|
|
|
102
78
|
.map(
|
|
103
79
|
table => `
|
|
104
80
|
<button
|
|
105
|
-
class="table-designer-sidebar__item
|
|
81
|
+
class="table-designer-sidebar__item subnavi-item border px-4 py-3 text-left transition-colors ${
|
|
106
82
|
!isNewDraft && table.name === state.tableDesigner.selectedTableName
|
|
107
83
|
? 'is-active border-primary-container/30 bg-surface-container-high'
|
|
108
84
|
: 'border-outline-variant/10 bg-surface-container-lowest hover:bg-surface-container-high'
|
|
@@ -23,7 +23,8 @@ function renderPreviewHeader(draft, isVisible) {
|
|
|
23
23
|
: ''
|
|
24
24
|
}
|
|
25
25
|
<button
|
|
26
|
-
class="standard-button"
|
|
26
|
+
class="standard-button panel-toggle-button ${isVisible ? '' : 'is-active'}"
|
|
27
|
+
aria-pressed="${isVisible ? 'false' : 'true'}"
|
|
27
28
|
data-action="toggle-table-designer-sql-preview"
|
|
28
29
|
data-next-value="${isVisible ? 'false' : 'true'}"
|
|
29
30
|
type="button"
|
package/frontend/js/router.js
CHANGED
|
@@ -11,6 +11,8 @@ export function parseHash(hash = window.location.hash) {
|
|
|
11
11
|
switch (segments[0]) {
|
|
12
12
|
case 'connections':
|
|
13
13
|
return { name: 'connections', path: '/connections', params: {} };
|
|
14
|
+
case 'backups':
|
|
15
|
+
return { name: 'backups', path: '/backups', params: {} };
|
|
14
16
|
case 'overview':
|
|
15
17
|
return { name: 'overview', path: '/overview', params: {} };
|
|
16
18
|
case 'charts':
|