sqlite-hub 0.5.0 → 0.7.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 (50) hide show
  1. package/README.md +109 -2
  2. package/bin/sqlite-hub.js +285 -4
  3. package/changelog.md +15 -0
  4. package/frontend/assets/mockups/connections.png +0 -0
  5. package/frontend/assets/mockups/data.png +0 -0
  6. package/frontend/assets/mockups/data_row_editor.png +0 -0
  7. package/frontend/assets/mockups/home.png +0 -0
  8. package/frontend/assets/mockups/sql_editor.png +0 -0
  9. package/frontend/assets/mockups/sql_editor_croped.png +0 -0
  10. package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
  11. package/frontend/assets/mockups/structure.png +0 -0
  12. package/frontend/assets/mockups/structure_inspector.png +0 -0
  13. package/frontend/index.html +1 -0
  14. package/frontend/js/api.js +49 -0
  15. package/frontend/js/app.js +377 -9
  16. package/frontend/js/components/modal.js +314 -1
  17. package/frontend/js/components/queryChartRenderer.js +125 -0
  18. package/frontend/js/components/queryEditor.js +37 -8
  19. package/frontend/js/components/queryHistoryPanel.js +16 -5
  20. package/frontend/js/components/sidebar.js +2 -0
  21. package/frontend/js/components/tableDesignerEditor.js +356 -0
  22. package/frontend/js/components/tableDesignerSidebar.js +129 -0
  23. package/frontend/js/components/tableDesignerSqlPreview.js +40 -0
  24. package/frontend/js/lib/queryChartOptions.js +283 -0
  25. package/frontend/js/lib/queryCharts.js +560 -0
  26. package/frontend/js/router.js +18 -0
  27. package/frontend/js/store.js +914 -1
  28. package/frontend/js/utils/tableDesigner.js +1192 -0
  29. package/frontend/js/views/charts.js +471 -0
  30. package/frontend/js/views/data.js +281 -277
  31. package/frontend/js/views/editor.js +19 -13
  32. package/frontend/js/views/structure.js +81 -39
  33. package/frontend/js/views/tableDesigner.js +37 -0
  34. package/frontend/styles/base.css +87 -73
  35. package/frontend/styles/components.css +790 -251
  36. package/frontend/styles/views.css +316 -46
  37. package/package.json +2 -1
  38. package/server/routes/charts.js +153 -0
  39. package/server/routes/tableDesigner.js +60 -0
  40. package/server/server.js +10 -0
  41. package/server/services/sqlite/tableDesigner/changeAnalysis.js +295 -0
  42. package/server/services/sqlite/tableDesigner/schemaMapping.js +233 -0
  43. package/server/services/sqlite/tableDesigner/sql.js +63 -0
  44. package/server/services/sqlite/tableDesigner/validation.js +245 -0
  45. package/server/services/sqlite/tableDesignerService.js +181 -0
  46. package/server/services/storage/appStateStore.js +400 -1
  47. package/server/services/storage/queryHistoryChartUtils.js +145 -0
  48. package/frontend/assets/mockups/data_edit.png +0 -0
  49. package/frontend/assets/mockups/graph_visualize.png +0 -0
  50. package/frontend/assets/mockups/overview.png +0 -0
@@ -1,84 +1,354 @@
1
1
  .machined-grid {
2
- background-image: radial-gradient(var(--color-grid-dot) 1px, transparent 1px);
3
- background-size: 32px 32px;
2
+ background-image: radial-gradient(var(--color-grid-dot) 1px, transparent 1px);
3
+ background-size: 32px 32px;
4
4
  }
5
5
 
6
6
  .data-grid-texture {
7
- background-image: radial-gradient(var(--color-primary-container) 1px, transparent 1px);
8
- background-size: 24px 24px;
9
- opacity: 0.03;
7
+ background-image: radial-gradient(var(--color-primary-container) 1px, transparent 1px);
8
+ background-size: 24px 24px;
9
+ opacity: 0.03;
10
10
  }
11
11
 
12
12
  .view-surface {
13
- background: var(--color-surface);
14
- min-height: 100%;
13
+ background: var(--color-surface);
14
+ min-height: 100%;
15
15
  }
16
16
 
17
17
  .view-frame {
18
- min-height: 100%;
19
- padding: var(--spacing-8);
18
+ min-height: 100%;
19
+ padding: var(--spacing-8);
20
20
  }
21
21
 
22
22
  .view-stack {
23
- display: flex;
24
- flex-direction: column;
25
- gap: var(--spacing-8);
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: var(--spacing-8);
26
+ }
27
+
28
+ .table-designer-view {
29
+ display: flex;
30
+ height: 100%;
31
+ min-height: 0;
32
+ overflow: hidden;
33
+ }
34
+
35
+ .table-designer-workspace {
36
+ display: flex;
37
+ flex: 1;
38
+ flex-direction: column;
39
+ gap: var(--spacing-4);
40
+ min-height: 0;
41
+ min-width: 0;
42
+ }
43
+
44
+ .table-designer-workspace__top,
45
+ .table-designer-workspace__bottom {
46
+ min-height: 0;
47
+ min-width: 0;
48
+ }
49
+
50
+ .table-designer-workspace__top {
51
+ flex: 1;
52
+ }
53
+
54
+ .table-designer-workspace__bottom {
55
+ flex: 0 0 34%;
26
56
  }
27
57
 
28
58
  .landing-view {
29
- align-items: center;
30
- display: flex;
31
- justify-content: center;
32
- min-height: 100%;
33
- overflow: hidden;
34
- position: relative;
59
+ align-items: center;
60
+ display: flex;
61
+ justify-content: center;
62
+ min-height: 100%;
63
+ overflow: hidden;
64
+ position: relative;
35
65
  }
36
66
 
37
67
  .landing-accent {
38
- background: rgba(75, 71, 50, 0.2);
39
- height: 1px;
40
- position: absolute;
68
+ background: rgba(75, 71, 50, 0.2);
69
+ height: 1px;
70
+ position: absolute;
41
71
  }
42
72
 
43
73
  .landing-accent--a {
44
- left: 3rem;
45
- top: 2.5rem;
46
- transform: rotate(45deg);
47
- width: 8rem;
74
+ left: 3rem;
75
+ top: 2.5rem;
76
+ transform: rotate(45deg);
77
+ width: 8rem;
48
78
  }
49
79
 
50
80
  .landing-accent--b {
51
- bottom: 2.5rem;
52
- right: 3rem;
53
- transform: rotate(-12deg);
54
- width: 16rem;
81
+ bottom: 2.5rem;
82
+ right: 3rem;
83
+ transform: rotate(-12deg);
84
+ width: 16rem;
55
85
  }
56
86
 
57
87
  .landing-accent--c {
58
- border-bottom: 1px solid rgba(75, 71, 50, 0.12);
59
- border-right: 1px solid rgba(75, 71, 50, 0.12);
60
- height: 4rem;
61
- left: 0;
62
- top: 50%;
63
- width: 4rem;
88
+ border-bottom: 1px solid rgba(75, 71, 50, 0.12);
89
+ border-right: 1px solid rgba(75, 71, 50, 0.12);
90
+ height: 4rem;
91
+ left: 0;
92
+ top: 50%;
93
+ width: 4rem;
64
94
  }
65
95
 
66
96
  .system-placeholder-grid {
67
- display: grid;
68
- gap: var(--spacing-4);
69
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
97
+ display: grid;
98
+ gap: var(--spacing-4);
99
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
70
100
  }
71
101
 
72
102
  .system-placeholder-card {
73
- background: var(--color-surface-low);
74
- border-bottom: 2px solid rgba(49, 48, 48, 1);
75
- display: flex;
76
- flex-direction: column;
77
- gap: var(--spacing-2);
78
- min-height: 160px;
79
- padding: var(--spacing-6);
103
+ background: var(--color-surface-low);
104
+ border-bottom: 2px solid rgba(49, 48, 48, 1);
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: var(--spacing-2);
108
+ min-height: 160px;
109
+ padding: var(--spacing-6);
80
110
  }
81
111
 
82
112
  .system-placeholder-card.is-primary {
83
- border-bottom-color: var(--color-primary-container);
113
+ border-bottom-color: var(--color-primary-container);
114
+ }
115
+
116
+ .charts-view {
117
+ background: var(--color-surface);
118
+ display: flex;
119
+ height: 100%;
120
+ min-height: 0;
121
+ overflow: hidden;
122
+ }
123
+
124
+ .charts-view__sidebar {
125
+ background: var(--color-surface-low);
126
+ border-right: 1px solid rgba(75, 71, 50, 0.18);
127
+ display: flex;
128
+ flex: 0 0 23rem;
129
+ flex-direction: column;
130
+ min-height: 0;
131
+ width: 23rem;
132
+ }
133
+
134
+ .charts-view__sidebar-header {
135
+ border-bottom: 1px solid rgba(75, 71, 50, 0.18);
136
+ padding: var(--spacing-5);
137
+ }
138
+
139
+ .structure-view {
140
+ background: var(--color-surface);
141
+ display: flex;
142
+ height: 100%;
143
+ min-height: 0;
144
+ overflow: hidden;
145
+ }
146
+
147
+ .structure-view__sidebar {
148
+ background: var(--color-surface-low);
149
+ border-right: 1px solid rgba(75, 71, 50, 0.18);
150
+ display: flex;
151
+ flex: 0 0 18.5rem;
152
+ flex-direction: column;
153
+ min-height: 0;
154
+ width: 18.5rem;
155
+ }
156
+
157
+ .structure-view__sidebar-header {
158
+ border-bottom: 1px solid rgba(75, 71, 50, 0.18);
159
+ padding: var(--spacing-5);
160
+ }
161
+
162
+ .structure-view__sidebar-body {
163
+ flex: 1;
164
+ min-height: 0;
165
+ overflow-y: auto;
166
+ padding: var(--spacing-4);
167
+ }
168
+
169
+ .structure-view__detail {
170
+ display: flex;
171
+ flex: 1;
172
+ flex-direction: column;
173
+ min-height: 0;
174
+ min-width: 0;
175
+ }
176
+
177
+ .structure-view__header {
178
+ border-bottom: 1px solid rgba(75, 71, 50, 0.18);
179
+ padding: 1.25rem 1.5rem;
180
+ }
181
+
182
+ .structure-headline-container {
183
+ max-width: min(64rem, 100%);
184
+ }
185
+
186
+ .structure-view__graph-shell {
187
+ flex: 1;
188
+ min-height: 0;
189
+ }
190
+
191
+ .structure-sidebar__section + .structure-sidebar__section {
192
+ margin-top: 1.5rem;
193
+ }
194
+
195
+ .charts-view__detail {
196
+ display: flex;
197
+ flex: 1;
198
+ flex-direction: column;
199
+ min-height: 0;
200
+ min-width: 0;
201
+ }
202
+
203
+ .charts-detail-shell {
204
+ min-height: 100%;
205
+ padding: var(--spacing-6);
206
+ }
207
+
208
+ .charts-detail-shell__header {
209
+ align-items: end;
210
+ display: flex;
211
+ flex-wrap: wrap;
212
+ gap: var(--spacing-4);
213
+ justify-content: space-between;
214
+ }
215
+
216
+ .charts-detail-shell__controls {
217
+ align-items: end;
218
+ display: flex;
219
+ flex-wrap: wrap;
220
+ gap: var(--spacing-3);
221
+ }
222
+
223
+ .charts-detail-shell__button {
224
+ background: var(--color-surface-container);
225
+ border: 1px solid rgba(75, 71, 50, 0.2);
226
+ color: var(--color-on-surface);
227
+ font-family: var(--font-family-mono);
228
+ font-size: 0.625rem;
229
+ font-weight: 700;
230
+ letter-spacing: 0.16em;
231
+ padding: 0.75rem 1rem;
232
+ }
233
+
234
+ .charts-detail-shell__button--primary {
235
+ background: var(--color-primary-container);
236
+ border-color: transparent;
237
+ color: var(--color-on-primary);
238
+ }
239
+
240
+ .charts-detail-shell__button[disabled] {
241
+ cursor: not-allowed;
242
+ opacity: 0.5;
243
+ }
244
+
245
+ .charts-height-toggle {
246
+ background: var(--color-surface-container-low);
247
+ border: 1px solid rgba(75, 71, 50, 0.18);
248
+ display: inline-flex;
249
+ gap: 0.25rem;
250
+ padding: 0.25rem;
251
+ }
252
+
253
+ .charts-height-toggle__button {
254
+ background: transparent;
255
+ border: 1px solid transparent;
256
+ color: var(--color-on-surface-variant);
257
+ font-family: var(--font-family-mono);
258
+ font-size: 0.625rem;
259
+ font-weight: 700;
260
+ letter-spacing: 0.16em;
261
+ padding: 0.55rem 0.8rem;
262
+ }
263
+
264
+ .charts-height-toggle__button.is-active {
265
+ background: var(--color-primary-container);
266
+ border-color: transparent;
267
+ color: var(--color-on-primary);
268
+ }
269
+
270
+ .query-chart-card {
271
+ --query-chart-height: 450px;
272
+ background: var(--color-surface-low);
273
+ border: 1px solid rgba(75, 71, 50, 0.12);
274
+ overflow: hidden;
275
+ }
276
+
277
+ .query-chart-card--small {
278
+ --query-chart-height: 300px;
279
+ }
280
+
281
+ .query-chart-card--medium {
282
+ --query-chart-height: 450px;
283
+ }
284
+
285
+ .query-chart-card--large {
286
+ --query-chart-height: 600px;
287
+ }
288
+
289
+ .query-chart-card__header {
290
+ align-items: center;
291
+ background: var(--color-surface-container-highest);
292
+ display: flex;
293
+ flex-wrap: wrap;
294
+ gap: var(--spacing-4);
295
+ justify-content: space-between;
296
+ padding: 1rem 1.25rem;
297
+ }
298
+
299
+ .query-chart-card__body {
300
+ background:
301
+ radial-gradient(circle at top right, rgba(252, 227, 0, 0.08), transparent 28%),
302
+ linear-gradient(180deg, rgba(32, 31, 31, 0.92), rgba(14, 14, 14, 1));
303
+ min-height: var(--query-chart-height);
304
+ padding: 1rem;
305
+ }
306
+
307
+ .query-chart-canvas,
308
+ .query-chart-surface-state {
309
+ height: var(--query-chart-height);
310
+ min-height: var(--query-chart-height);
311
+ }
312
+
313
+ .query-chart-card__action {
314
+ background: var(--color-surface-container);
315
+ border: 1px solid rgba(75, 71, 50, 0.18);
316
+ color: var(--color-on-surface);
317
+ font-family: var(--font-family-mono);
318
+ font-size: 0.625rem;
319
+ font-weight: 700;
320
+ letter-spacing: 0.14em;
321
+ padding: 0.55rem 0.85rem;
322
+ }
323
+
324
+ .query-chart-card__results {
325
+ background: var(--color-surface-container-lowest);
326
+ }
327
+
328
+ @media (max-width: 1023px) {
329
+ .charts-view {
330
+ flex-direction: column;
331
+ }
332
+
333
+ .charts-view__sidebar {
334
+ flex-basis: 18rem;
335
+ width: 100%;
336
+ }
337
+
338
+ .structure-view {
339
+ flex-direction: column;
340
+ }
341
+
342
+ .structure-view__sidebar {
343
+ flex-basis: 18rem;
344
+ width: 100%;
345
+ }
346
+
347
+ .table-designer-view {
348
+ flex-direction: column;
349
+ }
350
+
351
+ .table-designer-workspace__bottom {
352
+ flex-basis: 280px;
353
+ }
84
354
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sqlite-hub",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "SQLite-only local management app backend and SPA shell",
5
5
  "main": "server/server.js",
6
6
  "bin": {
@@ -15,6 +15,7 @@
15
15
  "better-sqlite3": "^11.8.1",
16
16
  "cytoscape": "^3.33.2",
17
17
  "cytoscape-elk": "^2.3.0",
18
+ "echarts": "^5.6.0",
18
19
  "elkjs": "^0.11.1",
19
20
  "express": "^4.21.2"
20
21
  }
@@ -0,0 +1,153 @@
1
+ const express = require("express");
2
+ const {
3
+ DatabaseRequiredError,
4
+ route,
5
+ successResponse,
6
+ } = require("../utils/errors");
7
+
8
+ function getActiveDatabaseKey(connectionManager) {
9
+ return connectionManager.getActiveConnection()?.id ?? null;
10
+ }
11
+
12
+ function requireActiveDatabaseKey(connectionManager) {
13
+ const databaseKey = getActiveDatabaseKey(connectionManager);
14
+
15
+ if (!databaseKey) {
16
+ throw new DatabaseRequiredError();
17
+ }
18
+
19
+ return databaseKey;
20
+ }
21
+
22
+ function createChartsRouter({ appStateStore, connectionManager, sqlExecutor }) {
23
+ const router = express.Router();
24
+
25
+ router.get(
26
+ "/query-history",
27
+ route((req, res) => {
28
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
29
+
30
+ res.json(
31
+ successResponse({
32
+ data: appStateStore.getChartQueryHistoryList(databaseKey),
33
+ metadata: { databaseKey },
34
+ })
35
+ );
36
+ })
37
+ );
38
+
39
+ router.get(
40
+ "/query-history/:historyId",
41
+ route((req, res) => {
42
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
43
+
44
+ res.json(
45
+ successResponse({
46
+ data: appStateStore.getQueryHistoryChartsDetail(req.params.historyId, databaseKey),
47
+ metadata: {
48
+ databaseKey,
49
+ historyId: Number(req.params.historyId),
50
+ },
51
+ })
52
+ );
53
+ })
54
+ );
55
+
56
+ router.post(
57
+ "/query-history/:historyId/execute",
58
+ route((req, res) => {
59
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
60
+ const item = appStateStore.getQueryHistoryItemForDatabase(req.params.historyId, databaseKey);
61
+ const result = sqlExecutor.execute(item.rawSql, {
62
+ persistHistory: false,
63
+ requireReader: true,
64
+ });
65
+
66
+ res.json(
67
+ successResponse({
68
+ message: "Query results loaded for charts.",
69
+ data: {
70
+ ...result,
71
+ queryHistoryId: item.id,
72
+ },
73
+ metadata: {
74
+ databaseKey,
75
+ historyId: item.id,
76
+ },
77
+ timingMs: result.timingMs,
78
+ })
79
+ );
80
+ })
81
+ );
82
+
83
+ router.post(
84
+ "/",
85
+ route((req, res) => {
86
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
87
+ const chart = appStateStore.createQueryHistoryChart({
88
+ databaseKey,
89
+ queryHistoryId: req.body?.queryHistoryId,
90
+ name: req.body?.name,
91
+ chartType: req.body?.chartType,
92
+ config: req.body?.config,
93
+ resultColumns: req.body?.resultColumns,
94
+ tableVisible: req.body?.tableVisible,
95
+ });
96
+
97
+ res.json(
98
+ successResponse({
99
+ message: "Chart created.",
100
+ data: chart,
101
+ metadata: { databaseKey },
102
+ })
103
+ );
104
+ })
105
+ );
106
+
107
+ router.patch(
108
+ "/:chartId",
109
+ route((req, res) => {
110
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
111
+ const chart = appStateStore.updateQueryHistoryChart(req.params.chartId, {
112
+ databaseKey,
113
+ name: req.body?.name,
114
+ chartType: req.body?.chartType,
115
+ config: req.body?.config,
116
+ resultColumns: req.body?.resultColumns,
117
+ tableVisible: req.body?.tableVisible,
118
+ });
119
+
120
+ res.json(
121
+ successResponse({
122
+ message: "Chart updated.",
123
+ data: chart,
124
+ metadata: { databaseKey },
125
+ })
126
+ );
127
+ })
128
+ );
129
+
130
+ router.delete(
131
+ "/:chartId",
132
+ route((req, res) => {
133
+ const databaseKey = requireActiveDatabaseKey(connectionManager);
134
+ appStateStore.deleteQueryHistoryChart(req.params.chartId, databaseKey);
135
+
136
+ res.json(
137
+ successResponse({
138
+ message: "Chart deleted.",
139
+ data: {
140
+ id: Number(req.params.chartId),
141
+ },
142
+ metadata: { databaseKey },
143
+ })
144
+ );
145
+ })
146
+ );
147
+
148
+ return router;
149
+ }
150
+
151
+ module.exports = {
152
+ createChartsRouter,
153
+ };
@@ -0,0 +1,60 @@
1
+ const express = require("express");
2
+ const { route, successResponse } = require("../utils/errors");
3
+
4
+ function createTableDesignerRouter({ tableDesignerService }) {
5
+ const router = express.Router();
6
+
7
+ router.get(
8
+ "/",
9
+ route((req, res) => {
10
+ const data = tableDesignerService.getOverview();
11
+
12
+ res.json(
13
+ successResponse({
14
+ data,
15
+ readOnly: false,
16
+ })
17
+ );
18
+ })
19
+ );
20
+
21
+ router.get(
22
+ "/:tableName",
23
+ route((req, res) => {
24
+ const data = tableDesignerService.getTableDraft(req.params.tableName);
25
+
26
+ res.json(
27
+ successResponse({
28
+ data,
29
+ readOnly: false,
30
+ })
31
+ );
32
+ })
33
+ );
34
+
35
+ router.post(
36
+ "/save",
37
+ route((req, res) => {
38
+ const data = tableDesignerService.saveDraft(req.body ?? {});
39
+ const isCreate = String(req.body?.draft?.mode ?? req.body?.mode ?? "").trim() !== "edit";
40
+ const fillsImportedRows = Boolean(req.body?.draft?.fillImportedRows ?? req.body?.fillImportedRows);
41
+
42
+ res.json(
43
+ successResponse({
44
+ message: isCreate
45
+ ? fillsImportedRows
46
+ ? "Table created and filled from CSV."
47
+ : "Table created."
48
+ : "Table schema updated.",
49
+ data,
50
+ })
51
+ );
52
+ })
53
+ );
54
+
55
+ return router;
56
+ }
57
+
58
+ module.exports = {
59
+ createTableDesignerRouter,
60
+ };
package/server/server.js CHANGED
@@ -11,11 +11,14 @@ const { BackupService } = require("./services/sqlite/backupService");
11
11
  const { ExportService } = require("./services/sqlite/exportService");
12
12
  const { StructureService } = require("./services/sqlite/structureService");
13
13
  const { DataBrowserService } = require("./services/sqlite/dataBrowserService");
14
+ const { TableDesignerService } = require("./services/sqlite/tableDesignerService");
14
15
  const { createConnectionsRouter } = require("./routes/connections");
15
16
  const { createOverviewRouter } = require("./routes/overview");
16
17
  const { createSqlRouter } = require("./routes/sql");
18
+ const { createChartsRouter } = require("./routes/charts");
17
19
  const { createStructureRouter } = require("./routes/structure");
18
20
  const { createDataRouter } = require("./routes/data");
21
+ const { createTableDesignerRouter } = require("./routes/tableDesigner");
19
22
  const { createSettingsRouter } = require("./routes/settings");
20
23
  const { createExportRouter } = require("./routes/export");
21
24
 
@@ -46,6 +49,7 @@ const exportService = new ExportService({
46
49
  });
47
50
  const structureService = new StructureService({ connectionManager, appStateStore });
48
51
  const dataBrowserService = new DataBrowserService({ connectionManager });
52
+ const tableDesignerService = new TableDesignerService({ connectionManager });
49
53
 
50
54
  connectionManager.initialize();
51
55
 
@@ -76,8 +80,10 @@ app.use(
76
80
  );
77
81
  app.use("/api/db", createOverviewRouter({ overviewService }));
78
82
  app.use("/api/sql", createSqlRouter({ appStateStore, connectionManager, sqlExecutor }));
83
+ app.use("/api/charts", createChartsRouter({ appStateStore, connectionManager, sqlExecutor }));
79
84
  app.use("/api/structure", createStructureRouter({ structureService }));
80
85
  app.use("/api/data", createDataRouter({ dataBrowserService }));
86
+ app.use("/api/table-designer", createTableDesignerRouter({ tableDesignerService }));
81
87
  app.use("/api/settings", createSettingsRouter({ appStateStore }));
82
88
  app.use("/api/export", createExportRouter({ exportService }));
83
89
 
@@ -105,6 +111,10 @@ app.use(
105
111
  "/vendor/elkjs",
106
112
  express.static(path.resolve(__dirname, "..", "node_modules", "elkjs"))
107
113
  );
114
+ app.use(
115
+ "/vendor/echarts",
116
+ express.static(path.resolve(__dirname, "..", "node_modules", "echarts"))
117
+ );
108
118
  app.use(express.static(FRONTEND_ROOT));
109
119
  app.use("/db_logos", express.static(path.join(APP_STATE_DIRECTORY, "db_logos")));
110
120
  app.use(errorMiddleware);