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
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ SQLite Hub keeps that workflow sharp:
|
|
|
23
23
|
- copy result columns with formatting, headers, first-10 previews, TXT export, and Markdown todo export
|
|
24
24
|
- keep database-scoped Markdown documents with previews, autosave, imports, exports, and saved-query inserts
|
|
25
25
|
- switch between recent databases with sidebar quick picks
|
|
26
|
-
- create
|
|
26
|
+
- create verified local backups of the active database and get safety prompts before risky operations
|
|
27
27
|
- run and format SQL in a syntax-highlighted editor with history, messages, and performance metrics
|
|
28
28
|
- keep large interactive query results bounded while full exports remain available
|
|
29
29
|
- turn query-history results into local charts
|
|
@@ -176,9 +176,18 @@ The Settings view reports the installed SQLite Hub version and the actual SQLite
|
|
|
176
176
|
|
|
177
177
|
The active database footer in the sidebar opens a quick-pick panel with the five most recent databases, so you can switch databases without going back to the Connections view.
|
|
178
178
|
|
|
179
|
-
###
|
|
179
|
+
### Backup Manager
|
|
180
180
|
|
|
181
|
-
Create
|
|
181
|
+
Create verified local backups of the active SQLite database, review backup metadata, edit backup notes, download backup files, restore verified backups, and delete managed backups from the Backups view. SQLite Hub stores backup files under its local app-state backup directory by connection id and keeps a `manifest.json` beside each database's backup files. Each backup is created through SQLite's backup API, hashed with SHA-256, and verified with `PRAGMA quick_check` before it is marked as verified.
|
|
182
|
+
|
|
183
|
+
SQLite Hub also proposes a safety backup before operations that can be hard to undo:
|
|
184
|
+
|
|
185
|
+
- SQL Editor execution when the statement set contains `DROP TABLE`, `ALTER TABLE`, `CREATE TABLE`, `CREATE INDEX`, `CREATE VIEW`, `CREATE TRIGGER`, `DROP INDEX`, `DROP VIEW`, `DROP TRIGGER`, `REINDEX`, or `VACUUM`.
|
|
186
|
+
- SQL Editor execution with multiple schema-affecting statements in one run; this is treated as a migration and suggests a `Before migration` backup.
|
|
187
|
+
- SQL import into the currently active database when the dump is larger than 5 MB or contains more than 1,000 parsed SQL statements.
|
|
188
|
+
- Restore from a managed backup, because the current active database file will be replaced.
|
|
189
|
+
|
|
190
|
+
The safety dialog lets you create the backup and continue, continue without creating one, or cancel the operation.
|
|
182
191
|
|
|
183
192
|
### Local-first
|
|
184
193
|
|
|
@@ -205,200 +214,12 @@ npm install -g sqlite-hub
|
|
|
205
214
|
sqlite-hub --port:4174
|
|
206
215
|
```
|
|
207
216
|
|
|
208
|
-
## CLI
|
|
209
|
-
|
|
210
|
-
SQLite Hub ships with a built-in CLI that lets you start the app or query information about your imported databases directly from the terminal.
|
|
211
|
-
|
|
212
|
-
### Start the app
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
sqlite-hub # start on default port 4173
|
|
216
|
-
sqlite-hub --port:4174 # start on a custom port
|
|
217
|
-
sqlite-hub --open # open SQLite Hub in the browser
|
|
218
|
-
sqlite-hub --info # show port, URL, versions, and update status
|
|
219
|
-
sqlite-hub --help # show help text
|
|
220
|
-
sqlite-hub --version # show version number
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### List all imported databases
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
sqlite-hub --database
|
|
227
|
-
sqlite-hub -d
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Shows an overview of all databases that have been opened in SQLite Hub, including:
|
|
231
|
-
|
|
232
|
-
- database name/label
|
|
233
|
-
- file path
|
|
234
|
-
- file size
|
|
235
|
-
- last opened timestamp
|
|
236
|
-
- read-only status
|
|
237
|
-
|
|
238
|
-
### Query specific database details
|
|
239
|
-
|
|
240
|
-
Retrieve details about a single database by its name (case-insensitive):
|
|
241
|
-
|
|
242
|
-
```bash
|
|
243
|
-
sqlite-hub --database:Billly --path # get the file path
|
|
244
|
-
sqlite-hub --database:Billly --size # get the file size
|
|
245
|
-
sqlite-hub --database:Billly --lastopened # get last opened timestamp
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### List all tables in a database
|
|
249
|
-
|
|
250
|
-
```bash
|
|
251
|
-
sqlite-hub --database:Billly --tables
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
Opens the database in read-only mode and prints all table names, sorted alphabetically.
|
|
255
|
-
|
|
256
|
-
### Inspect a table
|
|
257
|
-
|
|
258
|
-
```bash
|
|
259
|
-
sqlite-hub --database:Billly --table:companies
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Prints table metadata such as columns, primary keys, foreign keys, indexes, row count, and row identity strategy.
|
|
263
|
-
|
|
264
|
-
### SQL Editor - Raw SQL And Saved Queries
|
|
265
|
-
|
|
266
|
-
Execute raw SQL through the same SQL Editor execution path used by the app:
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
sqlite-hub --database:Unit-00 --query:"SELECT * FROM companies LIMIT 10"
|
|
270
|
-
sqlite-hub --database:Unit-00 --query:"SELECT * FROM companies LIMIT 10" --store:"Company Sample"
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
Raw CLI queries are recorded in Query History. Add `--store:"name"` to title the
|
|
274
|
-
history item and mark it as saved. Raw SQL execution is rejected when the target
|
|
275
|
-
database is marked read-only.
|
|
276
|
-
|
|
277
|
-
List all saved queries for a database:
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
sqlite-hub --database:Unit-00 --queries
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
Execute a specific saved query by name:
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
sqlite-hub --database:Unit-00 --execute:"15min Posting Buckets without id 96"
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
This searches the query history for the given database, finds the matching saved query by title, executes it, and returns all results with metadata (row count, columns, timing, and data).
|
|
290
|
-
|
|
291
|
-
Show the saved query SQL without executing it:
|
|
292
|
-
|
|
293
|
-
```bash
|
|
294
|
-
sqlite-hub --database:Unit-00 --saved-query:"Stock Winners"
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
Show the saved notes for a query:
|
|
298
|
-
|
|
299
|
-
```bash
|
|
300
|
-
sqlite-hub --database:Unit-00 --notes:"TOP25 Loser and Winner EOD, T1, T3, T5"
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
Export a saved query using the same CSV, TSV, Markdown, and JSON export logic as the SQL Editor:
|
|
304
|
-
|
|
305
|
-
```bash
|
|
306
|
-
sqlite-hub --database:Unit-00 --export:"Stock Winners" --format:csv
|
|
307
|
-
sqlite-hub --database:Unit-00 --export:"Stock Winners" --format:tsv
|
|
308
|
-
sqlite-hub --database:Unit-00 --export:"Stock Winners" --format:md
|
|
309
|
-
sqlite-hub --database:Unit-00 --export:"Stock Winners" --format:json
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
The export is written to the current working directory using the generated query export filename.
|
|
217
|
+
## CLI
|
|
313
218
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
```bash
|
|
319
|
-
sqlite-hub --database:Unit-00 --documents
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Print one document's Markdown content:
|
|
323
|
-
|
|
324
|
-
```bash
|
|
325
|
-
sqlite-hub --database:Unit-00 --documents:"Research Notes"
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
Export one document as a `.md` file into the current working directory:
|
|
329
|
-
|
|
330
|
-
```bash
|
|
331
|
-
sqlite-hub --database:Unit-00 --documents:"Research Notes" --export
|
|
332
|
-
sqlite-hub --database:Unit-00 --documents:"Research Notes--export"
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
Documents can be matched by id, filename, title, or a partial filename/title match.
|
|
336
|
-
|
|
337
|
-
### Row JSON export
|
|
338
|
-
|
|
339
|
-
Export a single row as JSON by primary key or rowid, using the same row-shaping logic as the Row Editor:
|
|
340
|
-
|
|
341
|
-
```bash
|
|
342
|
-
sqlite-hub --database:Unit-00 --table:companies --export:0a754aba373d34972998792a0be4333c
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### Available flags
|
|
346
|
-
|
|
347
|
-
| Flag | Description |
|
|
348
|
-
| --------------------------------------------------------------- | ----------------------------------------------- |
|
|
349
|
-
| `--help`, `-h` | Show help text |
|
|
350
|
-
| `--version`, `-v` | Show version number |
|
|
351
|
-
| `--info` | Show port, URL, versions, and update status |
|
|
352
|
-
| `--open` | Open SQLite Hub in the browser |
|
|
353
|
-
| `--port:PORT` | Start the server on a custom port |
|
|
354
|
-
| `--database`, `-d` | List all imported databases |
|
|
355
|
-
| `--database:name` | Select a database by name or id |
|
|
356
|
-
| `--database:name --path` | Get the file path of a database |
|
|
357
|
-
| `--database:name --size` | Get the size of a database |
|
|
358
|
-
| `--database:name --lastopened` | Get the last opened timestamp |
|
|
359
|
-
| `--database:name --tables` | Get all table names from a database |
|
|
360
|
-
| `--database:name --queries` | List saved queries for a database |
|
|
361
|
-
| `--database:name --query:"sql"` | Execute raw SQL and record it in Query History |
|
|
362
|
-
| `--database:name --query:"sql" --store:"name"` | Save a raw query in Query History with a name |
|
|
363
|
-
| `--database:name --execute:"query"` | Execute a saved query by name |
|
|
364
|
-
| `--database:name --saved-query:"query"` | Print a saved query by name |
|
|
365
|
-
| `--database:name --notes:"query"` | Print saved notes for a query |
|
|
366
|
-
| `--database:name --export:"query" --format:csv\|tsv\|md\|json` | Set query export format |
|
|
367
|
-
| `--database:name --documents` | List Markdown documents for a database |
|
|
368
|
-
| `--database:name --documents:"document"` | Print a document's Markdown content |
|
|
369
|
-
| `--database:name --documents:"document" --export` | Export a document as Markdown |
|
|
370
|
-
| `--database:name --table:"table"` | Print table metadata |
|
|
371
|
-
| `--database:name --table:"table" --export:"pk"` | Export one row as JSON |
|
|
372
|
-
|
|
373
|
-
Legacy aliases such as `--database-path:name`, `--database-size:name`, `--database-lastopened:name`, `--database-tables:name`, and `--database:name --sqleditor:"query"` still work.
|
|
374
|
-
|
|
375
|
-
### SQL editor CLI example
|
|
376
|
-
|
|
377
|
-
Saved queries created in the graphical SQL Editor can also be executed through the CLI. To execute one, run:
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
sqlite-hub --database:Unit-00 --execute:"Group by creation Year"
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
Example output:
|
|
384
|
-
|
|
385
|
-
```bash
|
|
386
|
-
Executing: Group by creation Year
|
|
387
|
-
SQL: SELECT STRFTIME('%Y', creation_time, 'unixepoch') AS creation_year, COUNT(*) AS channel_count FROM channels WHERE creation_time IS NOT NU...
|
|
388
|
-
────────────────────────────────────────────────────────────
|
|
389
|
-
|
|
390
|
-
Statement count: 1
|
|
391
|
-
Timing: 1ms
|
|
392
|
-
|
|
393
|
-
Statement 1 (resultSet):
|
|
394
|
-
Rows: 3
|
|
395
|
-
Columns: creation_year, channel_count
|
|
396
|
-
|
|
397
|
-
Results:
|
|
398
|
-
[0] 2024 | 11
|
|
399
|
-
[1] 2025 | 47
|
|
400
|
-
[2] 2026 | 40
|
|
401
|
-
```
|
|
219
|
+
SQLite Hub ships with a built-in CLI for starting the app, inspecting imported
|
|
220
|
+
databases, executing raw or saved SQL, exporting query results, exporting single
|
|
221
|
+
rows as JSON, and working with Markdown documents. See the
|
|
222
|
+
[CLI documentation](./docs/CLI.md) for commands, flags, and examples.
|
|
402
223
|
|
|
403
224
|
## API
|
|
404
225
|
|
package/bin/sqlite-hub.js
CHANGED
|
@@ -566,7 +566,9 @@ function printExecutionResult(result) {
|
|
|
566
566
|
}
|
|
567
567
|
|
|
568
568
|
function executeSavedQuery({ databaseService, conn, queryName }) {
|
|
569
|
-
const { query: matchingQuery, result } = databaseService.executeSavedQuery(conn.id, queryName
|
|
569
|
+
const { query: matchingQuery, result } = databaseService.executeSavedQuery(conn.id, queryName, {
|
|
570
|
+
executedBy: "cli",
|
|
571
|
+
});
|
|
570
572
|
|
|
571
573
|
console.log(`\nExecuting: ${getQueryTitle(matchingQuery)}`);
|
|
572
574
|
console.log(`SQL: ${matchingQuery.previewSql || matchingQuery.rawSql}`);
|
|
@@ -576,7 +578,10 @@ function executeSavedQuery({ databaseService, conn, queryName }) {
|
|
|
576
578
|
}
|
|
577
579
|
|
|
578
580
|
function executeRawQuery({ databaseService, conn, sql, storeName = null }) {
|
|
579
|
-
const { result, storedQuery } = databaseService.executeRawQuery(conn.id, sql, {
|
|
581
|
+
const { result, storedQuery } = databaseService.executeRawQuery(conn.id, sql, {
|
|
582
|
+
storeName,
|
|
583
|
+
executedBy: "cli",
|
|
584
|
+
});
|
|
580
585
|
|
|
581
586
|
console.log(`\nExecuting raw SQL against: ${conn.label}`);
|
|
582
587
|
console.log('─'.repeat(60));
|
package/frontend/js/api.js
CHANGED
|
@@ -105,6 +105,13 @@ export function importSql(payload) {
|
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
export function previewImportSql(payload) {
|
|
109
|
+
return request("/api/connections/import-sql/preview", {
|
|
110
|
+
method: "POST",
|
|
111
|
+
body: payload,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
108
115
|
export function selectActiveConnection(id) {
|
|
109
116
|
return request("/api/connections/select-active", {
|
|
110
117
|
method: "POST",
|
|
@@ -131,6 +138,59 @@ export function createActiveConnectionBackup() {
|
|
|
131
138
|
});
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
export function getBackups(options = {}) {
|
|
142
|
+
const params = new URLSearchParams();
|
|
143
|
+
|
|
144
|
+
if (options.all) {
|
|
145
|
+
params.set("all", "true");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const query = params.toString();
|
|
149
|
+
return request(`/api/backups${query ? `?${query}` : ""}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function getBackup(backupId) {
|
|
153
|
+
return request(`/api/backups/${encodeURIComponent(backupId)}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createBackup(payload = {}) {
|
|
157
|
+
return request("/api/backups", {
|
|
158
|
+
method: "POST",
|
|
159
|
+
body: payload,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function updateBackup(backupId, payload = {}) {
|
|
164
|
+
return request(`/api/backups/${encodeURIComponent(backupId)}`, {
|
|
165
|
+
method: "PATCH",
|
|
166
|
+
body: payload,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function restoreBackup(backupId) {
|
|
171
|
+
return request(`/api/backups/${encodeURIComponent(backupId)}/restore`, {
|
|
172
|
+
method: "POST",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function verifyBackup(backupId) {
|
|
177
|
+
return request(`/api/backups/${encodeURIComponent(backupId)}/verify`, {
|
|
178
|
+
method: "POST",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function deleteBackup(backupId) {
|
|
183
|
+
return request(`/api/backups/${encodeURIComponent(backupId)}`, {
|
|
184
|
+
method: "DELETE",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function downloadBackup(backupId) {
|
|
189
|
+
return download(`/api/backups/${encodeURIComponent(backupId)}/download`, {
|
|
190
|
+
fallbackFilename: "backup.sqlite",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
134
194
|
export function getOverview() {
|
|
135
195
|
return request("/api/db/overview");
|
|
136
196
|
}
|
package/frontend/js/app.js
CHANGED
|
@@ -24,6 +24,7 @@ import { renderTopNav } from './components/topNav.js';
|
|
|
24
24
|
import { createRouter } from './router.js';
|
|
25
25
|
import {
|
|
26
26
|
createActiveConnectionBackup,
|
|
27
|
+
downloadBackup,
|
|
27
28
|
createSettingsApiToken,
|
|
28
29
|
chooseOpenDatabasePath,
|
|
29
30
|
chooseCreateDatabasePath,
|
|
@@ -53,6 +54,10 @@ import {
|
|
|
53
54
|
openDeleteEditorRowModal,
|
|
54
55
|
openDeleteQueryHistoryModal,
|
|
55
56
|
openDeleteSettingsApiTokenModal,
|
|
57
|
+
openCreateBackupModal,
|
|
58
|
+
openEditBackupNotesModal,
|
|
59
|
+
openDeleteBackupModal,
|
|
60
|
+
openRestoreBackupModal,
|
|
56
61
|
openDeleteQueryChartModal,
|
|
57
62
|
openDataExportModal,
|
|
58
63
|
openDeleteDocumentModal,
|
|
@@ -68,6 +73,7 @@ import {
|
|
|
68
73
|
openDataRowUpdatePreview,
|
|
69
74
|
openEditorRowUpdatePreview,
|
|
70
75
|
refreshCurrentRoute,
|
|
76
|
+
refreshBackups,
|
|
71
77
|
refreshMediaTaggingPreview,
|
|
72
78
|
removeConnection,
|
|
73
79
|
removeCurrentMediaTag,
|
|
@@ -83,8 +89,10 @@ import {
|
|
|
83
89
|
selectConnection,
|
|
84
90
|
selectQueryHistoryItem,
|
|
85
91
|
selectStructureEntry,
|
|
92
|
+
setDocumentsSearchQuery,
|
|
86
93
|
setTableDesignerSearchQuery,
|
|
87
94
|
setTableDesignerSqlPreviewVisibility,
|
|
95
|
+
toggleTableDesignerTablesPanel,
|
|
88
96
|
setDataTableSearchQuery,
|
|
89
97
|
setStructureTableSearchQuery,
|
|
90
98
|
toggleStructureTablesPanel,
|
|
@@ -103,6 +111,7 @@ import {
|
|
|
103
111
|
setEditorPanelVisibility,
|
|
104
112
|
setEditorTab,
|
|
105
113
|
submitDeleteChartConfirmation,
|
|
114
|
+
submitDeleteBackupConfirmation,
|
|
106
115
|
submitDeleteDocumentConfirmation,
|
|
107
116
|
submitDocumentInsertNote,
|
|
108
117
|
submitDocumentInsertTable,
|
|
@@ -110,6 +119,9 @@ import {
|
|
|
110
119
|
submitCreateMediaTaggingMappingTable,
|
|
111
120
|
submitDeleteQueryHistoryConfirmation,
|
|
112
121
|
submitDeleteSettingsApiTokenConfirmation,
|
|
122
|
+
submitBackupSafetyChoice,
|
|
123
|
+
submitCreateBackupConfirmation,
|
|
124
|
+
submitEditBackupNotesConfirmation,
|
|
113
125
|
submitRowUpdatePreviewConfirmation,
|
|
114
126
|
setQueryHistoryPanelVisibility,
|
|
115
127
|
sortDataTableByColumn,
|
|
@@ -127,6 +139,7 @@ import {
|
|
|
127
139
|
toggleChartsResultsPanel,
|
|
128
140
|
toggleChartsSqlPanel,
|
|
129
141
|
toggleCurrentDocumentTodo,
|
|
142
|
+
toggleDocumentsPanel,
|
|
130
143
|
toggleDocumentsPane,
|
|
131
144
|
setMediaTaggingWorkflowMediaDetailsVisible,
|
|
132
145
|
setMediaTaggingWorkflowMediaRotationDegrees,
|
|
@@ -157,6 +170,7 @@ import {
|
|
|
157
170
|
removeCurrentTableDesignerColumn,
|
|
158
171
|
} from './store.js';
|
|
159
172
|
import { renderChartsDetail, renderChartsView } from './views/charts.js';
|
|
173
|
+
import { renderBackupsView } from './views/backups.js';
|
|
160
174
|
import { renderConnectionsView } from './views/connections.js';
|
|
161
175
|
import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
162
176
|
import { renderDocumentsView } from './views/documents.js';
|
|
@@ -239,6 +253,7 @@ const DOCUMENT_AUTOSAVE_DELAY_MS = 5000;
|
|
|
239
253
|
const APP_TITLE = 'SQLite Hub';
|
|
240
254
|
const ROUTE_TITLE_SEGMENTS = {
|
|
241
255
|
connections: 'Connections',
|
|
256
|
+
backups: 'Backups',
|
|
242
257
|
overview: 'Overview',
|
|
243
258
|
data: 'Data',
|
|
244
259
|
structure: 'Structure',
|
|
@@ -376,21 +391,19 @@ function syncMediaTaggingCurrentMediaUi(button, detailsVisible) {
|
|
|
376
391
|
}
|
|
377
392
|
|
|
378
393
|
const nextVisible = Boolean(detailsVisible);
|
|
379
|
-
const expandedLabel = button.dataset.expandedLabel || '
|
|
380
|
-
const collapsedLabel = button.dataset.collapsedLabel || 'Show
|
|
394
|
+
const expandedLabel = button.dataset.expandedLabel || 'Hide Viewer';
|
|
395
|
+
const collapsedLabel = button.dataset.collapsedLabel || 'Show Viewer';
|
|
381
396
|
preview.classList.toggle('media-tagging-preview--meta-hidden', !nextVisible);
|
|
397
|
+
button.classList.toggle('is-active', !nextVisible);
|
|
382
398
|
button.dataset.nextValue = nextVisible ? 'false' : 'true';
|
|
383
399
|
button.setAttribute('aria-expanded', nextVisible ? 'true' : 'false');
|
|
400
|
+
button.setAttribute('aria-pressed', nextVisible ? 'false' : 'true');
|
|
384
401
|
|
|
385
|
-
|
|
386
|
-
const icon = document.createElement('span');
|
|
402
|
+
const icon = document.createElement('span');
|
|
387
403
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
} else {
|
|
392
|
-
button.textContent = collapsedLabel;
|
|
393
|
-
}
|
|
404
|
+
icon.className = 'material-symbols-outlined';
|
|
405
|
+
icon.textContent = nextVisible ? 'visibility_off' : 'visibility';
|
|
406
|
+
button.replaceChildren(icon, document.createTextNode(` ${nextVisible ? expandedLabel : collapsedLabel}`));
|
|
394
407
|
|
|
395
408
|
return true;
|
|
396
409
|
}
|
|
@@ -1053,6 +1066,8 @@ function resolveView(state) {
|
|
|
1053
1066
|
return renderLandingView(state);
|
|
1054
1067
|
case 'connections':
|
|
1055
1068
|
return renderConnectionsView(state);
|
|
1069
|
+
case 'backups':
|
|
1070
|
+
return renderBackupsView(state);
|
|
1056
1071
|
case 'overview':
|
|
1057
1072
|
return renderOverviewView(state);
|
|
1058
1073
|
case 'charts':
|
|
@@ -1585,6 +1600,27 @@ function formatCurrentQuery() {
|
|
|
1585
1600
|
showToast('SQL query formatted.', 'success');
|
|
1586
1601
|
}
|
|
1587
1602
|
|
|
1603
|
+
async function copyCurrentQueryToClipboard() {
|
|
1604
|
+
const currentQuery = getState().editor.sqlText ?? '';
|
|
1605
|
+
|
|
1606
|
+
if (!currentQuery.trim()) {
|
|
1607
|
+
showToast('No SQL query to copy.', 'alert');
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
if (!navigator.clipboard?.writeText) {
|
|
1612
|
+
showToast('Clipboard API is not available.', 'alert');
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
try {
|
|
1617
|
+
await navigator.clipboard.writeText(currentQuery);
|
|
1618
|
+
showToast('SQL query copied.', 'success');
|
|
1619
|
+
} catch (error) {
|
|
1620
|
+
showToast('SQL query could not be copied.', 'alert');
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1588
1624
|
const OPENABLE_URL_PATTERN = /^https?:\/\/[^\s<>"']+$/i;
|
|
1589
1625
|
|
|
1590
1626
|
function getOpenableUrl(value) {
|
|
@@ -1795,6 +1831,14 @@ function closeCopyColumnMenus(exceptMenu = null) {
|
|
|
1795
1831
|
});
|
|
1796
1832
|
}
|
|
1797
1833
|
|
|
1834
|
+
function closeDropdownButtons(exceptDropdown = null) {
|
|
1835
|
+
document.querySelectorAll('[data-dropdown-button][open]').forEach(dropdown => {
|
|
1836
|
+
if (dropdown !== exceptDropdown && dropdown instanceof HTMLDetailsElement) {
|
|
1837
|
+
dropdown.open = false;
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1798
1842
|
function closeSidebarDatabasePickers(exceptPicker = null) {
|
|
1799
1843
|
document.querySelectorAll('.sidebar-db-picker[open]').forEach(picker => {
|
|
1800
1844
|
if (picker !== exceptPicker && picker instanceof HTMLDetailsElement) {
|
|
@@ -2446,6 +2490,33 @@ async function handleAction(actionNode) {
|
|
|
2446
2490
|
case 'create-backup':
|
|
2447
2491
|
await createActiveConnectionBackup();
|
|
2448
2492
|
return;
|
|
2493
|
+
case 'open-create-backup-modal':
|
|
2494
|
+
openCreateBackupModal();
|
|
2495
|
+
return;
|
|
2496
|
+
case 'refresh-backups':
|
|
2497
|
+
await refreshBackups();
|
|
2498
|
+
return;
|
|
2499
|
+
case 'open-restore-backup-modal':
|
|
2500
|
+
openRestoreBackupModal(actionNode.dataset.backupId);
|
|
2501
|
+
return;
|
|
2502
|
+
case 'open-edit-backup-notes-modal':
|
|
2503
|
+
openEditBackupNotesModal(actionNode.dataset.backupId);
|
|
2504
|
+
return;
|
|
2505
|
+
case 'open-delete-backup-modal':
|
|
2506
|
+
openDeleteBackupModal(actionNode.dataset.backupId);
|
|
2507
|
+
return;
|
|
2508
|
+
case 'download-backup':
|
|
2509
|
+
await downloadBackup(actionNode.dataset.backupId);
|
|
2510
|
+
return;
|
|
2511
|
+
case 'backup-safety-create':
|
|
2512
|
+
await submitBackupSafetyChoice('create');
|
|
2513
|
+
return;
|
|
2514
|
+
case 'backup-safety-continue':
|
|
2515
|
+
await submitBackupSafetyChoice('continue');
|
|
2516
|
+
return;
|
|
2517
|
+
case 'backup-safety-cancel':
|
|
2518
|
+
await submitBackupSafetyChoice('cancel');
|
|
2519
|
+
return;
|
|
2449
2520
|
case 'copy-media-tags': {
|
|
2450
2521
|
const tags = getState().mediaTagging.tags ?? [];
|
|
2451
2522
|
const formattedTags = tags
|
|
@@ -2476,6 +2547,9 @@ async function handleAction(actionNode) {
|
|
|
2476
2547
|
case 'format-current-query':
|
|
2477
2548
|
formatCurrentQuery();
|
|
2478
2549
|
return;
|
|
2550
|
+
case 'copy-current-query':
|
|
2551
|
+
await copyCurrentQueryToClipboard();
|
|
2552
|
+
return;
|
|
2479
2553
|
case 'delete-data-row':
|
|
2480
2554
|
openDeleteDataRowModal(actionNode.dataset.rowIndex);
|
|
2481
2555
|
return;
|
|
@@ -2656,6 +2730,12 @@ async function handleAction(actionNode) {
|
|
|
2656
2730
|
case 'toggle-structure-tables':
|
|
2657
2731
|
toggleStructureTablesPanel();
|
|
2658
2732
|
return;
|
|
2733
|
+
case 'toggle-table-designer-tables':
|
|
2734
|
+
toggleTableDesignerTablesPanel();
|
|
2735
|
+
return;
|
|
2736
|
+
case 'toggle-documents-panel':
|
|
2737
|
+
toggleDocumentsPanel();
|
|
2738
|
+
return;
|
|
2659
2739
|
case 'select-structure-entry':
|
|
2660
2740
|
if (actionNode.dataset.entryName) {
|
|
2661
2741
|
await selectStructureEntry(actionNode.dataset.entryName);
|
|
@@ -2864,6 +2944,20 @@ document.addEventListener('click', event => {
|
|
|
2864
2944
|
});
|
|
2865
2945
|
}
|
|
2866
2946
|
|
|
2947
|
+
const dropdownButton = target.closest('[data-dropdown-button]');
|
|
2948
|
+
|
|
2949
|
+
if (!dropdownButton) {
|
|
2950
|
+
closeDropdownButtons();
|
|
2951
|
+
} else if (target.closest('.dropdown-button__toggle')) {
|
|
2952
|
+
window.requestAnimationFrame(() => {
|
|
2953
|
+
if (dropdownButton instanceof HTMLDetailsElement && dropdownButton.open) {
|
|
2954
|
+
closeDropdownButtons(dropdownButton);
|
|
2955
|
+
}
|
|
2956
|
+
});
|
|
2957
|
+
} else if (target.closest('.dropdown-button__item')) {
|
|
2958
|
+
closeDropdownButtons();
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2867
2961
|
const sidebarDatabasePicker = target.closest('.sidebar-db-picker');
|
|
2868
2962
|
|
|
2869
2963
|
if (!sidebarDatabasePicker) {
|
|
@@ -2983,12 +3077,30 @@ document.addEventListener('keydown', event => {
|
|
|
2983
3077
|
return;
|
|
2984
3078
|
}
|
|
2985
3079
|
|
|
3080
|
+
if (document.querySelector('[data-dropdown-button][open]')) {
|
|
3081
|
+
event.preventDefault();
|
|
3082
|
+
closeDropdownButtons();
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
2986
3086
|
if (document.querySelector('.sidebar-db-picker[open]')) {
|
|
2987
3087
|
event.preventDefault();
|
|
2988
3088
|
closeSidebarDatabasePickers();
|
|
2989
3089
|
return;
|
|
2990
3090
|
}
|
|
2991
3091
|
|
|
3092
|
+
if (state.route.name === 'charts' && state.charts.detailPanelVisible) {
|
|
3093
|
+
event.preventDefault();
|
|
3094
|
+
setChartsDetailPanelVisibility(false);
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3098
|
+
if (state.editor.historySelectedId !== null || state.editor.historyDetail) {
|
|
3099
|
+
event.preventDefault();
|
|
3100
|
+
clearQueryHistorySelection();
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
2992
3104
|
if (
|
|
2993
3105
|
state.route.name === 'data' &&
|
|
2994
3106
|
(typeof state.dataBrowser.selectedRowIndex === 'number' || Boolean(state.dataBrowser.selectedRow))
|
|
@@ -3051,6 +3163,11 @@ document.addEventListener('input', event => {
|
|
|
3051
3163
|
return;
|
|
3052
3164
|
}
|
|
3053
3165
|
|
|
3166
|
+
if (bindNode.dataset.bind === 'documents-search') {
|
|
3167
|
+
setDocumentsSearchQuery(bindNode.value);
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3054
3171
|
if (bindNode.dataset.bind === 'data-search-query') {
|
|
3055
3172
|
void setDataSearchQuery(bindNode.value);
|
|
3056
3173
|
return;
|
|
@@ -3413,6 +3530,19 @@ document.addEventListener('submit', async event => {
|
|
|
3413
3530
|
case 'delete-row-confirm':
|
|
3414
3531
|
await submitDeleteRowConfirmation();
|
|
3415
3532
|
return;
|
|
3533
|
+
case 'create-backup':
|
|
3534
|
+
await submitCreateBackupConfirmation({
|
|
3535
|
+
name: String(formData.get('name') ?? ''),
|
|
3536
|
+
notes: String(formData.get('notes') ?? ''),
|
|
3537
|
+
type: String(formData.get('type') ?? 'manual'),
|
|
3538
|
+
});
|
|
3539
|
+
return;
|
|
3540
|
+
case 'edit-backup-notes':
|
|
3541
|
+
await submitEditBackupNotesConfirmation(String(formData.get('notes') ?? ''));
|
|
3542
|
+
return;
|
|
3543
|
+
case 'delete-backup-confirm':
|
|
3544
|
+
await submitDeleteBackupConfirmation();
|
|
3545
|
+
return;
|
|
3416
3546
|
case 'save-query-chart':
|
|
3417
3547
|
await saveCurrentQueryChartDraft();
|
|
3418
3548
|
return;
|