sqlite-hub 1.2.0 → 1.5.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 +113 -71
- package/bin/sqlite-hub.js +723 -116
- package/docs/API.md +152 -0
- package/docs/CLI.md +249 -0
- package/docs/CLI_API_PARITY.md +61 -0
- package/docs/changelog.md +149 -0
- package/docs/guidelines/AGENTS.md +32 -0
- package/docs/guidelines/DESIGN.md +55 -0
- package/docs/shortkeys.md +27 -0
- package/docs/todo.md +4 -0
- package/examples/api/generate-types.js +52 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_1_1920.webp +0 -0
- package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_1_1920.webp +0 -0
- package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_1_1920.webp +0 -0
- package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_1_1920.webp +0 -0
- package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/overview_1_1920.webp +0 -0
- package/frontend/assets/mockups/settings_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_1_1920.webp +0 -0
- package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
- package/frontend/js/api.js +63 -0
- package/frontend/js/app.js +310 -10
- package/frontend/js/components/connectionCard.js +4 -2
- package/frontend/js/components/emptyState.js +4 -4
- package/frontend/js/components/generateTypesDropdown.js +33 -0
- package/frontend/js/components/metricCard.js +1 -1
- package/frontend/js/components/modal.js +555 -21
- package/frontend/js/components/pageHeader.js +1 -1
- package/frontend/js/components/queryHistoryDetail.js +3 -3
- package/frontend/js/components/queryHistoryHeader.js +1 -1
- package/frontend/js/components/queryHistoryPanel.js +1 -1
- package/frontend/js/components/rowEditorPanel.js +13 -3
- package/frontend/js/components/sidebar.js +41 -7
- package/frontend/js/components/topNav.js +2 -14
- package/frontend/js/router.js +38 -6
- package/frontend/js/store.js +870 -8
- package/frontend/js/utils/inputClear.js +36 -0
- package/frontend/js/utils/syntheticData.js +240 -0
- package/frontend/js/views/backups.js +581 -37
- package/frontend/js/views/charts.js +9 -9
- package/frontend/js/views/connections.js +2 -2
- package/frontend/js/views/data.js +17 -1
- package/frontend/js/views/documents.js +3 -3
- package/frontend/js/views/editor.js +6 -6
- package/frontend/js/views/logs.js +339 -0
- package/frontend/js/views/mediaTagging.js +7 -5
- package/frontend/js/views/overview.js +3 -3
- package/frontend/js/views/settings.js +80 -30
- package/frontend/js/views/structure.js +12 -1
- package/frontend/js/views/tableAdvisor.js +385 -0
- package/frontend/js/views/tableDesigner.js +6 -0
- package/frontend/styles/base.css +1 -40
- package/frontend/styles/components.css +258 -232
- package/frontend/styles/structure-graph.css +32 -68
- package/frontend/styles/tailwind.css +8 -4
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +28 -21
- package/frontend/styles/views.css +102 -261
- package/package.json +18 -2
- package/server/routes/backups.js +18 -2
- package/server/routes/data.js +45 -0
- package/server/routes/externalApi.js +321 -2
- package/server/routes/logs.js +127 -0
- package/server/routes/structure.js +22 -0
- package/server/server.js +3 -0
- package/server/services/databaseCommandService.js +59 -0
- package/server/services/sqlite/backupDiff.js +914 -0
- package/server/services/sqlite/backupService.js +77 -1
- package/server/services/sqlite/dataBrowserService.js +36 -0
- package/server/services/sqlite/introspection.js +226 -22
- package/server/services/sqlite/structureService.js +7 -0
- package/server/services/sqlite/syntheticDataGenerator.js +728 -0
- package/server/services/sqlite/tableAdvisor.js +790 -0
- package/server/services/storage/appStateStore.js +773 -169
- package/server/services/typeGenerationService.js +663 -0
- package/tailwind.config.cjs +0 -1
- package/.github/funding.yml +0 -2
- package/.github/workflows/ci.yml +0 -36
- package/database.sqlite +0 -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/tests/api-token-auth.test.js +0 -279
- package/tests/backup-manager.test.js +0 -140
- package/tests/backups-view.test.js +0 -70
- package/tests/charts-height-preset-storage.test.js +0 -60
- package/tests/charts-route-state.test.js +0 -144
- package/tests/check-constraint-options.test.js +0 -90
- package/tests/cli-args.test.js +0 -113
- package/tests/cli-service-delegation.test.js +0 -140
- package/tests/connection-removal.test.js +0 -52
- package/tests/connections-file-dialog-route.test.js +0 -89
- package/tests/copy-column-modal.test.js +0 -131
- package/tests/database-command-service.test.js +0 -174
- package/tests/database-documents.test.js +0 -85
- package/tests/documents-view.test.js +0 -132
- package/tests/dropdown-button.test.js +0 -101
- package/tests/email-preview.test.js +0 -89
- package/tests/export-blob.test.js +0 -99
- package/tests/export-filenames.test.js +0 -38
- package/tests/file-path-preview.test.js +0 -165
- package/tests/form-controls.test.js +0 -34
- package/tests/json-preview.test.js +0 -49
- package/tests/local-request-security.test.js +0 -85
- package/tests/markdown-documents.test.js +0 -103
- package/tests/native-file-dialog.test.js +0 -105
- package/tests/query-editor.test.js +0 -28
- package/tests/query-history-detail.test.js +0 -37
- package/tests/query-history-header.test.js +0 -30
- package/tests/query-results-truncation.test.js +0 -20
- package/tests/risky-sql.test.js +0 -30
- package/tests/row-editor-json.test.js +0 -82
- package/tests/row-editor-null-values.test.js +0 -184
- package/tests/row-editor-timestamp-preview.test.js +0 -192
- package/tests/security-paths.test.js +0 -84
- package/tests/settings-api-tokens-route.test.js +0 -97
- package/tests/settings-metadata.test.js +0 -114
- package/tests/settings-view.test.js +0 -108
- package/tests/sql-formatter.test.js +0 -173
- package/tests/sql-highlight.test.js +0 -38
- package/tests/sql-identifier-safety.test.js +0 -171
- package/tests/sql-result-limit.test.js +0 -66
- package/tests/structure-view.test.js +0 -60
- package/tests/table-designer-v2-unique-constraints.test.js +0 -78
- package/tests/table-scroll-state.test.js +0 -70
- package/tests/text-cell-stats.test.js +0 -38
package/frontend/js/store.js
CHANGED
|
@@ -23,6 +23,11 @@ import { MEDIA_TAGGING_DEFAULT_MAPPING_TABLE, MEDIA_TAGGING_DEFAULT_TAG_TABLE }
|
|
|
23
23
|
import { buildTextExportFilename } from './utils/exportFilenames.js';
|
|
24
24
|
import { toggleMarkdownTodoLine } from './utils/markdownDocuments.js';
|
|
25
25
|
import { buildRiskySqlBackupContext, detectRiskySqlOperations } from './utils/riskySql.js';
|
|
26
|
+
import {
|
|
27
|
+
buildSyntheticDataMappings,
|
|
28
|
+
getDefaultSyntheticOptions,
|
|
29
|
+
normalizeSyntheticGeneratorType,
|
|
30
|
+
} from './utils/syntheticData.js';
|
|
26
31
|
|
|
27
32
|
const listeners = new Set();
|
|
28
33
|
const DEFAULT_SETTINGS = {
|
|
@@ -59,7 +64,18 @@ const UI_PREFERENCE_STORAGE_KEYS = {
|
|
|
59
64
|
};
|
|
60
65
|
const QUERY_HISTORY_PAGE_SIZE = 30;
|
|
61
66
|
const QUERY_HISTORY_RUN_LIMIT = 8;
|
|
67
|
+
const LOG_PAGE_SIZE = 80;
|
|
68
|
+
const LOG_KINDS = new Set(['all', 'query', 'access']);
|
|
69
|
+
const LOG_RANGES = new Set(['1h', '24h', '7d', '30d', 'all']);
|
|
70
|
+
const LOG_ACTORS = new Set(['all', 'user', 'cli', 'api', 'mcp']);
|
|
71
|
+
const LOG_STATUSES = new Set(['all', 'success', 'error']);
|
|
72
|
+
const LOG_QUERY_TYPES = new Set(['all', 'select', 'insert', 'update', 'delete', 'pragma', 'create', 'alter', 'drop', 'other']);
|
|
73
|
+
const LOG_DESTRUCTIVE_FILTERS = new Set(['all', 'yes', 'no']);
|
|
62
74
|
const CHART_HEIGHT_PRESETS = new Set(['small', 'medium', 'large']);
|
|
75
|
+
const TYPE_GENERATION_TARGETS = new Set(['typescript', 'rust', 'kotlin', 'swift']);
|
|
76
|
+
const TYPE_GENERATION_NAMING = new Set(['preserve', 'camel', 'pascal', 'snake']);
|
|
77
|
+
const TYPE_GENERATION_NULLABLE = new Set(['native', 'optional']);
|
|
78
|
+
const TYPE_GENERATION_JSON_TYPES = new Set(['unknown', 'record', 'json-value']);
|
|
63
79
|
const EDITOR_RESULT_TABS = new Set(['results', 'performance', 'messages']);
|
|
64
80
|
const COPY_COLUMN_MODES = new Set(['column', 'column-with-header', 'first-10', 'markdown-todo']);
|
|
65
81
|
const TEXT_EXPORT_FORMAT_LABELS = {
|
|
@@ -254,6 +270,24 @@ function storeQueryHistoryTab(tab) {
|
|
|
254
270
|
}
|
|
255
271
|
}
|
|
256
272
|
|
|
273
|
+
function normalizeSetValue(value, allowedValues, fallback) {
|
|
274
|
+
const normalized = String(value ?? fallback).trim().toLowerCase();
|
|
275
|
+
return allowedValues.has(normalized) ? normalized : fallback;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function normalizeLogFilters(filters = {}) {
|
|
279
|
+
return {
|
|
280
|
+
kind: normalizeSetValue(filters.kind, LOG_KINDS, 'all'),
|
|
281
|
+
range: normalizeSetValue(filters.range, LOG_RANGES, 'all'),
|
|
282
|
+
actor: normalizeSetValue(filters.actor, LOG_ACTORS, 'all'),
|
|
283
|
+
status: normalizeSetValue(filters.status, LOG_STATUSES, 'all'),
|
|
284
|
+
queryType: normalizeSetValue(filters.queryType, LOG_QUERY_TYPES, 'all'),
|
|
285
|
+
destructive: normalizeSetValue(filters.destructive, LOG_DESTRUCTIVE_FILTERS, 'all'),
|
|
286
|
+
searchInput: String(filters.searchInput ?? filters.search ?? ''),
|
|
287
|
+
search: String(filters.search ?? filters.searchInput ?? '').trim(),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
257
291
|
const state = {
|
|
258
292
|
ready: false,
|
|
259
293
|
route: { name: 'landing', path: '/', params: {} },
|
|
@@ -271,6 +305,18 @@ const state = {
|
|
|
271
305
|
loading: false,
|
|
272
306
|
operationLoading: false,
|
|
273
307
|
error: null,
|
|
308
|
+
diff: {
|
|
309
|
+
visible: false,
|
|
310
|
+
backupId: null,
|
|
311
|
+
backupName: '',
|
|
312
|
+
backupCreatedAt: null,
|
|
313
|
+
currentLabel: '',
|
|
314
|
+
activeTab: 'schema',
|
|
315
|
+
requestId: null,
|
|
316
|
+
loading: false,
|
|
317
|
+
error: null,
|
|
318
|
+
data: null,
|
|
319
|
+
},
|
|
274
320
|
},
|
|
275
321
|
settings: {
|
|
276
322
|
data: { ...DEFAULT_SETTINGS },
|
|
@@ -292,6 +338,18 @@ const state = {
|
|
|
292
338
|
loading: false,
|
|
293
339
|
error: null,
|
|
294
340
|
},
|
|
341
|
+
logs: {
|
|
342
|
+
items: [],
|
|
343
|
+
total: 0,
|
|
344
|
+
limit: LOG_PAGE_SIZE,
|
|
345
|
+
offset: 0,
|
|
346
|
+
hasMore: false,
|
|
347
|
+
loading: false,
|
|
348
|
+
loadingMore: false,
|
|
349
|
+
error: null,
|
|
350
|
+
filters: normalizeLogFilters(),
|
|
351
|
+
metadata: null,
|
|
352
|
+
},
|
|
295
353
|
dataBrowser: {
|
|
296
354
|
tables: [],
|
|
297
355
|
selectedTable: null,
|
|
@@ -316,6 +374,15 @@ const state = {
|
|
|
316
374
|
error: null,
|
|
317
375
|
saveError: null,
|
|
318
376
|
},
|
|
377
|
+
tableAdvisor: {
|
|
378
|
+
tables: [],
|
|
379
|
+
selectedTableName: null,
|
|
380
|
+
result: null,
|
|
381
|
+
loading: false,
|
|
382
|
+
analysisLoading: false,
|
|
383
|
+
error: null,
|
|
384
|
+
analysisError: null,
|
|
385
|
+
},
|
|
319
386
|
editor: {
|
|
320
387
|
sqlText: readStoredString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft),
|
|
321
388
|
editorPanelVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.sqlEditorEditorVisible, true),
|
|
@@ -551,6 +618,21 @@ function getMediaTaggingRouteErrorKey(error = {}) {
|
|
|
551
618
|
return `route:${String(error.code ?? '').trim()}:${String(error.message ?? '').trim()}`;
|
|
552
619
|
}
|
|
553
620
|
|
|
621
|
+
function createEmptyBackupDiffState() {
|
|
622
|
+
return {
|
|
623
|
+
visible: false,
|
|
624
|
+
backupId: null,
|
|
625
|
+
backupName: '',
|
|
626
|
+
backupCreatedAt: null,
|
|
627
|
+
currentLabel: '',
|
|
628
|
+
activeTab: 'schema',
|
|
629
|
+
requestId: null,
|
|
630
|
+
loading: false,
|
|
631
|
+
error: null,
|
|
632
|
+
data: null,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
554
636
|
function syncDismissedMediaTaggingIssues() {
|
|
555
637
|
const availableKeys = new Set();
|
|
556
638
|
|
|
@@ -591,6 +673,7 @@ function requiresActiveDatabase(routeName) {
|
|
|
591
673
|
return [
|
|
592
674
|
'overview',
|
|
593
675
|
'backups',
|
|
676
|
+
'logs',
|
|
594
677
|
'data',
|
|
595
678
|
'editor',
|
|
596
679
|
'editorResults',
|
|
@@ -598,6 +681,7 @@ function requiresActiveDatabase(routeName) {
|
|
|
598
681
|
'documents',
|
|
599
682
|
'structure',
|
|
600
683
|
'tableDesigner',
|
|
684
|
+
'tableAdvisor',
|
|
601
685
|
'mediaTaggingSetup',
|
|
602
686
|
'mediaTaggingQueue',
|
|
603
687
|
].includes(routeName);
|
|
@@ -864,6 +948,91 @@ function findDataBrowserRowIndexByIdentity(rows = [], identity) {
|
|
|
864
948
|
return rows.findIndex(row => areRowIdentitiesEqual(row?.__identity, identity));
|
|
865
949
|
}
|
|
866
950
|
|
|
951
|
+
function coerceDataRouteIdentityValue(column, value) {
|
|
952
|
+
const text = String(value ?? '');
|
|
953
|
+
const affinity = String(column?.affinity ?? '').toUpperCase();
|
|
954
|
+
|
|
955
|
+
if (['INTEGER', 'REAL', 'NUMERIC'].includes(affinity) && text.trim() !== '') {
|
|
956
|
+
const numberValue = Number(text);
|
|
957
|
+
|
|
958
|
+
if (Number.isFinite(numberValue)) {
|
|
959
|
+
return numberValue;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return value;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function parseCompositeDataRoutePrimaryKey(rawValue) {
|
|
967
|
+
try {
|
|
968
|
+
const parsed = JSON.parse(rawValue);
|
|
969
|
+
|
|
970
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
971
|
+
return parsed;
|
|
972
|
+
}
|
|
973
|
+
} catch {
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
function buildDataRouteRowIdentity(table, rawPrimaryKey) {
|
|
981
|
+
const primaryKeyValue = String(rawPrimaryKey ?? '').trim();
|
|
982
|
+
const identityStrategy = table?.identityStrategy ?? null;
|
|
983
|
+
|
|
984
|
+
if (!primaryKeyValue || !identityStrategy) {
|
|
985
|
+
return null;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (identityStrategy.type === 'rowid') {
|
|
989
|
+
const numberValue = Number(primaryKeyValue);
|
|
990
|
+
|
|
991
|
+
return {
|
|
992
|
+
kind: 'rowid',
|
|
993
|
+
values: {
|
|
994
|
+
rowid: Number.isInteger(numberValue) ? numberValue : primaryKeyValue,
|
|
995
|
+
},
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (identityStrategy.type !== 'primaryKey') {
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const columns = identityStrategy.columns ?? [];
|
|
1004
|
+
|
|
1005
|
+
if (columns.length === 1) {
|
|
1006
|
+
const columnName = columns[0];
|
|
1007
|
+
const column = table?.columnMeta?.find(candidate => candidate.name === columnName);
|
|
1008
|
+
|
|
1009
|
+
return {
|
|
1010
|
+
kind: 'primaryKey',
|
|
1011
|
+
columns,
|
|
1012
|
+
values: {
|
|
1013
|
+
[columnName]: coerceDataRouteIdentityValue(column, primaryKeyValue),
|
|
1014
|
+
},
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const parsedValue = parseCompositeDataRoutePrimaryKey(primaryKeyValue);
|
|
1019
|
+
|
|
1020
|
+
if (!parsedValue || columns.some(columnName => !Object.prototype.hasOwnProperty.call(parsedValue, columnName))) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return {
|
|
1025
|
+
kind: 'primaryKey',
|
|
1026
|
+
columns,
|
|
1027
|
+
values: Object.fromEntries(
|
|
1028
|
+
columns.map(columnName => {
|
|
1029
|
+
const column = table?.columnMeta?.find(candidate => candidate.name === columnName);
|
|
1030
|
+
return [columnName, coerceDataRouteIdentityValue(column, parsedValue[columnName])];
|
|
1031
|
+
}),
|
|
1032
|
+
),
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
867
1036
|
function clearDataBrowserRowSelectionState() {
|
|
868
1037
|
state.dataBrowser.selectedRowIndex = null;
|
|
869
1038
|
state.dataBrowser.selectedRow = null;
|
|
@@ -1174,6 +1343,7 @@ function resolveQueryHistorySql(historyId) {
|
|
|
1174
1343
|
|
|
1175
1344
|
function clearRouteSlices() {
|
|
1176
1345
|
state.overview.error = null;
|
|
1346
|
+
state.logs.error = null;
|
|
1177
1347
|
state.dataBrowser.error = null;
|
|
1178
1348
|
state.dataBrowser.saveError = null;
|
|
1179
1349
|
state.charts.error = null;
|
|
@@ -1181,6 +1351,8 @@ function clearRouteSlices() {
|
|
|
1181
1351
|
state.charts.resultError = null;
|
|
1182
1352
|
state.tableDesigner.error = null;
|
|
1183
1353
|
state.tableDesigner.saveError = null;
|
|
1354
|
+
state.tableAdvisor.error = null;
|
|
1355
|
+
state.tableAdvisor.analysisError = null;
|
|
1184
1356
|
state.structure.error = null;
|
|
1185
1357
|
state.mediaTagging.error = null;
|
|
1186
1358
|
state.documents.error = null;
|
|
@@ -1194,6 +1366,15 @@ function setMissingDatabaseState() {
|
|
|
1194
1366
|
state.overview.data = null;
|
|
1195
1367
|
state.overview.error = error;
|
|
1196
1368
|
|
|
1369
|
+
state.logs.loading = false;
|
|
1370
|
+
state.logs.loadingMore = false;
|
|
1371
|
+
state.logs.items = [];
|
|
1372
|
+
state.logs.total = 0;
|
|
1373
|
+
state.logs.offset = 0;
|
|
1374
|
+
state.logs.hasMore = false;
|
|
1375
|
+
state.logs.error = error;
|
|
1376
|
+
state.logs.metadata = null;
|
|
1377
|
+
|
|
1197
1378
|
state.dataBrowser.loading = false;
|
|
1198
1379
|
state.dataBrowser.tableLoading = false;
|
|
1199
1380
|
state.dataBrowser.tables = [];
|
|
@@ -1234,6 +1415,14 @@ function setMissingDatabaseState() {
|
|
|
1234
1415
|
state.tableDesigner.error = error;
|
|
1235
1416
|
state.tableDesigner.saveError = null;
|
|
1236
1417
|
|
|
1418
|
+
state.tableAdvisor.loading = false;
|
|
1419
|
+
state.tableAdvisor.analysisLoading = false;
|
|
1420
|
+
state.tableAdvisor.tables = [];
|
|
1421
|
+
state.tableAdvisor.selectedTableName = null;
|
|
1422
|
+
state.tableAdvisor.result = null;
|
|
1423
|
+
state.tableAdvisor.error = error;
|
|
1424
|
+
state.tableAdvisor.analysisError = null;
|
|
1425
|
+
|
|
1237
1426
|
state.mediaTagging.loading = false;
|
|
1238
1427
|
state.mediaTagging.previewLoading = false;
|
|
1239
1428
|
state.mediaTagging.saving = false;
|
|
@@ -1324,6 +1513,10 @@ function syncRouteContext() {
|
|
|
1324
1513
|
state.documents.saveError = null;
|
|
1325
1514
|
}
|
|
1326
1515
|
|
|
1516
|
+
if (route.name !== 'backups') {
|
|
1517
|
+
state.backups.diff = createEmptyBackupDiffState();
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1327
1520
|
if (route.name !== 'mediaTaggingSetup' && route.name !== 'mediaTaggingQueue') {
|
|
1328
1521
|
state.mediaTagging.selectedTagKeys = [];
|
|
1329
1522
|
}
|
|
@@ -1375,10 +1568,70 @@ async function refreshSettingsState() {
|
|
|
1375
1568
|
}
|
|
1376
1569
|
}
|
|
1377
1570
|
|
|
1571
|
+
function buildLogRequestOptions({ append = false } = {}) {
|
|
1572
|
+
const filters = normalizeLogFilters(state.logs.filters);
|
|
1573
|
+
|
|
1574
|
+
return {
|
|
1575
|
+
...filters,
|
|
1576
|
+
search: filters.search,
|
|
1577
|
+
limit: state.logs.limit,
|
|
1578
|
+
offset: append ? state.logs.items.length : 0,
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
async function loadLogs(options = {}) {
|
|
1583
|
+
const append = Boolean(options.append);
|
|
1584
|
+
|
|
1585
|
+
if (append && (state.logs.loading || state.logs.loadingMore || !state.logs.hasMore)) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
if (append) {
|
|
1590
|
+
state.logs.loadingMore = true;
|
|
1591
|
+
} else {
|
|
1592
|
+
state.logs.loading = true;
|
|
1593
|
+
state.logs.offset = 0;
|
|
1594
|
+
state.logs.error = null;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
emitChange();
|
|
1598
|
+
|
|
1599
|
+
try {
|
|
1600
|
+
const response = await api.getLogs(buildLogRequestOptions({ append }));
|
|
1601
|
+
const data = response.data ?? {};
|
|
1602
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
1603
|
+
|
|
1604
|
+
state.logs.items = append ? [...state.logs.items, ...items] : items;
|
|
1605
|
+
state.logs.total = Number(data.total ?? state.logs.items.length);
|
|
1606
|
+
state.logs.limit = Number(data.limit ?? state.logs.limit);
|
|
1607
|
+
state.logs.offset = Number(data.offset ?? 0);
|
|
1608
|
+
state.logs.hasMore = Boolean(data.hasMore);
|
|
1609
|
+
state.logs.metadata = response.metadata ?? data.filters ?? null;
|
|
1610
|
+
state.logs.filters = normalizeLogFilters({
|
|
1611
|
+
...state.logs.filters,
|
|
1612
|
+
...(data.filters ?? {}),
|
|
1613
|
+
...(response.metadata ?? {}),
|
|
1614
|
+
});
|
|
1615
|
+
state.logs.error = null;
|
|
1616
|
+
} catch (error) {
|
|
1617
|
+
state.logs.error = normalizeError(error);
|
|
1618
|
+
if (!append) {
|
|
1619
|
+
state.logs.items = [];
|
|
1620
|
+
state.logs.total = 0;
|
|
1621
|
+
state.logs.hasMore = false;
|
|
1622
|
+
}
|
|
1623
|
+
} finally {
|
|
1624
|
+
state.logs.loading = false;
|
|
1625
|
+
state.logs.loadingMore = false;
|
|
1626
|
+
emitChange();
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1378
1630
|
async function refreshBackupsState() {
|
|
1379
1631
|
if (!state.connections.active) {
|
|
1380
1632
|
state.backups.items = [];
|
|
1381
1633
|
state.backups.error = null;
|
|
1634
|
+
state.backups.diff = createEmptyBackupDiffState();
|
|
1382
1635
|
emitChange();
|
|
1383
1636
|
return;
|
|
1384
1637
|
}
|
|
@@ -1431,6 +1684,48 @@ export function setSettingsSection(section) {
|
|
|
1431
1684
|
emitChange();
|
|
1432
1685
|
}
|
|
1433
1686
|
|
|
1687
|
+
export async function setLogFilter(field, value) {
|
|
1688
|
+
if (!Object.prototype.hasOwnProperty.call(state.logs.filters, field)) {
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
state.logs.filters = normalizeLogFilters({
|
|
1693
|
+
...state.logs.filters,
|
|
1694
|
+
[field]: value,
|
|
1695
|
+
});
|
|
1696
|
+
state.logs.items = [];
|
|
1697
|
+
state.logs.total = 0;
|
|
1698
|
+
state.logs.hasMore = false;
|
|
1699
|
+
await loadLogs();
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
export function setLogSearchInput(value) {
|
|
1703
|
+
state.logs.filters = normalizeLogFilters({
|
|
1704
|
+
...state.logs.filters,
|
|
1705
|
+
searchInput: value,
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
export async function applyLogSearch(value = state.logs.filters.searchInput) {
|
|
1710
|
+
state.logs.filters = normalizeLogFilters({
|
|
1711
|
+
...state.logs.filters,
|
|
1712
|
+
searchInput: value,
|
|
1713
|
+
search: value,
|
|
1714
|
+
});
|
|
1715
|
+
state.logs.items = [];
|
|
1716
|
+
state.logs.total = 0;
|
|
1717
|
+
state.logs.hasMore = false;
|
|
1718
|
+
await loadLogs();
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
export async function refreshLogs() {
|
|
1722
|
+
await loadLogs();
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
export async function loadMoreLogs() {
|
|
1726
|
+
await loadLogs({ append: true });
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1434
1729
|
export async function checkSettingsAppVersion() {
|
|
1435
1730
|
state.settings.versionCheckLoading = true;
|
|
1436
1731
|
state.settings.versionCheckError = null;
|
|
@@ -1956,8 +2251,9 @@ async function resolvePendingDataBrowserRow(version) {
|
|
|
1956
2251
|
}
|
|
1957
2252
|
}
|
|
1958
2253
|
|
|
1959
|
-
async function loadDataTable(version) {
|
|
2254
|
+
async function loadDataTable(version, options = {}) {
|
|
1960
2255
|
const tableName = state.dataBrowser.selectedTable;
|
|
2256
|
+
const routeRowPrimaryKey = options.rowPrimaryKey ?? null;
|
|
1961
2257
|
const pageSize = normalizeDataPageSize(state.dataBrowser.pageSize, DEFAULT_DATA_PAGE_SIZE);
|
|
1962
2258
|
const page = Math.max(1, Number(state.dataBrowser.page) || 1);
|
|
1963
2259
|
const sortColumn = state.dataBrowser.sortColumn;
|
|
@@ -2011,6 +2307,15 @@ async function loadDataTable(version) {
|
|
|
2011
2307
|
state.dataBrowser.searchColumn,
|
|
2012
2308
|
);
|
|
2013
2309
|
clearDataBrowserRowSelectionState();
|
|
2310
|
+
const routeRowIdentity = buildDataRouteRowIdentity(state.dataBrowser.table, routeRowPrimaryKey);
|
|
2311
|
+
if (routeRowPrimaryKey !== null && routeRowPrimaryKey !== undefined) {
|
|
2312
|
+
state.dataBrowser.pendingOpenRow = routeRowIdentity
|
|
2313
|
+
? {
|
|
2314
|
+
tableName,
|
|
2315
|
+
identity: routeRowIdentity,
|
|
2316
|
+
}
|
|
2317
|
+
: null;
|
|
2318
|
+
}
|
|
2014
2319
|
await resolvePendingDataBrowserRow(version);
|
|
2015
2320
|
} catch (error) {
|
|
2016
2321
|
if (version !== routeLoadVersion) {
|
|
@@ -2071,7 +2376,9 @@ async function loadData(version, route) {
|
|
|
2071
2376
|
return;
|
|
2072
2377
|
}
|
|
2073
2378
|
|
|
2074
|
-
await loadDataTable(version
|
|
2379
|
+
await loadDataTable(version, {
|
|
2380
|
+
rowPrimaryKey: route.params?.rowPrimaryKey ?? null,
|
|
2381
|
+
});
|
|
2075
2382
|
} catch (error) {
|
|
2076
2383
|
if (version !== routeLoadVersion) {
|
|
2077
2384
|
return;
|
|
@@ -2091,6 +2398,94 @@ async function loadData(version, route) {
|
|
|
2091
2398
|
}
|
|
2092
2399
|
}
|
|
2093
2400
|
|
|
2401
|
+
async function loadTableAdvisorResult(version, tableName) {
|
|
2402
|
+
if (!tableName) {
|
|
2403
|
+
state.tableAdvisor.result = null;
|
|
2404
|
+
state.tableAdvisor.analysisError = null;
|
|
2405
|
+
state.tableAdvisor.analysisLoading = false;
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
state.tableAdvisor.analysisLoading = true;
|
|
2410
|
+
state.tableAdvisor.analysisError = null;
|
|
2411
|
+
state.tableAdvisor.result = null;
|
|
2412
|
+
emitChange();
|
|
2413
|
+
|
|
2414
|
+
try {
|
|
2415
|
+
const response = await api.analyzeTableAdvisor(tableName);
|
|
2416
|
+
|
|
2417
|
+
if (version !== routeLoadVersion) {
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
state.tableAdvisor.result = response.data ?? null;
|
|
2422
|
+
state.tableAdvisor.analysisError = null;
|
|
2423
|
+
} catch (error) {
|
|
2424
|
+
if (version !== routeLoadVersion) {
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
state.tableAdvisor.result = null;
|
|
2429
|
+
state.tableAdvisor.analysisError = normalizeError(error);
|
|
2430
|
+
} finally {
|
|
2431
|
+
if (version === routeLoadVersion) {
|
|
2432
|
+
state.tableAdvisor.analysisLoading = false;
|
|
2433
|
+
emitChange();
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
async function loadTableAdvisor(version, route) {
|
|
2439
|
+
state.tableAdvisor.loading = true;
|
|
2440
|
+
state.tableAdvisor.error = null;
|
|
2441
|
+
state.tableAdvisor.analysisError = null;
|
|
2442
|
+
emitChange();
|
|
2443
|
+
|
|
2444
|
+
try {
|
|
2445
|
+
const response = await api.getDataTables();
|
|
2446
|
+
|
|
2447
|
+
if (version !== routeLoadVersion) {
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
const tables = response.data?.tables ?? [];
|
|
2452
|
+
const requestedTableName = route.params?.tableName ?? null;
|
|
2453
|
+
const selectedTableName =
|
|
2454
|
+
requestedTableName && tables.some(table => table.name === requestedTableName)
|
|
2455
|
+
? requestedTableName
|
|
2456
|
+
: (state.tableAdvisor.selectedTableName && tables.some(table => table.name === state.tableAdvisor.selectedTableName)
|
|
2457
|
+
? state.tableAdvisor.selectedTableName
|
|
2458
|
+
: (tables[0]?.name ?? null));
|
|
2459
|
+
|
|
2460
|
+
state.tableAdvisor.tables = tables;
|
|
2461
|
+
state.tableAdvisor.selectedTableName = selectedTableName;
|
|
2462
|
+
state.tableAdvisor.error = null;
|
|
2463
|
+
|
|
2464
|
+
if (!selectedTableName) {
|
|
2465
|
+
state.tableAdvisor.result = null;
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
state.tableAdvisor.loading = false;
|
|
2470
|
+
emitChange();
|
|
2471
|
+
await loadTableAdvisorResult(version, selectedTableName);
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
if (version !== routeLoadVersion) {
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
state.tableAdvisor.tables = [];
|
|
2478
|
+
state.tableAdvisor.selectedTableName = null;
|
|
2479
|
+
state.tableAdvisor.result = null;
|
|
2480
|
+
state.tableAdvisor.error = normalizeError(error);
|
|
2481
|
+
} finally {
|
|
2482
|
+
if (version === routeLoadVersion) {
|
|
2483
|
+
state.tableAdvisor.loading = false;
|
|
2484
|
+
emitChange();
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2094
2489
|
async function loadStructureDetail(version) {
|
|
2095
2490
|
const entry = getCurrentStructureEntry(state);
|
|
2096
2491
|
|
|
@@ -2435,6 +2830,7 @@ function invalidateDatabaseCaches(options = {}) {
|
|
|
2435
2830
|
}
|
|
2436
2831
|
|
|
2437
2832
|
state.overview.data = null;
|
|
2833
|
+
state.backups.diff = createEmptyBackupDiffState();
|
|
2438
2834
|
state.dataBrowser.tables = [];
|
|
2439
2835
|
if (!preserveDataBrowserState) {
|
|
2440
2836
|
state.dataBrowser.selectedTable = null;
|
|
@@ -2537,6 +2933,9 @@ async function loadRouteData(route, options = {}) {
|
|
|
2537
2933
|
case 'backups':
|
|
2538
2934
|
await refreshBackupsState();
|
|
2539
2935
|
return;
|
|
2936
|
+
case 'logs':
|
|
2937
|
+
await loadLogs();
|
|
2938
|
+
return;
|
|
2540
2939
|
case 'data':
|
|
2541
2940
|
await loadData(version, route);
|
|
2542
2941
|
return;
|
|
@@ -2556,6 +2955,9 @@ async function loadRouteData(route, options = {}) {
|
|
|
2556
2955
|
case 'tableDesigner':
|
|
2557
2956
|
await loadTableDesigner(version, route);
|
|
2558
2957
|
return;
|
|
2958
|
+
case 'tableAdvisor':
|
|
2959
|
+
await loadTableAdvisor(version, route);
|
|
2960
|
+
return;
|
|
2559
2961
|
case 'mediaTaggingSetup':
|
|
2560
2962
|
case 'mediaTaggingQueue':
|
|
2561
2963
|
await loadMediaTagging(version);
|
|
@@ -2758,6 +3160,186 @@ export function openDataExportModal() {
|
|
|
2758
3160
|
emitChange();
|
|
2759
3161
|
}
|
|
2760
3162
|
|
|
3163
|
+
function getGenerateDataModalColumn(modal, columnName) {
|
|
3164
|
+
return (modal?.columns ?? []).find(column => column.name === columnName) ?? null;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
function resetGenerateDataPreview(modal) {
|
|
3168
|
+
if (!modal) {
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
modal.previewRows = [];
|
|
3173
|
+
modal.previewColumns = (modal.columns ?? []).map(column => column.name);
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
function buildGenerateDataPayload(modal) {
|
|
3177
|
+
return {
|
|
3178
|
+
rowCount: Number(modal.rowCount ?? 100),
|
|
3179
|
+
mappings: (modal.mappings ?? []).map(mapping => ({
|
|
3180
|
+
columnName: mapping.columnName,
|
|
3181
|
+
generator: mapping.generator,
|
|
3182
|
+
options: { ...(mapping.options ?? {}) },
|
|
3183
|
+
})),
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
export function openGenerateDataModal() {
|
|
3188
|
+
const table = state.dataBrowser.table;
|
|
3189
|
+
const tableName = table?.name ?? state.dataBrowser.selectedTable;
|
|
3190
|
+
|
|
3191
|
+
if (!tableName || !table) {
|
|
3192
|
+
pushToast('Select a table before generating rows.', 'alert');
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
const columns = (table.columnMeta ?? []).filter(column => column.visible && !column.generated);
|
|
3197
|
+
|
|
3198
|
+
state.modal = {
|
|
3199
|
+
kind: 'generate-data',
|
|
3200
|
+
tableName,
|
|
3201
|
+
columns,
|
|
3202
|
+
rowCount: 100,
|
|
3203
|
+
mappings: buildSyntheticDataMappings(columns, table.foreignKeys ?? []),
|
|
3204
|
+
previewColumns: columns.map(column => column.name),
|
|
3205
|
+
previewRows: [],
|
|
3206
|
+
previewLoading: false,
|
|
3207
|
+
previewRequestId: 0,
|
|
3208
|
+
error: null,
|
|
3209
|
+
submitting: false,
|
|
3210
|
+
};
|
|
3211
|
+
emitChange();
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
export function updateGenerateDataModal(field, value, options = {}) {
|
|
3215
|
+
if (state.modal?.kind !== 'generate-data') {
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
if (field === 'rowCount') {
|
|
3220
|
+
const nextValue = String(value ?? '');
|
|
3221
|
+
|
|
3222
|
+
if (state.modal.rowCount === nextValue) {
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
state.modal.rowCount = nextValue;
|
|
3227
|
+
state.modal.error = null;
|
|
3228
|
+
resetGenerateDataPreview(state.modal);
|
|
3229
|
+
if (options.notify !== false) {
|
|
3230
|
+
emitChange();
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3235
|
+
export function updateGenerateDataMapping(columnName, field, value, options = {}) {
|
|
3236
|
+
const modal = state.modal;
|
|
3237
|
+
|
|
3238
|
+
if (modal?.kind !== 'generate-data') {
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
const mapping = (modal.mappings ?? []).find(item => item.columnName === columnName);
|
|
3243
|
+
|
|
3244
|
+
if (!mapping) {
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
const column = getGenerateDataModalColumn(modal, columnName);
|
|
3249
|
+
|
|
3250
|
+
if (field === 'generator') {
|
|
3251
|
+
const generator = normalizeSyntheticGeneratorType(value, mapping.generator);
|
|
3252
|
+
|
|
3253
|
+
if (mapping.generator === generator) {
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
mapping.generator = generator;
|
|
3258
|
+
mapping.options = getDefaultSyntheticOptions(generator, column ?? {});
|
|
3259
|
+
} else {
|
|
3260
|
+
const nextValue = value;
|
|
3261
|
+
|
|
3262
|
+
if ((mapping.options ?? {})[field] === nextValue) {
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
mapping.options = {
|
|
3267
|
+
...(mapping.options ?? {}),
|
|
3268
|
+
[field]: nextValue,
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
modal.error = null;
|
|
3273
|
+
resetGenerateDataPreview(modal);
|
|
3274
|
+
if (options.notify !== false) {
|
|
3275
|
+
emitChange();
|
|
3276
|
+
}
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
export async function previewGenerateDataRows() {
|
|
3280
|
+
const modal = state.modal;
|
|
3281
|
+
|
|
3282
|
+
if (modal?.kind !== 'generate-data') {
|
|
3283
|
+
return null;
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
const requestId = (modal.previewRequestId ?? 0) + 1;
|
|
3287
|
+
modal.previewRequestId = requestId;
|
|
3288
|
+
modal.previewLoading = true;
|
|
3289
|
+
modal.error = null;
|
|
3290
|
+
emitChange();
|
|
3291
|
+
|
|
3292
|
+
try {
|
|
3293
|
+
const response = await api.previewSyntheticDataRows(modal.tableName, buildGenerateDataPayload(modal));
|
|
3294
|
+
|
|
3295
|
+
if (state.modal?.kind !== 'generate-data' || state.modal.previewRequestId !== requestId) {
|
|
3296
|
+
return null;
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
state.modal.previewColumns = response.data?.columns ?? [];
|
|
3300
|
+
state.modal.previewRows = response.data?.rows ?? [];
|
|
3301
|
+
state.modal.error = null;
|
|
3302
|
+
return response.data;
|
|
3303
|
+
} catch (error) {
|
|
3304
|
+
if (state.modal?.kind === 'generate-data' && state.modal.previewRequestId === requestId) {
|
|
3305
|
+
state.modal.error = normalizeError(error);
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
return null;
|
|
3309
|
+
} finally {
|
|
3310
|
+
if (state.modal?.kind === 'generate-data' && state.modal.previewRequestId === requestId) {
|
|
3311
|
+
state.modal.previewLoading = false;
|
|
3312
|
+
emitChange();
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
export async function submitGenerateDataRows() {
|
|
3318
|
+
const modal = state.modal;
|
|
3319
|
+
|
|
3320
|
+
if (modal?.kind !== 'generate-data') {
|
|
3321
|
+
return null;
|
|
3322
|
+
}
|
|
3323
|
+
|
|
3324
|
+
startModalSubmission();
|
|
3325
|
+
|
|
3326
|
+
try {
|
|
3327
|
+
const response = await api.insertSyntheticDataRows(modal.tableName, buildGenerateDataPayload(modal));
|
|
3328
|
+
|
|
3329
|
+
if (state.modal?.kind === 'generate-data') {
|
|
3330
|
+
closeModalInternal();
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
pushToast(response.message || `Generated ${response.data?.insertedRowCount ?? modal.rowCount} rows for ${modal.tableName}.`, 'success');
|
|
3334
|
+
await loadDataTable(++routeLoadVersion);
|
|
3335
|
+
clearDataBrowserRowSelectionState();
|
|
3336
|
+
return response.data;
|
|
3337
|
+
} catch (error) {
|
|
3338
|
+
withModalError(error);
|
|
3339
|
+
return null;
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
|
|
2761
3343
|
export function openCopyColumnModal({ scope = 'editor', columnName = '', mode = 'column' } = {}) {
|
|
2762
3344
|
const resultScope = normalizeCopyColumnScope(scope);
|
|
2763
3345
|
const normalizedColumnName = String(columnName ?? '');
|
|
@@ -3976,7 +4558,7 @@ export async function submitCreateBackupConfirmation({ name = '', notes = '', ty
|
|
|
3976
4558
|
}
|
|
3977
4559
|
}
|
|
3978
4560
|
|
|
3979
|
-
export function
|
|
4561
|
+
export function openEditBackupModal(backupId) {
|
|
3980
4562
|
const backup = state.backups.items.find(item => String(item.id) === String(backupId));
|
|
3981
4563
|
|
|
3982
4564
|
if (!backup) {
|
|
@@ -3985,7 +4567,7 @@ export function openEditBackupNotesModal(backupId) {
|
|
|
3985
4567
|
}
|
|
3986
4568
|
|
|
3987
4569
|
state.modal = {
|
|
3988
|
-
kind: 'edit-backup
|
|
4570
|
+
kind: 'edit-backup',
|
|
3989
4571
|
backupId: backup.id,
|
|
3990
4572
|
backupName: backup.name,
|
|
3991
4573
|
notes: backup.notes ?? '',
|
|
@@ -3995,8 +4577,8 @@ export function openEditBackupNotesModal(backupId) {
|
|
|
3995
4577
|
emitChange();
|
|
3996
4578
|
}
|
|
3997
4579
|
|
|
3998
|
-
export async function
|
|
3999
|
-
if (state.modal?.kind !== 'edit-backup
|
|
4580
|
+
export async function submitEditBackupConfirmation({ name = '', notes = '' } = {}) {
|
|
4581
|
+
if (state.modal?.kind !== 'edit-backup') {
|
|
4000
4582
|
return null;
|
|
4001
4583
|
}
|
|
4002
4584
|
|
|
@@ -4005,12 +4587,12 @@ export async function submitEditBackupNotesConfirmation(notes = '') {
|
|
|
4005
4587
|
state.backups.operationLoading = true;
|
|
4006
4588
|
|
|
4007
4589
|
try {
|
|
4008
|
-
const response = await api.updateBackup(backupId, { notes });
|
|
4590
|
+
const response = await api.updateBackup(backupId, { name, notes });
|
|
4009
4591
|
state.backups.items = state.backups.items.map(item =>
|
|
4010
4592
|
String(item.id) === String(response.data.id) ? response.data : item
|
|
4011
4593
|
);
|
|
4012
4594
|
closeModalInternal();
|
|
4013
|
-
pushToast(response.message || 'Backup
|
|
4595
|
+
pushToast(response.message || 'Backup updated.', 'success');
|
|
4014
4596
|
return response.data;
|
|
4015
4597
|
} catch (error) {
|
|
4016
4598
|
withModalError(error);
|
|
@@ -4052,6 +4634,9 @@ export async function submitDeleteBackupConfirmation() {
|
|
|
4052
4634
|
try {
|
|
4053
4635
|
const response = await api.deleteBackup(backupId);
|
|
4054
4636
|
state.backups.items = state.backups.items.filter(item => String(item.id) !== String(backupId));
|
|
4637
|
+
if (String(state.backups.diff.backupId ?? '') === String(backupId)) {
|
|
4638
|
+
state.backups.diff = createEmptyBackupDiffState();
|
|
4639
|
+
}
|
|
4055
4640
|
closeModalInternal();
|
|
4056
4641
|
pushToast(response.message || 'Backup deleted.', 'muted');
|
|
4057
4642
|
return true;
|
|
@@ -4082,6 +4667,75 @@ export async function downloadBackup(backupId) {
|
|
|
4082
4667
|
}
|
|
4083
4668
|
}
|
|
4084
4669
|
|
|
4670
|
+
export async function openBackupDiffDrawer(backupId) {
|
|
4671
|
+
const backup = state.backups.items.find(item => String(item.id) === String(backupId));
|
|
4672
|
+
|
|
4673
|
+
if (!backup) {
|
|
4674
|
+
pushToast('The selected backup could not be loaded.', 'alert');
|
|
4675
|
+
return null;
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
const requestId = crypto.randomUUID();
|
|
4679
|
+
state.backups.diff = {
|
|
4680
|
+
visible: true,
|
|
4681
|
+
backupId: backup.id,
|
|
4682
|
+
backupName: backup.name,
|
|
4683
|
+
backupCreatedAt: backup.createdAt,
|
|
4684
|
+
currentLabel: state.connections.active?.label ?? 'Current database',
|
|
4685
|
+
activeTab: 'schema',
|
|
4686
|
+
requestId,
|
|
4687
|
+
loading: true,
|
|
4688
|
+
error: null,
|
|
4689
|
+
data: null,
|
|
4690
|
+
};
|
|
4691
|
+
state.backups.operationLoading = true;
|
|
4692
|
+
emitChange();
|
|
4693
|
+
|
|
4694
|
+
try {
|
|
4695
|
+
const response = await api.getBackupDiff(backup.id);
|
|
4696
|
+
|
|
4697
|
+
if (state.backups.diff.requestId === requestId) {
|
|
4698
|
+
state.backups.diff.data = response.data ?? null;
|
|
4699
|
+
state.backups.diff.backupName = response.data?.backup?.name ?? state.backups.diff.backupName;
|
|
4700
|
+
state.backups.diff.backupCreatedAt = response.data?.backup?.createdAt ?? state.backups.diff.backupCreatedAt;
|
|
4701
|
+
state.backups.diff.currentLabel = response.data?.current?.label ?? state.backups.diff.currentLabel;
|
|
4702
|
+
state.backups.diff.error = null;
|
|
4703
|
+
}
|
|
4704
|
+
|
|
4705
|
+
return response.data ?? null;
|
|
4706
|
+
} catch (error) {
|
|
4707
|
+
if (state.backups.diff.requestId === requestId) {
|
|
4708
|
+
state.backups.diff.error = normalizeError(error);
|
|
4709
|
+
} else {
|
|
4710
|
+
pushToast(normalizeError(error).message || 'Backup comparison failed.', 'alert');
|
|
4711
|
+
}
|
|
4712
|
+
|
|
4713
|
+
return null;
|
|
4714
|
+
} finally {
|
|
4715
|
+
state.backups.operationLoading = false;
|
|
4716
|
+
|
|
4717
|
+
if (state.backups.diff.requestId === requestId) {
|
|
4718
|
+
state.backups.diff.loading = false;
|
|
4719
|
+
}
|
|
4720
|
+
|
|
4721
|
+
emitChange();
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
|
|
4725
|
+
export function setBackupDiffTab(tab) {
|
|
4726
|
+
if (!state.backups.diff.visible) {
|
|
4727
|
+
return;
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
state.backups.diff.activeTab = tab === 'data' ? 'data' : 'schema';
|
|
4731
|
+
emitChange();
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
export function closeBackupDiffDrawer() {
|
|
4735
|
+
state.backups.diff = createEmptyBackupDiffState();
|
|
4736
|
+
emitChange();
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4085
4739
|
export function openRestoreBackupModal(backupId) {
|
|
4086
4740
|
const backup = state.backups.items.find(item => String(item.id) === String(backupId));
|
|
4087
4741
|
|
|
@@ -4510,6 +5164,214 @@ export async function selectStructureEntry(name) {
|
|
|
4510
5164
|
await loadStructureDetail(++routeLoadVersion);
|
|
4511
5165
|
}
|
|
4512
5166
|
|
|
5167
|
+
function getDefaultTypeGenerationOptions(target = 'typescript') {
|
|
5168
|
+
const propertyNaming = target === 'rust' ? 'snake' : 'camel';
|
|
5169
|
+
|
|
5170
|
+
return {
|
|
5171
|
+
typeName: '',
|
|
5172
|
+
propertyNaming,
|
|
5173
|
+
nullableMode: 'native',
|
|
5174
|
+
includeComments: false,
|
|
5175
|
+
includeDefaultsAsComments: false,
|
|
5176
|
+
includeGeneratedColumns: true,
|
|
5177
|
+
includeHiddenColumns: false,
|
|
5178
|
+
exportDeclaration: true,
|
|
5179
|
+
jsonType: 'unknown',
|
|
5180
|
+
};
|
|
5181
|
+
}
|
|
5182
|
+
|
|
5183
|
+
function normalizeTypeGenerationOptions(options = {}, target = 'typescript') {
|
|
5184
|
+
const defaults = getDefaultTypeGenerationOptions(target);
|
|
5185
|
+
const propertyNaming = TYPE_GENERATION_NAMING.has(options.propertyNaming)
|
|
5186
|
+
? options.propertyNaming
|
|
5187
|
+
: defaults.propertyNaming;
|
|
5188
|
+
const nullableMode =
|
|
5189
|
+
TYPE_GENERATION_NULLABLE.has(options.nullableMode) && (target === 'typescript' || options.nullableMode !== 'optional')
|
|
5190
|
+
? options.nullableMode
|
|
5191
|
+
: 'native';
|
|
5192
|
+
const jsonType = TYPE_GENERATION_JSON_TYPES.has(options.jsonType) ? options.jsonType : 'unknown';
|
|
5193
|
+
|
|
5194
|
+
return {
|
|
5195
|
+
...defaults,
|
|
5196
|
+
...options,
|
|
5197
|
+
propertyNaming,
|
|
5198
|
+
nullableMode,
|
|
5199
|
+
jsonType,
|
|
5200
|
+
typeName: String(options.typeName ?? ''),
|
|
5201
|
+
};
|
|
5202
|
+
}
|
|
5203
|
+
|
|
5204
|
+
function getStructureTableNames() {
|
|
5205
|
+
return (state.structure.data?.grouped?.tables ?? [])
|
|
5206
|
+
.filter(table => table?.type === 'table')
|
|
5207
|
+
.map(table => String(table.name ?? '').trim())
|
|
5208
|
+
.filter(Boolean);
|
|
5209
|
+
}
|
|
5210
|
+
|
|
5211
|
+
function getTypeGenerationRequestOptions(modal) {
|
|
5212
|
+
const options = { ...modal.options };
|
|
5213
|
+
|
|
5214
|
+
if (!String(options.typeName ?? '').trim()) {
|
|
5215
|
+
delete options.typeName;
|
|
5216
|
+
}
|
|
5217
|
+
|
|
5218
|
+
if (modal.target !== 'typescript') {
|
|
5219
|
+
delete options.jsonType;
|
|
5220
|
+
}
|
|
5221
|
+
|
|
5222
|
+
return options;
|
|
5223
|
+
}
|
|
5224
|
+
|
|
5225
|
+
function combineGeneratedTypeFiles(files = []) {
|
|
5226
|
+
return files
|
|
5227
|
+
.map(file => [`// ${file.fileName}`, file.code].filter(Boolean).join('\n'))
|
|
5228
|
+
.join('\n\n');
|
|
5229
|
+
}
|
|
5230
|
+
|
|
5231
|
+
async function refreshTypeGenerationPreview() {
|
|
5232
|
+
const modal = state.modal;
|
|
5233
|
+
|
|
5234
|
+
if (modal?.kind !== 'generate-types') {
|
|
5235
|
+
return;
|
|
5236
|
+
}
|
|
5237
|
+
|
|
5238
|
+
const requestId = (modal.previewRequestId ?? 0) + 1;
|
|
5239
|
+
modal.previewRequestId = requestId;
|
|
5240
|
+
modal.loading = true;
|
|
5241
|
+
modal.error = null;
|
|
5242
|
+
emitChange();
|
|
5243
|
+
|
|
5244
|
+
try {
|
|
5245
|
+
const options = getTypeGenerationRequestOptions(modal);
|
|
5246
|
+
const isAllTables = modal.scope === 'all';
|
|
5247
|
+
const tableNames = isAllTables ? modal.tableNames : [modal.tableName];
|
|
5248
|
+
|
|
5249
|
+
if (!tableNames.length) {
|
|
5250
|
+
throw new Error('No tables available for type generation.');
|
|
5251
|
+
}
|
|
5252
|
+
|
|
5253
|
+
const responses = await Promise.all(
|
|
5254
|
+
tableNames.map(async tableName => {
|
|
5255
|
+
const response = await api.generateStructureTypes(tableName, {
|
|
5256
|
+
target: modal.target,
|
|
5257
|
+
options,
|
|
5258
|
+
});
|
|
5259
|
+
|
|
5260
|
+
return {
|
|
5261
|
+
...response.data,
|
|
5262
|
+
tableName,
|
|
5263
|
+
warnings: response.warnings ?? [],
|
|
5264
|
+
metadata: response.metadata ?? {},
|
|
5265
|
+
};
|
|
5266
|
+
}),
|
|
5267
|
+
);
|
|
5268
|
+
|
|
5269
|
+
if (state.modal?.kind !== 'generate-types' || state.modal.previewRequestId !== requestId) {
|
|
5270
|
+
return;
|
|
5271
|
+
}
|
|
5272
|
+
|
|
5273
|
+
if (isAllTables) {
|
|
5274
|
+
state.modal.result = {
|
|
5275
|
+
scope: 'all',
|
|
5276
|
+
target: modal.target,
|
|
5277
|
+
fileName: `${responses.length} files`,
|
|
5278
|
+
files: responses,
|
|
5279
|
+
code: combineGeneratedTypeFiles(responses),
|
|
5280
|
+
};
|
|
5281
|
+
state.modal.warnings = responses.flatMap(file =>
|
|
5282
|
+
(file.warnings ?? []).map(warning => `${file.tableName}: ${warning}`),
|
|
5283
|
+
);
|
|
5284
|
+
state.modal.metadata = {
|
|
5285
|
+
fileCount: responses.length,
|
|
5286
|
+
columnCount: responses.reduce((count, file) => count + Number(file.metadata?.columnCount ?? 0), 0),
|
|
5287
|
+
};
|
|
5288
|
+
} else {
|
|
5289
|
+
const [response] = responses;
|
|
5290
|
+
state.modal.result = response;
|
|
5291
|
+
state.modal.warnings = response.warnings ?? [];
|
|
5292
|
+
state.modal.metadata = response.metadata ?? {};
|
|
5293
|
+
}
|
|
5294
|
+
} catch (error) {
|
|
5295
|
+
if (state.modal?.kind !== 'generate-types' || state.modal.previewRequestId !== requestId) {
|
|
5296
|
+
return;
|
|
5297
|
+
}
|
|
5298
|
+
|
|
5299
|
+
state.modal.error = normalizeError(error);
|
|
5300
|
+
state.modal.result = null;
|
|
5301
|
+
state.modal.warnings = error.warnings ?? [];
|
|
5302
|
+
} finally {
|
|
5303
|
+
if (state.modal?.kind === 'generate-types' && state.modal.previewRequestId === requestId) {
|
|
5304
|
+
state.modal.loading = false;
|
|
5305
|
+
emitChange();
|
|
5306
|
+
}
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
|
|
5310
|
+
export async function openGenerateTypesModal(tableName, target = 'typescript', scope = 'selected') {
|
|
5311
|
+
const normalizedTarget = TYPE_GENERATION_TARGETS.has(target) ? target : 'typescript';
|
|
5312
|
+
const normalizedScope = scope === 'all' ? 'all' : 'selected';
|
|
5313
|
+
const tableNames = getStructureTableNames();
|
|
5314
|
+
const normalizedTableName = String(tableName ?? state.structure.selectedName ?? '').trim();
|
|
5315
|
+
|
|
5316
|
+
if (normalizedScope === 'all' && !tableNames.length) {
|
|
5317
|
+
pushToast('No tables available for type generation.', 'alert');
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5321
|
+
if (normalizedScope === 'selected' && !normalizedTableName) {
|
|
5322
|
+
pushToast('Select a table before generating types.', 'alert');
|
|
5323
|
+
return;
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5326
|
+
state.modal = {
|
|
5327
|
+
kind: 'generate-types',
|
|
5328
|
+
scope: normalizedScope,
|
|
5329
|
+
tableName: normalizedScope === 'all' ? '' : normalizedTableName,
|
|
5330
|
+
tableNames: normalizedScope === 'all' ? tableNames : [],
|
|
5331
|
+
target: normalizedTarget,
|
|
5332
|
+
options: getDefaultTypeGenerationOptions(normalizedTarget),
|
|
5333
|
+
result: null,
|
|
5334
|
+
warnings: [],
|
|
5335
|
+
metadata: {},
|
|
5336
|
+
loading: false,
|
|
5337
|
+
error: null,
|
|
5338
|
+
submitting: false,
|
|
5339
|
+
};
|
|
5340
|
+
emitChange();
|
|
5341
|
+
await refreshTypeGenerationPreview();
|
|
5342
|
+
}
|
|
5343
|
+
|
|
5344
|
+
export async function updateGenerateTypesModal(field, value) {
|
|
5345
|
+
if (state.modal?.kind !== 'generate-types') {
|
|
5346
|
+
return;
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5349
|
+
if (field === 'target') {
|
|
5350
|
+
const target = TYPE_GENERATION_TARGETS.has(value) ? value : 'typescript';
|
|
5351
|
+
state.modal.target = target;
|
|
5352
|
+
state.modal.options = normalizeTypeGenerationOptions(
|
|
5353
|
+
{
|
|
5354
|
+
...state.modal.options,
|
|
5355
|
+
propertyNaming: getDefaultTypeGenerationOptions(target).propertyNaming,
|
|
5356
|
+
},
|
|
5357
|
+
target,
|
|
5358
|
+
);
|
|
5359
|
+
} else if (field in state.modal.options) {
|
|
5360
|
+
const booleanFields = new Set([
|
|
5361
|
+
'includeComments',
|
|
5362
|
+
'includeDefaultsAsComments',
|
|
5363
|
+
'includeGeneratedColumns',
|
|
5364
|
+
'includeHiddenColumns',
|
|
5365
|
+
'exportDeclaration',
|
|
5366
|
+
]);
|
|
5367
|
+
state.modal.options[field] = booleanFields.has(field) ? Boolean(value) : String(value ?? '');
|
|
5368
|
+
state.modal.options = normalizeTypeGenerationOptions(state.modal.options, state.modal.target);
|
|
5369
|
+
}
|
|
5370
|
+
|
|
5371
|
+
emitChange();
|
|
5372
|
+
await refreshTypeGenerationPreview();
|
|
5373
|
+
}
|
|
5374
|
+
|
|
4513
5375
|
export function toggleStructureTablesPanel() {
|
|
4514
5376
|
state.structure.tablesVisible = state.structure.tablesVisible === false;
|
|
4515
5377
|
storeBoolean(UI_PREFERENCE_STORAGE_KEYS.structureTablesVisible, state.structure.tablesVisible);
|