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.
Files changed (169) hide show
  1. package/README.md +113 -71
  2. package/bin/sqlite-hub.js +723 -116
  3. package/docs/API.md +152 -0
  4. package/docs/CLI.md +249 -0
  5. package/docs/CLI_API_PARITY.md +61 -0
  6. package/docs/changelog.md +149 -0
  7. package/docs/guidelines/AGENTS.md +32 -0
  8. package/docs/guidelines/DESIGN.md +55 -0
  9. package/docs/shortkeys.md +27 -0
  10. package/docs/todo.md +4 -0
  11. package/examples/api/generate-types.js +52 -0
  12. package/frontend/assets/mockups/backups_1_1920.webp +0 -0
  13. package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
  14. package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
  15. package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
  16. package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
  17. package/frontend/assets/mockups/charts_1_1920.webp +0 -0
  18. package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
  19. package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
  20. package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
  21. package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
  22. package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
  23. package/frontend/assets/mockups/connections_1_1920.webp +0 -0
  24. package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
  25. package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
  26. package/frontend/assets/mockups/data_1_1920.webp +0 -0
  27. package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
  28. package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
  29. package/frontend/assets/mockups/documents_1_1920.webp +0 -0
  30. package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
  31. package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
  32. package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
  33. package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
  34. package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
  35. package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
  36. package/frontend/assets/mockups/overview_1_1920.webp +0 -0
  37. package/frontend/assets/mockups/settings_1_1920.webp +0 -0
  38. package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
  39. package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
  40. package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
  41. package/frontend/assets/mockups/structure_1_1920.webp +0 -0
  42. package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
  43. package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
  44. package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
  45. package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
  46. package/frontend/js/api.js +63 -0
  47. package/frontend/js/app.js +310 -10
  48. package/frontend/js/components/connectionCard.js +4 -2
  49. package/frontend/js/components/emptyState.js +4 -4
  50. package/frontend/js/components/generateTypesDropdown.js +33 -0
  51. package/frontend/js/components/metricCard.js +1 -1
  52. package/frontend/js/components/modal.js +555 -21
  53. package/frontend/js/components/pageHeader.js +1 -1
  54. package/frontend/js/components/queryHistoryDetail.js +3 -3
  55. package/frontend/js/components/queryHistoryHeader.js +1 -1
  56. package/frontend/js/components/queryHistoryPanel.js +1 -1
  57. package/frontend/js/components/rowEditorPanel.js +13 -3
  58. package/frontend/js/components/sidebar.js +41 -7
  59. package/frontend/js/components/topNav.js +2 -14
  60. package/frontend/js/router.js +38 -6
  61. package/frontend/js/store.js +870 -8
  62. package/frontend/js/utils/inputClear.js +36 -0
  63. package/frontend/js/utils/syntheticData.js +240 -0
  64. package/frontend/js/views/backups.js +581 -37
  65. package/frontend/js/views/charts.js +9 -9
  66. package/frontend/js/views/connections.js +2 -2
  67. package/frontend/js/views/data.js +17 -1
  68. package/frontend/js/views/documents.js +3 -3
  69. package/frontend/js/views/editor.js +6 -6
  70. package/frontend/js/views/logs.js +339 -0
  71. package/frontend/js/views/mediaTagging.js +7 -5
  72. package/frontend/js/views/overview.js +3 -3
  73. package/frontend/js/views/settings.js +80 -30
  74. package/frontend/js/views/structure.js +12 -1
  75. package/frontend/js/views/tableAdvisor.js +385 -0
  76. package/frontend/js/views/tableDesigner.js +6 -0
  77. package/frontend/styles/base.css +1 -40
  78. package/frontend/styles/components.css +258 -232
  79. package/frontend/styles/structure-graph.css +32 -68
  80. package/frontend/styles/tailwind.css +8 -4
  81. package/frontend/styles/tailwind.generated.css +1 -1
  82. package/frontend/styles/tokens.css +28 -21
  83. package/frontend/styles/views.css +102 -261
  84. package/package.json +18 -2
  85. package/server/routes/backups.js +18 -2
  86. package/server/routes/data.js +45 -0
  87. package/server/routes/externalApi.js +321 -2
  88. package/server/routes/logs.js +127 -0
  89. package/server/routes/structure.js +22 -0
  90. package/server/server.js +3 -0
  91. package/server/services/databaseCommandService.js +59 -0
  92. package/server/services/sqlite/backupDiff.js +914 -0
  93. package/server/services/sqlite/backupService.js +77 -1
  94. package/server/services/sqlite/dataBrowserService.js +36 -0
  95. package/server/services/sqlite/introspection.js +226 -22
  96. package/server/services/sqlite/structureService.js +7 -0
  97. package/server/services/sqlite/syntheticDataGenerator.js +728 -0
  98. package/server/services/sqlite/tableAdvisor.js +790 -0
  99. package/server/services/storage/appStateStore.js +773 -169
  100. package/server/services/typeGenerationService.js +663 -0
  101. package/tailwind.config.cjs +0 -1
  102. package/.github/funding.yml +0 -2
  103. package/.github/workflows/ci.yml +0 -36
  104. package/database.sqlite +0 -0
  105. package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
  106. package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
  107. package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
  108. package/frontend/assets/mockups/connections_1200.webp +0 -0
  109. package/frontend/assets/mockups/data_1_1200.webp +0 -0
  110. package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
  111. package/frontend/assets/mockups/documents_1200.webp +0 -0
  112. package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
  113. package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
  114. package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
  115. package/frontend/assets/mockups/overview_1200.webp +0 -0
  116. package/frontend/assets/mockups/settings_1200.webp +0 -0
  117. package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
  118. package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
  119. package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
  120. package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
  121. package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
  122. package/frontend/assets/mockups/structure_1_1200.webp +0 -0
  123. package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
  124. package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
  125. package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
  126. package/tests/api-token-auth.test.js +0 -279
  127. package/tests/backup-manager.test.js +0 -140
  128. package/tests/backups-view.test.js +0 -70
  129. package/tests/charts-height-preset-storage.test.js +0 -60
  130. package/tests/charts-route-state.test.js +0 -144
  131. package/tests/check-constraint-options.test.js +0 -90
  132. package/tests/cli-args.test.js +0 -113
  133. package/tests/cli-service-delegation.test.js +0 -140
  134. package/tests/connection-removal.test.js +0 -52
  135. package/tests/connections-file-dialog-route.test.js +0 -89
  136. package/tests/copy-column-modal.test.js +0 -131
  137. package/tests/database-command-service.test.js +0 -174
  138. package/tests/database-documents.test.js +0 -85
  139. package/tests/documents-view.test.js +0 -132
  140. package/tests/dropdown-button.test.js +0 -101
  141. package/tests/email-preview.test.js +0 -89
  142. package/tests/export-blob.test.js +0 -99
  143. package/tests/export-filenames.test.js +0 -38
  144. package/tests/file-path-preview.test.js +0 -165
  145. package/tests/form-controls.test.js +0 -34
  146. package/tests/json-preview.test.js +0 -49
  147. package/tests/local-request-security.test.js +0 -85
  148. package/tests/markdown-documents.test.js +0 -103
  149. package/tests/native-file-dialog.test.js +0 -105
  150. package/tests/query-editor.test.js +0 -28
  151. package/tests/query-history-detail.test.js +0 -37
  152. package/tests/query-history-header.test.js +0 -30
  153. package/tests/query-results-truncation.test.js +0 -20
  154. package/tests/risky-sql.test.js +0 -30
  155. package/tests/row-editor-json.test.js +0 -82
  156. package/tests/row-editor-null-values.test.js +0 -184
  157. package/tests/row-editor-timestamp-preview.test.js +0 -192
  158. package/tests/security-paths.test.js +0 -84
  159. package/tests/settings-api-tokens-route.test.js +0 -97
  160. package/tests/settings-metadata.test.js +0 -114
  161. package/tests/settings-view.test.js +0 -108
  162. package/tests/sql-formatter.test.js +0 -173
  163. package/tests/sql-highlight.test.js +0 -38
  164. package/tests/sql-identifier-safety.test.js +0 -171
  165. package/tests/sql-result-limit.test.js +0 -66
  166. package/tests/structure-view.test.js +0 -60
  167. package/tests/table-designer-v2-unique-constraints.test.js +0 -78
  168. package/tests/table-scroll-state.test.js +0 -70
  169. package/tests/text-cell-stats.test.js +0 -38
@@ -100,7 +100,7 @@ export function renderQueryHistoryDetail({
100
100
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
101
101
  <div class="border-b border-outline-variant/10 px-5 py-4">
102
102
  <div class="flex items-center justify-between gap-3">
103
- <span class="font-headline text-sm font-black uppercase tracking-[0.18em] text-primary-container">
103
+ <span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
104
104
  Query Detail
105
105
  </span>
106
106
  <button
@@ -124,7 +124,7 @@ export function renderQueryHistoryDetail({
124
124
  <section class="flex h-full min-h-0 flex-col bg-surface-low">
125
125
  <div class="border-b border-outline-variant/10 px-5 py-4">
126
126
  <div class="flex items-center justify-between gap-3">
127
- <span class="font-headline text-sm font-black uppercase tracking-[0.18em] text-primary-container">
127
+ <span class="font-body text-sm font-black uppercase tracking-[0.18em] text-primary-container">
128
128
  Query Detail
129
129
  </span>
130
130
  <button
@@ -178,7 +178,7 @@ export function renderQueryHistoryDetail({
178
178
  '<div class="border-b border-outline-variant/10 px-5 py-4">',
179
179
  '<div class="flex items-center justify-between gap-3"><div>',
180
180
  '<div class="text-[10px] font-mono uppercase tracking-[0.18em] text-primary-container/70">Query Detail</div>',
181
- '<h2 class="mt-1 font-headline text-lg font-black uppercase tracking-tight text-on-surface">',
181
+ '<h2 class="mt-1 font-body text-lg font-black uppercase tracking-tight text-on-surface">',
182
182
  escapeHtml(item.displayTitle),
183
183
  "</h2></div>",
184
184
  '<button class="query-history-icon-button" data-action="clear-query-history-selection" type="button"><span class="material-symbols-outlined text-[18px]">close</span></button>',
@@ -11,7 +11,7 @@ export function renderQueryHistoryHeader({
11
11
  <div class="flex items-center justify-between gap-3">
12
12
  <div class="flex items-center gap-2">
13
13
  <span class="material-symbols-outlined text-[18px] text-primary-container">${escapeHtml(icon)}</span>
14
- <span class="font-headline text-xs font-black uppercase tracking-[0.18em] text-primary-container">
14
+ <span class="font-body text-xs font-black uppercase tracking-[0.18em] text-primary-container">
15
15
  ${escapeHtml(title)}
16
16
  </span>
17
17
  </div>
@@ -114,7 +114,7 @@ export function renderQueryHistoryPanel({
114
114
  ? `
115
115
  <div class="flex h-full min-h-[240px] flex-col items-center justify-center px-6 text-center">
116
116
  <span class="material-symbols-outlined text-4xl text-on-surface-variant/25">manage_search</span>
117
- <p class="mt-4 font-headline text-lg font-bold uppercase tracking-tight text-on-surface">
117
+ <p class="mt-4 font-body text-lg font-bold uppercase tracking-tight text-on-surface">
118
118
  No Matching Queries
119
119
  </p>
120
120
  <p class="mt-2 max-w-xs text-sm leading-6 text-on-surface-variant/65">
@@ -176,6 +176,14 @@ function getFieldTimestampPreview(field = {}, tableMeta = {}) {
176
176
  });
177
177
  }
178
178
 
179
+ function isNumberInputCompatibleValue(value) {
180
+ if (value === null || value === undefined || value === "") {
181
+ return true;
182
+ }
183
+
184
+ return /^[-+]?(?:\d+|\d*\.\d+)(?:e[-+]?\d+)?$/i.test(String(value).trim());
185
+ }
186
+
179
187
  function renderTimestampPreview(field = {}, tableMeta = {}) {
180
188
  const preview = getFieldTimestampPreview(field, tableMeta);
181
189
 
@@ -361,7 +369,9 @@ function renderEditableField(field, tableMeta = {}) {
361
369
  const baseBadges = withCheckBadge(Array.isArray(field.badges) ? field.badges : [], allowedValues);
362
370
  const badges = withEmailBadge(withFilePathBadge(withUrlBadge(baseBadges, url), filePathPreview), email);
363
371
  const jsonPreview = formatJsonPreview(field.value);
364
- const inputType = field.inputType === "number" ? "number" : "text";
372
+ const canUseNumberInput =
373
+ field.inputType === "number" && isNumberInputCompatibleValue(getFieldRawValue(field));
374
+ const inputType = canUseNumberInput ? "number" : "text";
365
375
  const numberStep = field.numberStep === "1" ? "1" : "any";
366
376
  const valueState = getRowEditorValueState(getFieldRawValue(field));
367
377
 
@@ -577,7 +587,7 @@ export function renderRowEditorPanel({
577
587
  <div class="text-[10px] font-bold uppercase tracking-[0.2em] text-primary-container">
578
588
  ${escapeHtml(sectionLabel)}
579
589
  </div>
580
- <h2 class="mt-2 font-headline text-3xl font-black uppercase tracking-tight text-primary-container">
590
+ <h2 class="mt-2 font-body text-3xl font-black uppercase tracking-tight text-primary-container">
581
591
  ${escapeHtml(title)}
582
592
  </h2>
583
593
  ${
@@ -647,7 +657,7 @@ export function renderRowEditorPanel({
647
657
  saveError
648
658
  ? `
649
659
  <div class="border border-error/20 bg-error-container/10 px-4 py-4 text-sm text-on-surface">
650
- <div class="font-headline text-xs font-bold uppercase tracking-[0.18em] text-error">
660
+ <div class="font-body text-xs font-bold uppercase tracking-[0.18em] text-error">
651
661
  ${escapeHtml(saveError.code)}
652
662
  </div>
653
663
  <div class="mt-2">${escapeHtml(saveError.message)}</div>
@@ -3,13 +3,36 @@ import { renderConnectionLogo } from './connectionLogo.js';
3
3
 
4
4
  const sidebarItems = [
5
5
  { label: 'Connections', href: '#/connections', key: 'connections', icon: 'database' },
6
- { label: 'Overview', href: '#/overview', key: 'overview', icon: 'dashboard' },
7
6
  { label: 'Data', href: '#/data', key: 'data', icon: 'table_rows' },
8
- { label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
9
7
  { label: 'SQL_Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
10
- { label: 'Charts', href: '#/charts', key: 'charts', icon: 'bar_chart' },
11
- { label: 'Documents', href: '#/documents', key: 'documents', icon: 'description' },
12
- { label: 'Table_Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
8
+ {
9
+ label: 'SCHEMA',
10
+ key: 'schema',
11
+ icon: 'account_tree',
12
+ children: [
13
+ { label: 'STRUCTURE', href: '#/structure', key: 'structure' },
14
+ { label: 'ADVISOR', href: '#/table-advisor', key: 'tableAdvisor' },
15
+ { label: 'DESIGNER', href: '#/table-designer', key: 'tableDesigner' },
16
+ ],
17
+ },
18
+ {
19
+ label: 'Insights',
20
+ key: 'insights',
21
+ icon: 'monitoring',
22
+ children: [
23
+ { label: 'CHARTS', href: '#/charts', key: 'charts' },
24
+ { label: 'OVERVIEW', href: '#/overview', key: 'overview' },
25
+ ],
26
+ },
27
+ {
28
+ label: 'Workspace',
29
+ key: 'workspace',
30
+ icon: 'folder_open',
31
+ children: [
32
+ { label: 'DOCUMENTS', href: '#/documents', key: 'documents' },
33
+ { label: 'BACKUPS', href: '#/backups', key: 'backups' },
34
+ ],
35
+ },
13
36
  {
14
37
  label: 'MEDIA_TAGGING',
15
38
  key: 'mediaTagging',
@@ -19,7 +42,6 @@ const sidebarItems = [
19
42
  { label: 'TAGGING_QUEUE', href: '#/media-tagging/queue', key: 'mediaTaggingQueue' },
20
43
  ],
21
44
  },
22
- { label: 'Backups', href: '#/backups', key: 'backups', icon: 'inventory_2' },
23
45
  { label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },
24
46
  ];
25
47
 
@@ -36,6 +58,18 @@ function getActiveSidebarKey(routeName) {
36
58
  return 'mediaTagging';
37
59
  }
38
60
 
61
+ if (routeName === 'structure' || routeName === 'tableAdvisor' || routeName === 'tableDesigner') {
62
+ return 'schema';
63
+ }
64
+
65
+ if (routeName === 'charts' || routeName === 'overview') {
66
+ return 'insights';
67
+ }
68
+
69
+ if (routeName === 'documents' || routeName === 'backups') {
70
+ return 'workspace';
71
+ }
72
+
39
73
  return routeName;
40
74
  }
41
75
 
@@ -140,7 +174,7 @@ function renderConnectionQuickPicker(state) {
140
174
 
141
175
  export function renderSidebar(state) {
142
176
  const activeKey = getActiveSidebarKey(state.route.name);
143
- const expandedKey = activeKey === 'mediaTagging' ? 'mediaTagging' : null;
177
+ const expandedKey = sidebarItems.some(item => item.key === activeKey && item.children) ? activeKey : null;
144
178
 
145
179
  return `
146
180
  <nav class="sidebar-links">
@@ -3,20 +3,8 @@ export function renderTopNav() {
3
3
  <div class="top-nav-shell">
4
4
  <a class="top-nav-brand" href="#/">SQLite Hub</a>
5
5
  <div class="top-nav-actions">
6
- <button
7
- class="top-nav-icon"
8
- data-action="open-modal"
9
- data-modal="open-connection"
10
- type="button"
11
- aria-label="Open Database"
12
- >
13
- <span class="material-symbols-outlined">folder_open</span>
14
- </button>
15
- <button class="top-nav-icon" data-action="navigate" data-to="/editor" type="button" aria-label="SQL Editor">
16
- <span class="material-symbols-outlined">terminal</span>
17
- </button>
18
- <button class="top-nav-icon" data-action="navigate" data-to="/settings" type="button" aria-label="Settings">
19
- <span class="material-symbols-outlined">settings</span>
6
+ <button class="top-nav-icon" data-action="navigate" data-to="/logs" type="button" aria-label="Logs">
7
+ <span class="material-symbols-outlined">receipt_long</span>
20
8
  </button>
21
9
  </div>
22
10
  </div>
@@ -1,8 +1,29 @@
1
+ function decodeRouteValue(value) {
2
+ try {
3
+ return decodeURIComponent(value);
4
+ } catch {
5
+ return value;
6
+ }
7
+ }
8
+
1
9
  export function parseHash(hash = window.location.hash) {
2
10
  const normalized = hash.startsWith('#') ? hash.slice(1) : hash;
3
- const [pathname = '/'] = normalized.split('?');
11
+ const queryIndex = normalized.indexOf('?');
12
+ const pathWithFragment = queryIndex >= 0 ? normalized.slice(0, queryIndex) : normalized;
13
+ const queryWithFragment = queryIndex >= 0 ? normalized.slice(queryIndex + 1) : '';
14
+ const pathFragmentIndex = pathWithFragment.indexOf('#');
15
+ const queryFragmentIndex = queryWithFragment.indexOf('#');
16
+ const pathname =
17
+ pathFragmentIndex >= 0 ? pathWithFragment.slice(0, pathFragmentIndex) : pathWithFragment || '/';
18
+ const routeFragment =
19
+ pathFragmentIndex >= 0
20
+ ? pathWithFragment.slice(pathFragmentIndex + 1)
21
+ : queryFragmentIndex >= 0
22
+ ? queryWithFragment.slice(queryFragmentIndex + 1)
23
+ : null;
4
24
  const cleanPath = pathname || '/';
5
25
  const segments = cleanPath.split('/').filter(Boolean);
26
+ const decodedRouteFragment = routeFragment ? decodeRouteValue(routeFragment) : null;
6
27
 
7
28
  if (segments.length === 0) {
8
29
  return { name: 'landing', path: '/', params: {} };
@@ -20,7 +41,7 @@ export function parseHash(hash = window.location.hash) {
20
41
  name: 'charts',
21
42
  path: cleanPath,
22
43
  params: {
23
- historyId: segments[1] ? decodeURIComponent(segments[1]) : null,
44
+ historyId: segments[1] ? decodeRouteValue(segments[1]) : null,
24
45
  },
25
46
  };
26
47
  case 'documents':
@@ -28,7 +49,7 @@ export function parseHash(hash = window.location.hash) {
28
49
  name: 'documents',
29
50
  path: cleanPath,
30
51
  params: {
31
- documentId: segments[1] ? decodeURIComponent(segments[1]) : null,
52
+ documentId: segments[1] ? decodeRouteValue(segments[1]) : null,
32
53
  },
33
54
  };
34
55
  case 'editor':
@@ -42,7 +63,8 @@ export function parseHash(hash = window.location.hash) {
42
63
  name: 'data',
43
64
  path: cleanPath,
44
65
  params: {
45
- tableName: segments[1] ? decodeURIComponent(segments[1]) : null,
66
+ tableName: segments[1] ? decodeRouteValue(segments[1]) : null,
67
+ rowPrimaryKey: decodedRouteFragment,
46
68
  },
47
69
  };
48
70
  case 'structure':
@@ -50,7 +72,7 @@ export function parseHash(hash = window.location.hash) {
50
72
  name: 'structure',
51
73
  path: cleanPath,
52
74
  params: {
53
- tableName: segments[1] ? decodeURIComponent(segments[1]) : null,
75
+ tableName: segments[1] ? decodeRouteValue(segments[1]) : null,
54
76
  },
55
77
  };
56
78
  case 'table-designer':
@@ -59,7 +81,15 @@ export function parseHash(hash = window.location.hash) {
59
81
  path: cleanPath,
60
82
  params: {
61
83
  isNew: segments[1] === 'new',
62
- tableName: segments[1] && segments[1] !== 'new' ? decodeURIComponent(segments[1]) : null,
84
+ tableName: segments[1] && segments[1] !== 'new' ? decodeRouteValue(segments[1]) : null,
85
+ },
86
+ };
87
+ case 'table-advisor':
88
+ return {
89
+ name: 'tableAdvisor',
90
+ path: cleanPath,
91
+ params: {
92
+ tableName: segments[1] ? decodeRouteValue(segments[1]) : null,
63
93
  },
64
94
  };
65
95
  case 'media-tagging':
@@ -69,6 +99,8 @@ export function parseHash(hash = window.location.hash) {
69
99
  return { name: 'mediaTaggingSetup', path: '/media-tagging', params: {} };
70
100
  case 'settings':
71
101
  return { name: 'settings', path: '/settings', params: {} };
102
+ case 'logs':
103
+ return { name: 'logs', path: '/logs', params: {} };
72
104
  default:
73
105
  return { name: 'notFound', path: cleanPath, params: {} };
74
106
  }