sqlite-hub 0.17.2 → 1.1.0
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 +143 -47
- package/bin/sqlite-hub.js +206 -444
- package/examples/api/queries.js +31 -0
- package/examples/api/rows.js +27 -0
- package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
- package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
- package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
- package/frontend/assets/mockups/connections_1200.webp +0 -0
- package/frontend/assets/mockups/data_1_1200.webp +0 -0
- package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
- package/frontend/assets/mockups/documents_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
- package/frontend/assets/mockups/overview_1200.webp +0 -0
- package/frontend/assets/mockups/settings_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
- package/frontend/assets/mockups/structure_1_1200.webp +0 -0
- package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
- package/frontend/js/api.js +25 -0
- package/frontend/js/app.js +263 -34
- package/frontend/js/components/formControls.js +38 -0
- package/frontend/js/components/modal.js +77 -21
- package/frontend/js/components/queryChartRenderer.js +28 -3
- package/frontend/js/components/queryEditor.js +20 -24
- package/frontend/js/components/queryHistoryDetail.js +1 -1
- package/frontend/js/components/queryHistoryHeader.js +48 -0
- package/frontend/js/components/queryHistoryList.js +132 -0
- package/frontend/js/components/queryHistoryPanel.js +72 -136
- package/frontend/js/components/structureGraph.js +699 -89
- package/frontend/js/components/tableDesignerEditor.js +3 -5
- package/frontend/js/store.js +188 -7
- package/frontend/js/utils/exportFilenames.js +3 -1
- package/frontend/js/views/charts.js +320 -169
- package/frontend/js/views/connections.js +59 -64
- package/frontend/js/views/data.js +12 -20
- package/frontend/js/views/editor.js +0 -2
- package/frontend/js/views/settings.js +219 -5
- package/frontend/js/views/structure.js +27 -13
- package/frontend/styles/components.css +155 -0
- package/frontend/styles/structure-graph.css +140 -35
- package/frontend/styles/tailwind.generated.css +2 -2
- package/frontend/styles/views.css +12 -6
- package/package.json +7 -6
- package/server/middleware/apiTokenAuth.js +30 -0
- package/server/routes/connections.js +17 -0
- package/server/routes/export.js +89 -22
- package/server/routes/externalApi.js +304 -0
- package/server/routes/settings.js +90 -21
- package/server/server.js +22 -1
- package/server/services/apiTokenService.js +101 -0
- package/server/services/appInfoService.js +215 -0
- package/server/services/databaseCommandService.js +443 -0
- package/server/services/nativeFileDialogService.js +93 -1
- package/server/services/sqlite/exportService.js +307 -22
- package/server/services/storage/appStateStore.js +113 -0
- package/server/utils/errors.js +7 -0
- package/tests/api-token-auth.test.js +236 -0
- package/tests/cli-args.test.js +16 -3
- package/tests/cli-service-delegation.test.js +43 -0
- package/tests/connections-file-dialog-route.test.js +43 -0
- package/tests/copy-column-modal.test.js +34 -0
- package/tests/database-command-service.test.js +139 -0
- package/tests/export-blob.test.js +54 -1
- package/tests/export-filenames.test.js +4 -0
- package/tests/form-controls.test.js +34 -0
- package/tests/native-file-dialog.test.js +27 -0
- package/tests/settings-api-tokens-route.test.js +97 -0
- package/tests/settings-metadata.test.js +99 -1
- package/tests/settings-view.test.js +75 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
|
@@ -1,39 +1,36 @@
|
|
|
1
|
-
import { renderConnectionCard } from
|
|
2
|
-
import { renderPageHeader } from
|
|
3
|
-
import { escapeHtml } from
|
|
1
|
+
import { renderConnectionCard } from '../components/connectionCard.js';
|
|
2
|
+
import { renderPageHeader } from '../components/pageHeader.js';
|
|
3
|
+
import { escapeHtml } from '../utils/format.js';
|
|
4
4
|
|
|
5
5
|
function renderConnectionsActionButton({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
label,
|
|
7
|
+
icon,
|
|
8
|
+
action = 'open-modal',
|
|
9
|
+
modal,
|
|
10
|
+
tone = 'secondary',
|
|
11
|
+
disabled = false,
|
|
12
12
|
}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
: "standard-button";
|
|
17
|
-
const iconClassName = "";
|
|
18
|
-
const modalAttribute = modal ? `data-modal="${modal}"` : "";
|
|
13
|
+
const toneClassName = tone === 'primary' ? 'signature-button' : 'standard-button';
|
|
14
|
+
const iconClassName = '';
|
|
15
|
+
const modalAttribute = modal ? `data-modal="${modal}"` : '';
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
return `
|
|
21
18
|
<button
|
|
22
19
|
class="${toneClassName}"
|
|
23
20
|
data-action="${escapeHtml(action)}"
|
|
24
21
|
${modalAttribute}
|
|
25
22
|
type="button"
|
|
26
|
-
${disabled ?
|
|
23
|
+
${disabled ? 'disabled' : ''}
|
|
27
24
|
>
|
|
28
25
|
<span>${label}</span>
|
|
29
|
-
<span class="material-symbols-outlined${iconClassName ? ` ${iconClassName}` :
|
|
26
|
+
<span class="material-symbols-outlined${iconClassName ? ` ${iconClassName}` : ''}">${icon}</span>
|
|
30
27
|
</button>
|
|
31
28
|
`;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
function renderConnectionsBody(state) {
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
if (state.connections.loading && !state.connections.recent.length) {
|
|
33
|
+
return `
|
|
37
34
|
<div class="flex min-h-[280px] items-center justify-center border border-outline-variant/10 bg-surface-container-low">
|
|
38
35
|
<div class="text-center text-on-surface-variant/40">
|
|
39
36
|
<span class="material-symbols-outlined mb-3 text-4xl">progress_activity</span>
|
|
@@ -41,10 +38,10 @@ function renderConnectionsBody(state) {
|
|
|
41
38
|
</div>
|
|
42
39
|
</div>
|
|
43
40
|
`;
|
|
44
|
-
|
|
41
|
+
}
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
if (state.connections.error) {
|
|
44
|
+
return `
|
|
48
45
|
<div class="border border-error/20 bg-error-container/10 px-6 py-5 text-sm text-on-surface">
|
|
49
46
|
<div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
|
|
50
47
|
${escapeHtml(state.connections.error.code)}
|
|
@@ -52,10 +49,10 @@ function renderConnectionsBody(state) {
|
|
|
52
49
|
<div class="mt-2">${escapeHtml(state.connections.error.message)}</div>
|
|
53
50
|
</div>
|
|
54
51
|
`;
|
|
55
|
-
|
|
52
|
+
}
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
if (!state.connections.recent.length) {
|
|
55
|
+
return `
|
|
59
56
|
<div class="border border-dashed border-outline-variant/20 bg-surface-container-low px-8 py-10 text-center">
|
|
60
57
|
<span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
|
|
61
58
|
<p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
|
|
@@ -66,71 +63,69 @@ function renderConnectionsBody(state) {
|
|
|
66
63
|
</p>
|
|
67
64
|
<div class="mt-6 flex flex-wrap items-center justify-center gap-3">
|
|
68
65
|
${renderConnectionsActionButton({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tone: "primary",
|
|
66
|
+
label: 'Create Database',
|
|
67
|
+
icon: 'note_add',
|
|
68
|
+
modal: 'create-connection',
|
|
73
69
|
})}
|
|
74
70
|
${renderConnectionsActionButton({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
label: 'Open Database',
|
|
72
|
+
icon: 'folder_open',
|
|
73
|
+
modal: 'open-connection',
|
|
74
|
+
tone: 'primary',
|
|
78
75
|
})}
|
|
79
76
|
</div>
|
|
80
77
|
</div>
|
|
81
78
|
`;
|
|
82
|
-
|
|
79
|
+
}
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
return `
|
|
85
82
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3">
|
|
86
83
|
${state.connections.recent
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
.join("")}
|
|
84
|
+
.map(connection => renderConnectionCard(connection, state.connections.active?.id))
|
|
85
|
+
.join('')}
|
|
91
86
|
</div>
|
|
92
87
|
`;
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
export function renderConnectionsView(state) {
|
|
96
|
-
|
|
91
|
+
const actions = `
|
|
92
|
+
${
|
|
93
|
+
state.connections.active
|
|
94
|
+
? renderConnectionsActionButton({
|
|
95
|
+
label: state.connections.backupLoading ? 'Creating Backup...' : 'Create Backup',
|
|
96
|
+
icon: 'inventory_2',
|
|
97
|
+
action: 'create-backup',
|
|
98
|
+
disabled: state.connections.backupLoading,
|
|
99
|
+
})
|
|
100
|
+
: ''
|
|
101
|
+
}
|
|
97
102
|
${renderConnectionsActionButton({
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
tone: "primary",
|
|
103
|
+
label: 'Create Database',
|
|
104
|
+
icon: 'note_add',
|
|
105
|
+
modal: 'create-connection',
|
|
102
106
|
})}
|
|
103
107
|
${renderConnectionsActionButton({
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
label: 'Open Database',
|
|
109
|
+
icon: 'folder_open',
|
|
110
|
+
modal: 'open-connection',
|
|
111
|
+
tone: 'primary',
|
|
107
112
|
})}
|
|
108
|
-
${
|
|
109
|
-
state.connections.active
|
|
110
|
-
? renderConnectionsActionButton({
|
|
111
|
-
label: state.connections.backupLoading ? "Creating Backup..." : "Create Backup",
|
|
112
|
-
icon: "inventory_2",
|
|
113
|
-
action: "create-backup",
|
|
114
|
-
disabled: state.connections.backupLoading,
|
|
115
|
-
})
|
|
116
|
-
: ""
|
|
117
|
-
}
|
|
118
113
|
`;
|
|
119
114
|
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
return {
|
|
116
|
+
main: `
|
|
122
117
|
<section class="view-surface relative min-h-full overflow-hidden">
|
|
123
118
|
<div class="data-grid-texture pointer-events-none absolute inset-0"></div>
|
|
124
119
|
<div class="view-frame relative z-10">
|
|
125
120
|
${renderPageHeader({
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
121
|
+
title: 'Connections',
|
|
122
|
+
subtitle: `Registry // Recent_Targets: ${String(state.connections.recent.length).padStart(2, '0')}`,
|
|
123
|
+
actions,
|
|
129
124
|
})}
|
|
130
125
|
${renderConnectionsBody(state)}
|
|
131
126
|
</div>
|
|
132
127
|
</section>
|
|
133
128
|
`,
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
panel: '',
|
|
130
|
+
};
|
|
136
131
|
}
|
|
@@ -112,25 +112,8 @@ function renderWorkspaceHeader(state) {
|
|
|
112
112
|
const tablesVisible = state.dataBrowser.tablesVisible !== false;
|
|
113
113
|
|
|
114
114
|
return `
|
|
115
|
-
<header class="border-b border-outline-variant/10 bg-surface-container px-6 py-
|
|
116
|
-
<div class="flex flex-wrap items-
|
|
117
|
-
<div class="data-headline-container">
|
|
118
|
-
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
|
|
119
|
-
Data Browser
|
|
120
|
-
</div>
|
|
121
|
-
<h1 class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
|
|
122
|
-
${escapeHtml(table?.name ?? 'Table Data')}
|
|
123
|
-
</h1>
|
|
124
|
-
<div class="mt-2 text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">
|
|
125
|
-
${
|
|
126
|
-
table
|
|
127
|
-
? `rows ${escapeHtml(formatNumber(table.rowCount ?? 0))} // columns ${escapeHtml(
|
|
128
|
-
formatNumber(table.columns?.length ?? 0),
|
|
129
|
-
)}`
|
|
130
|
-
: `tables ${escapeHtml(formatNumber(state.dataBrowser.tables.length))}`
|
|
131
|
-
}
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
115
|
+
<header class="border-b border-outline-variant/10 bg-surface-container px-6 py-3">
|
|
116
|
+
<div class="flex flex-wrap items-center justify-between gap-4">
|
|
134
117
|
<div class="flex items-center gap-3">
|
|
135
118
|
${
|
|
136
119
|
table
|
|
@@ -145,7 +128,14 @@ function renderWorkspaceHeader(state) {
|
|
|
145
128
|
: '<span class="material-symbols-outlined text-sm">visibility</span> Show Tables'
|
|
146
129
|
}
|
|
147
130
|
</button>
|
|
148
|
-
|
|
131
|
+
`
|
|
132
|
+
: ''
|
|
133
|
+
}
|
|
134
|
+
</div>
|
|
135
|
+
<div class="flex flex-wrap items-center justify-end gap-3">
|
|
136
|
+
${
|
|
137
|
+
table
|
|
138
|
+
? `<button
|
|
149
139
|
class="standard-button"
|
|
150
140
|
data-action="open-data-export-modal"
|
|
151
141
|
type="button"
|
|
@@ -476,6 +466,8 @@ function renderTableSurface(state) {
|
|
|
476
466
|
escapeHtml(formatNumber(totalRows)),
|
|
477
467
|
filteredRowsText,
|
|
478
468
|
' rows',
|
|
469
|
+
' // columns ',
|
|
470
|
+
escapeHtml(formatNumber(table.columns?.length ?? 0)),
|
|
479
471
|
'</div>',
|
|
480
472
|
'<div class="flex flex-wrap items-center gap-4"><div class="flex items-center gap-2">',
|
|
481
473
|
'<span class="text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/55">rows</span>',
|
|
@@ -307,8 +307,6 @@ export function renderEditorView(state, { isResultsRoute = false } = {}) {
|
|
|
307
307
|
query: state.editor.sqlText,
|
|
308
308
|
executing: state.editor.executing,
|
|
309
309
|
exporting: state.editor.exportLoading,
|
|
310
|
-
historyLoading: state.editor.historyLoading,
|
|
311
|
-
historyTotal: state.editor.historyTotal,
|
|
312
310
|
editorVisible: state.editor.editorPanelVisible,
|
|
313
311
|
historyVisible: state.editor.historyPanelVisible,
|
|
314
312
|
})}
|
|
@@ -1,6 +1,95 @@
|
|
|
1
1
|
import { renderPageHeader } from '../components/pageHeader.js';
|
|
2
|
+
import { renderTextInput } from '../components/formControls.js';
|
|
2
3
|
import { escapeHtml } from '../utils/format.js';
|
|
3
4
|
|
|
5
|
+
function renderSettingsNavigation(activeSection) {
|
|
6
|
+
const items = [
|
|
7
|
+
{ id: 'information', icon: 'info', label: 'Information' },
|
|
8
|
+
{ id: 'api-tokens', icon: 'key', label: 'API Tokens' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
return `
|
|
12
|
+
<nav class="flex flex-wrap gap-2 border-b border-outline-variant/10 pb-4" aria-label="Settings sections">
|
|
13
|
+
${items
|
|
14
|
+
.map(
|
|
15
|
+
item => `
|
|
16
|
+
<button
|
|
17
|
+
class="query-history-tab ${activeSection === item.id ? 'is-active' : ''}"
|
|
18
|
+
aria-current="${activeSection === item.id ? 'page' : 'false'}"
|
|
19
|
+
data-action="set-settings-section"
|
|
20
|
+
data-section="${item.id}"
|
|
21
|
+
type="button"
|
|
22
|
+
>
|
|
23
|
+
<span class="material-symbols-outlined text-sm mr-[5px]">${item.icon}</span>
|
|
24
|
+
${item.label}
|
|
25
|
+
</button>
|
|
26
|
+
`,
|
|
27
|
+
)
|
|
28
|
+
.join('')}
|
|
29
|
+
</nav>
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderVersionCheckStatus(settings) {
|
|
34
|
+
const result = settings.versionCheck;
|
|
35
|
+
const error = settings.versionCheckError;
|
|
36
|
+
|
|
37
|
+
if (error) {
|
|
38
|
+
return `
|
|
39
|
+
<div class="border border-error/20 bg-error-container/10 px-4 py-3 text-sm text-on-surface">
|
|
40
|
+
<div class="font-mono text-[10px] font-bold uppercase tracking-[0.18em] text-error">
|
|
41
|
+
${escapeHtml(error.code ?? 'VERSION_CHECK_FAILED')}
|
|
42
|
+
</div>
|
|
43
|
+
<div class="mt-1 text-on-surface-variant">${escapeHtml(error.message ?? 'Version check failed.')}</div>
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!result) {
|
|
49
|
+
return `
|
|
50
|
+
<div class="border border-outline-variant/10 bg-surface-container-high px-4 py-3 text-sm text-on-surface-variant">
|
|
51
|
+
No version check has been run yet.
|
|
52
|
+
</div>
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const updateAvailable = Boolean(result.updateAvailable);
|
|
57
|
+
const releaseLink =
|
|
58
|
+
updateAvailable && result.releaseUrl
|
|
59
|
+
? `
|
|
60
|
+
<a
|
|
61
|
+
class="standard-button"
|
|
62
|
+
href="${escapeHtml(result.releaseUrl)}"
|
|
63
|
+
rel="noreferrer"
|
|
64
|
+
target="_blank"
|
|
65
|
+
>
|
|
66
|
+
<span class="material-symbols-outlined text-sm">open_in_new</span>
|
|
67
|
+
Open Release
|
|
68
|
+
</a>
|
|
69
|
+
`
|
|
70
|
+
: '';
|
|
71
|
+
|
|
72
|
+
return `
|
|
73
|
+
<div class="border ${
|
|
74
|
+
updateAvailable ? 'border-primary-container/30 bg-primary-container/5' : 'border-outline-variant/10 bg-surface-container-high'
|
|
75
|
+
} px-4 py-3">
|
|
76
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
77
|
+
<div class="min-w-0">
|
|
78
|
+
<div class="font-mono text-[10px] font-bold uppercase tracking-[0.18em] ${
|
|
79
|
+
updateAvailable ? 'text-primary-container' : 'text-on-surface-variant/70'
|
|
80
|
+
}">
|
|
81
|
+
${updateAvailable ? 'New Version Available' : 'Up To Date'}
|
|
82
|
+
</div>
|
|
83
|
+
<div class="mt-1 text-sm text-on-surface-variant">
|
|
84
|
+
Current v${escapeHtml(result.currentVersion ?? '0.0.0')} · Latest v${escapeHtml(result.latestVersion ?? 'unknown')}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
${releaseLink}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
|
|
4
93
|
function renderSettingsContent(state) {
|
|
5
94
|
if (state.settings.loading && !state.settings.appVersion) {
|
|
6
95
|
return `
|
|
@@ -24,11 +113,98 @@ function renderSettingsContent(state) {
|
|
|
24
113
|
`;
|
|
25
114
|
}
|
|
26
115
|
|
|
116
|
+
const activeSection = state.settings.section === 'api-tokens' ? 'api-tokens' : 'information';
|
|
27
117
|
const appVersion = escapeHtml(state.settings.appVersion ?? '0.0.0');
|
|
28
118
|
const sqliteVersion = escapeHtml(state.settings.sqliteVersion ?? 'unknown');
|
|
119
|
+
const versionCheckLoading = Boolean(state.settings.versionCheckLoading);
|
|
120
|
+
const tokenDatabase = state.settings.tokenDatabase;
|
|
121
|
+
const apiTokens = state.settings.apiTokens ?? [];
|
|
122
|
+
const createdApiToken = state.settings.createdApiToken;
|
|
123
|
+
const tokenSaving = Boolean(state.settings.tokenSaving);
|
|
124
|
+
const tokenItems = apiTokens.length
|
|
125
|
+
? apiTokens
|
|
126
|
+
.map(
|
|
127
|
+
token => `
|
|
128
|
+
<div class="flex flex-col gap-3 border border-outline-variant/10 bg-surface-container-high px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
|
|
129
|
+
<div class="min-w-0">
|
|
130
|
+
<div class="truncate text-sm font-semibold text-on-surface">${escapeHtml(token.name)}</div>
|
|
131
|
+
<div class="mt-1 font-mono text-[10px] text-on-surface-variant/60">
|
|
132
|
+
${escapeHtml(token.tokenPrefix)}... · Created ${escapeHtml(token.createdAt ?? 'unknown')}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<button
|
|
136
|
+
class="delete-button"
|
|
137
|
+
data-action="open-delete-api-token-modal"
|
|
138
|
+
data-token-id="${escapeHtml(token.id)}"
|
|
139
|
+
type="button"
|
|
140
|
+
${tokenSaving ? 'disabled' : ''}
|
|
141
|
+
>
|
|
142
|
+
Delete
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
`,
|
|
146
|
+
)
|
|
147
|
+
.join('')
|
|
148
|
+
: '<div class="text-sm text-on-surface-variant">No API tokens exist for this database.</div>';
|
|
149
|
+
const tokenSection = tokenDatabase
|
|
150
|
+
? `
|
|
151
|
+
<div class="space-y-5">
|
|
152
|
+
<div class="space-y-3">
|
|
153
|
+
<div class="text-sm font-semibold text-on-surface">${escapeHtml(tokenDatabase.label)}</div>
|
|
154
|
+
<div>
|
|
155
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
156
|
+
Database ID
|
|
157
|
+
</div>
|
|
158
|
+
<div class="mt-2 grid grid-cols-1 gap-3 sm:grid-cols-[minmax(0,1fr)_9rem]">
|
|
159
|
+
<div
|
|
160
|
+
class="min-w-0 flex-1 break-all border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 font-mono text-sm text-primary-container"
|
|
161
|
+
data-database-id
|
|
162
|
+
>${escapeHtml(tokenDatabase.id)}</div>
|
|
163
|
+
<button class="standard-button w-full self-center justify-center" data-action="copy-database-id" type="button">
|
|
164
|
+
<span class="material-symbols-outlined text-sm">content_copy</span>
|
|
165
|
+
Copy ID
|
|
166
|
+
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
<form class="grid grid-cols-1 gap-3 sm:grid-cols-[minmax(0,1fr)_9rem]" data-form="create-api-token">
|
|
171
|
+
${renderTextInput({
|
|
172
|
+
className: 'min-w-0 flex-1',
|
|
173
|
+
dataAttributes: { apiTokenName: true },
|
|
174
|
+
maxlength: 80,
|
|
175
|
+
name: 'name',
|
|
176
|
+
placeholder: 'Token name',
|
|
177
|
+
})}
|
|
178
|
+
<button class="standard-button w-full self-center justify-center" type="submit" ${tokenSaving ? 'disabled' : ''}>
|
|
179
|
+
Create Token
|
|
180
|
+
</button>
|
|
181
|
+
</form>
|
|
182
|
+
${
|
|
183
|
+
createdApiToken?.token
|
|
184
|
+
? `
|
|
185
|
+
<div class="border border-primary-container/20 bg-primary-container/5 p-4">
|
|
186
|
+
<div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">Shown once</div>
|
|
187
|
+
<div class="mt-2 text-sm text-on-surface-variant">Store this token now. SQLite Hub only keeps its hash.</div>
|
|
188
|
+
<div class="mt-3 flex flex-col gap-2 sm:flex-row">
|
|
189
|
+
${renderTextInput({
|
|
190
|
+
className: 'min-w-0 flex-1 font-mono',
|
|
191
|
+
dataAttributes: { createdApiToken: true },
|
|
192
|
+
readonly: true,
|
|
193
|
+
value: createdApiToken.token,
|
|
194
|
+
})}
|
|
195
|
+
<button class="standard-button" data-action="copy-created-api-token" type="button">Copy</button>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
`
|
|
199
|
+
: ''
|
|
200
|
+
}
|
|
201
|
+
<div class="space-y-2">${tokenItems}</div>
|
|
202
|
+
</div>
|
|
203
|
+
`
|
|
204
|
+
: '<div class="text-sm leading-6 text-on-surface-variant">Select a database before creating database-specific API tokens.</div>';
|
|
29
205
|
|
|
30
|
-
|
|
31
|
-
|
|
206
|
+
const informationSection = `
|
|
207
|
+
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
|
32
208
|
<section class="shell-section overflow-hidden">
|
|
33
209
|
<div class="flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-highest px-4 py-2">
|
|
34
210
|
<span class="text-[10px] font-bold uppercase tracking-[0.25em]">Application</span>
|
|
@@ -56,6 +232,23 @@ function renderSettingsContent(state) {
|
|
|
56
232
|
<div class="border-t border-outline-variant/10 py-4 text-sm leading-6 text-on-surface-variant">
|
|
57
233
|
SQL functions and syntax are evaluated with this SQLite runtime.
|
|
58
234
|
</div>
|
|
235
|
+
<div class="border-t border-outline-variant/10 pt-4">
|
|
236
|
+
<div class="mb-3 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
237
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
238
|
+
Version_Check
|
|
239
|
+
</div>
|
|
240
|
+
<button
|
|
241
|
+
class="standard-button"
|
|
242
|
+
data-action="check-app-version"
|
|
243
|
+
type="button"
|
|
244
|
+
${versionCheckLoading ? 'disabled aria-disabled="true"' : ''}
|
|
245
|
+
>
|
|
246
|
+
<span class="material-symbols-outlined text-sm">${versionCheckLoading ? 'progress_activity' : 'sync'}</span>
|
|
247
|
+
${versionCheckLoading ? 'Checking...' : 'Check Updates'}
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
${renderVersionCheckStatus(state.settings)}
|
|
251
|
+
</div>
|
|
59
252
|
</div>
|
|
60
253
|
</section>
|
|
61
254
|
|
|
@@ -120,8 +313,26 @@ function renderSettingsContent(state) {
|
|
|
120
313
|
</a>
|
|
121
314
|
</div>
|
|
122
315
|
</section>
|
|
123
|
-
|
|
124
|
-
|
|
316
|
+
</div>
|
|
317
|
+
`;
|
|
318
|
+
const apiTokensSection = `
|
|
319
|
+
<section class="shell-section overflow-hidden">
|
|
320
|
+
<div class="flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-highest px-4 py-2">
|
|
321
|
+
<span class="text-[10px] font-bold uppercase tracking-[0.25em] mr-px">API Tokens</span>
|
|
322
|
+
<span class="material-symbols-outlined text-xs text-primary-container">key</span>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="p-6">
|
|
325
|
+
${tokenSection}
|
|
326
|
+
</div>
|
|
327
|
+
</section>
|
|
328
|
+
`;
|
|
329
|
+
|
|
330
|
+
return `
|
|
331
|
+
<div class="space-y-6">
|
|
332
|
+
${renderSettingsNavigation(activeSection)}
|
|
333
|
+
${activeSection === 'api-tokens' ? apiTokensSection : informationSection}
|
|
334
|
+
</div>
|
|
335
|
+
`;
|
|
125
336
|
}
|
|
126
337
|
|
|
127
338
|
export function renderSettingsView(state) {
|
|
@@ -131,7 +342,10 @@ export function renderSettingsView(state) {
|
|
|
131
342
|
<div class="view-frame mx-auto max-w-6xl space-y-8">
|
|
132
343
|
${renderPageHeader({
|
|
133
344
|
title: 'Settings',
|
|
134
|
-
subtitle:
|
|
345
|
+
subtitle:
|
|
346
|
+
state.settings.section === 'api-tokens'
|
|
347
|
+
? 'Security // Database API access'
|
|
348
|
+
: 'Application // Build + Credits',
|
|
135
349
|
})}
|
|
136
350
|
${renderSettingsContent(state)}
|
|
137
351
|
</div>
|
|
@@ -115,20 +115,27 @@ function renderObjectInspector(detail) {
|
|
|
115
115
|
return `
|
|
116
116
|
<div class="structure-graph__panel">
|
|
117
117
|
<div class="space-y-3">
|
|
118
|
-
<div class="structure-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
`
|
|
129
|
-
: ''
|
|
130
|
-
}
|
|
118
|
+
<div class="structure-graph__panel-heading">
|
|
119
|
+
<div class="structure-graph__eyebrow">Object Inspector</div>
|
|
120
|
+
<button
|
|
121
|
+
class="query-history-icon-button"
|
|
122
|
+
data-structure-graph-action="toggle-inspector"
|
|
123
|
+
type="button"
|
|
124
|
+
aria-label="Hide inspector"
|
|
125
|
+
>
|
|
126
|
+
<span class="material-symbols-outlined text-[18px]">close</span>
|
|
127
|
+
</button>
|
|
131
128
|
</div>
|
|
129
|
+
<div class="structure-graph__title">${escapeHtml(detail.name)}</div>
|
|
130
|
+
${
|
|
131
|
+
detail.tableName
|
|
132
|
+
? `
|
|
133
|
+
<div class="structure-graph__subtitle">
|
|
134
|
+
TABLE ${escapeHtml(detail.tableName)}
|
|
135
|
+
</div>
|
|
136
|
+
`
|
|
137
|
+
: ''
|
|
138
|
+
}
|
|
132
139
|
</div>
|
|
133
140
|
|
|
134
141
|
<div class="structure-graph__summary">
|
|
@@ -255,6 +262,8 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
|
|
|
255
262
|
<span class="material-symbols-outlined text-sm">table_rows</span>
|
|
256
263
|
Open Data
|
|
257
264
|
</button>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="structure-graph__toolbar-inspector">
|
|
258
267
|
<button
|
|
259
268
|
class="${toolbarButtonClass}"
|
|
260
269
|
data-structure-graph-action="toggle-inspector"
|
|
@@ -280,6 +289,11 @@ function renderGraphSurface(structure, selectedName, detail, detailLoading, tabl
|
|
|
280
289
|
The active database does not expose table metadata for the schema graph.
|
|
281
290
|
</p>
|
|
282
291
|
</div>
|
|
292
|
+
<div
|
|
293
|
+
class="structure-graph__edge-readout"
|
|
294
|
+
data-structure-graph-edge-readout
|
|
295
|
+
hidden
|
|
296
|
+
></div>
|
|
283
297
|
</div>
|
|
284
298
|
|
|
285
299
|
<aside
|