@adminforth/dashboard 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +23 -4
  2. package/custom/api/dashboardApi.ts +6 -9
  3. package/custom/model/dashboard.types.ts +60 -275
  4. package/custom/model/dashboardTopics.ts +5 -0
  5. package/custom/package.json +1 -0
  6. package/custom/runtime/DashboardGroup.vue +2 -2
  7. package/custom/runtime/DashboardPage.vue +17 -7
  8. package/custom/runtime/DashboardRuntime.vue +20 -8
  9. package/custom/runtime/WidgetRenderer.vue +1 -2
  10. package/custom/runtime/WidgetShell.vue +3 -3
  11. package/custom/skills/adminforth-dashboard/SKILL.md +2 -2
  12. package/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  13. package/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  14. package/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  15. package/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  16. package/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  17. package/custom/widgets/chart/ChartWidget.vue +4 -15
  18. package/{dist/custom/widgets/chart/funnel → custom/widgets/chart}/FunnelChart.vue +80 -78
  19. package/{dist/custom/widgets/chart/line → custom/widgets/chart}/LineChart.vue +2 -2
  20. package/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  21. package/{dist/custom/widgets/chart/stacked-bar → custom/widgets/chart}/StackedBarChart.vue +97 -95
  22. package/custom/widgets/chart/chart.types.ts +0 -28
  23. package/dist/custom/api/dashboardApi.d.ts +4 -8
  24. package/dist/custom/api/dashboardApi.js +2 -6
  25. package/dist/custom/api/dashboardApi.ts +6 -9
  26. package/dist/custom/composables/useElementSize.js +7 -10
  27. package/dist/custom/model/dashboard.types.d.ts +38 -32
  28. package/dist/custom/model/dashboard.types.js +4 -161
  29. package/dist/custom/model/dashboard.types.ts +60 -275
  30. package/dist/custom/model/dashboardTopics.d.ts +2 -0
  31. package/dist/custom/model/dashboardTopics.js +4 -0
  32. package/dist/custom/model/dashboardTopics.ts +5 -0
  33. package/dist/custom/package.json +1 -0
  34. package/dist/custom/queries/useDashboardConfig.d.ts +96 -96
  35. package/dist/custom/queries/useDashboardConfig.js +9 -12
  36. package/dist/custom/queries/useWidgetData.d.ts +96 -96
  37. package/dist/custom/queries/useWidgetData.js +9 -12
  38. package/dist/custom/runtime/DashboardGroup.vue +2 -2
  39. package/dist/custom/runtime/DashboardPage.vue +17 -7
  40. package/dist/custom/runtime/DashboardRuntime.vue +20 -8
  41. package/dist/custom/runtime/WidgetRenderer.vue +1 -2
  42. package/dist/custom/runtime/WidgetShell.vue +3 -3
  43. package/dist/custom/skills/adminforth-dashboard/SKILL.md +2 -2
  44. package/dist/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  45. package/dist/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  46. package/dist/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  47. package/dist/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  48. package/dist/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  49. package/dist/custom/widgets/chart/ChartWidget.vue +4 -15
  50. package/{custom/widgets/chart/funnel → dist/custom/widgets/chart}/FunnelChart.vue +80 -78
  51. package/{custom/widgets/chart/line → dist/custom/widgets/chart}/LineChart.vue +2 -2
  52. package/dist/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  53. package/{custom/widgets/chart/stacked-bar → dist/custom/widgets/chart}/StackedBarChart.vue +97 -95
  54. package/dist/custom/widgets/chart/chart.types.d.ts +0 -2
  55. package/dist/custom/widgets/chart/chart.types.js +1 -25
  56. package/dist/custom/widgets/chart/chart.types.ts +0 -28
  57. package/dist/custom/widgets/chart/chart.utils.js +6 -14
  58. package/dist/custom/widgets/registry.js +14 -22
  59. package/dist/endpoint/dashboard.d.ts +2 -3
  60. package/dist/endpoint/dashboard.js +12 -32
  61. package/dist/endpoint/groups.d.ts +2 -21
  62. package/dist/endpoint/groups.js +18 -16
  63. package/dist/endpoint/widgets.d.ts +0 -3
  64. package/dist/endpoint/widgets.js +27 -74
  65. package/dist/index.js +1 -3
  66. package/dist/schema/api.d.ts +2090 -511
  67. package/dist/schema/api.js +18 -15
  68. package/dist/schema/widget.d.ts +1003 -250
  69. package/dist/schema/widget.js +102 -46
  70. package/dist/services/dashboardConfigService.d.ts +0 -10
  71. package/dist/services/dashboardConfigService.js +6 -21
  72. package/dist/services/widgetDataService.js +226 -196
  73. package/endpoint/dashboard.ts +13 -46
  74. package/endpoint/groups.ts +25 -42
  75. package/endpoint/widgets.ts +36 -95
  76. package/index.ts +0 -3
  77. package/package.json +3 -3
  78. package/schema/api.ts +19 -15
  79. package/schema/widget.ts +113 -52
  80. package/services/dashboardConfigService.ts +6 -25
  81. package/services/widgetDataService.ts +304 -229
  82. package/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  83. package/dist/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  84. package/dist/services/widgetConfigValidator.d.ts +0 -8
  85. package/dist/services/widgetConfigValidator.js +0 -27
  86. package/services/widgetConfigValidator.ts +0 -61
package/README.md CHANGED
@@ -61,16 +61,35 @@ Chart widget types:
61
61
  type QueryConfig = {
62
62
  resource: string
63
63
  select?: Array<
64
- | { field: string; as?: string; grain?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' }
65
- | { agg: 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'; field?: string; as: string; filters?: unknown }
64
+ | { field: string; as?: string; grain?: 'day' | 'week' | 'month' | 'year' }
65
+ | { agg: 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'; field?: string; as: string; filters?: DashboardFilter | DashboardFilter[] }
66
66
  | { calc: string; as: string }
67
67
  >
68
- filters?: unknown
69
- group_by?: Array<string | { field: string; as?: string; grain?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; timezone?: string }>
68
+ filters?: DashboardFilter | DashboardFilter[]
69
+ group_by?: Array<string | { field: string; as?: string; grain?: 'day' | 'week' | 'month' | 'year'; timezone?: string }>
70
70
  order_by?: Array<{ field: string; direction?: 'asc' | 'desc' }>
71
71
  limit?: number
72
72
  offset?: number
73
73
  }
74
+
75
+ type DashboardFilter =
76
+ | { and: DashboardFilter[] }
77
+ | { or: DashboardFilter[] }
78
+ | {
79
+ field: string
80
+ eq?: JsonValue
81
+ neq?: JsonValue
82
+ gt?: JsonValue
83
+ gte?: JsonValue
84
+ lt?: JsonValue
85
+ lte?: JsonValue
86
+ in?: JsonValue[]
87
+ not_in?: JsonValue[]
88
+ like?: JsonValue
89
+ ilike?: JsonValue
90
+ }
91
+
92
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
74
93
  ```
75
94
 
76
95
  Step-based chart queries use `steps` and may include `calcs`:
@@ -1,16 +1,13 @@
1
1
  import type {
2
2
  DashboardConfig,
3
- DashboardGroupConfig,
3
+ EditableDashboardGroupConfig,
4
+ EditableDashboardWidgetConfig,
4
5
  DashboardGroupMoveDirection,
5
6
  DashboardWidgetConfig,
7
+ DashboardWidgetConfigValidationError,
6
8
  DashboardWidgetMoveDirection,
7
9
  } from '../model/dashboard.types.js'
8
10
 
9
- export type DashboardWidgetConfigValidationError = {
10
- field: string
11
- message: string
12
- }
13
-
14
11
  export type DashboardResponse = {
15
12
  id: string
16
13
  slug: string
@@ -143,7 +140,7 @@ export const dashboardApi = {
143
140
  return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
144
141
  },
145
142
 
146
- async setDashboardConfig(slug: string, config: DashboardConfig): Promise<DashboardResponse> {
143
+ async setDashboardConfig(slug: string, config: unknown): Promise<DashboardResponse> {
147
144
  return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
148
145
  },
149
146
 
@@ -170,7 +167,7 @@ export const dashboardApi = {
170
167
  })
171
168
  },
172
169
 
173
- async setDashboardGroupConfig(slug: string, groupId: string, config: DashboardGroupConfig): Promise<DashboardResponse> {
170
+ async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<DashboardResponse> {
174
171
  return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
175
172
  slug,
176
173
  groupId,
@@ -204,7 +201,7 @@ export const dashboardApi = {
204
201
  })
205
202
  },
206
203
 
207
- async setWidgetConfig(slug: string, widgetId: string, config: DashboardWidgetConfig): Promise<DashboardResponse> {
204
+ async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<DashboardResponse> {
208
205
  return callDashboardApi('/adminapi/v1/dashboard/set_widget_config', {
209
206
  slug,
210
207
  widgetId,
@@ -6,7 +6,15 @@ export type DashboardConfig = {
6
6
  widgets: DashboardWidgetConfig[]
7
7
  }
8
8
 
9
- export type DashboardVariables = Record<string, unknown>
9
+ export type JsonValue =
10
+ | string
11
+ | number
12
+ | boolean
13
+ | null
14
+ | JsonValue[]
15
+ | { [key: string]: JsonValue }
16
+
17
+ export type DashboardVariables = Record<string, JsonValue>
10
18
 
11
19
  export type DashboardGroupConfig = {
12
20
  id: string
@@ -14,12 +22,18 @@ export type DashboardGroupConfig = {
14
22
  order: number
15
23
  }
16
24
 
25
+ export type EditableDashboardGroupConfig = Pick<DashboardGroupConfig, 'label'>
26
+
17
27
  export type DashboardGroupMoveDirection = 'up' | 'down'
18
28
  export type DashboardWidgetMoveDirection = 'up' | 'down'
19
29
  export type DashboardWidgetTarget = 'empty' | 'table' | 'chart' | 'kpi_card' | 'pivot_table' | 'gauge_card'
20
30
  export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full'
31
+ export type DashboardWidgetConfigValidationError = {
32
+ field: string
33
+ message: string
34
+ }
21
35
  export type QueryAggregateOperation = 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'
22
- export type TimeGrain = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
36
+ export type TimeGrain = 'day' | 'week' | 'month' | 'year'
23
37
  export type ValueFormat =
24
38
  | 'number'
25
39
  | 'compact_number'
@@ -32,8 +46,8 @@ export type ValueFormat =
32
46
  export type WidgetLayout = {
33
47
  size?: DashboardWidgetSize
34
48
  width?: number
35
- minWidth?: number
36
- maxWidth?: number | null
49
+ min_width?: number
50
+ max_width?: number | null
37
51
  height?: number
38
52
  }
39
53
 
@@ -45,8 +59,8 @@ export type WidgetBaseConfig = {
45
59
  size?: DashboardWidgetSize
46
60
  width?: number
47
61
  height?: number
48
- minWidth?: number
49
- maxWidth?: number | null
62
+ min_width?: number
63
+ max_width?: number | null
50
64
  order: number
51
65
  }
52
66
 
@@ -56,16 +70,16 @@ export type FilterExpression =
56
70
  | Array<FilterExpression>
57
71
  | {
58
72
  field: string
59
- eq?: unknown
60
- neq?: unknown
61
- gt?: unknown
62
- gte?: unknown
63
- lt?: unknown
64
- lte?: unknown
65
- in?: unknown[]
66
- not_in?: unknown[]
67
- like?: unknown
68
- ilike?: unknown
73
+ eq?: JsonValue
74
+ neq?: JsonValue
75
+ gt?: JsonValue
76
+ gte?: JsonValue
77
+ lt?: JsonValue
78
+ lte?: JsonValue
79
+ in?: JsonValue[]
80
+ not_in?: JsonValue[]
81
+ like?: JsonValue
82
+ ilike?: JsonValue
69
83
  }
70
84
 
71
85
  export type QueryFieldSelectItem = {
@@ -106,26 +120,26 @@ export type QueryConfig = {
106
120
  resource: string
107
121
  select?: QuerySelectItem[]
108
122
  filters?: FilterExpression
109
- groupBy?: QueryGroupByItem[]
110
- orderBy?: QueryOrderByItem[]
123
+ group_by?: QueryGroupByItem[]
124
+ order_by?: QueryOrderByItem[]
111
125
  limit?: number
112
126
  offset?: number
113
- timeSeries?: {
127
+ time_series?: {
114
128
  field: string
115
129
  grain: TimeGrain
116
130
  timezone?: string
117
131
  }
118
132
  period?: {
119
133
  field: string
120
- gte?: unknown
121
- lt?: unknown
134
+ gte?: JsonValue
135
+ lt?: JsonValue
122
136
  }
123
137
  bucket?: {
124
138
  field: string
125
139
  buckets: Array<{ label: string, min?: number, max?: number }>
126
140
  }
127
141
  calcs?: QueryCalcSelectItem[]
128
- formatting?: Record<string, unknown>
142
+ formatting?: Record<string, JsonValue>
129
143
  }
130
144
 
131
145
  export type FunnelQueryConfig = {
@@ -149,7 +163,7 @@ export type FieldRef = string | {
149
163
  export type TableViewConfig = {
150
164
  columns?: FieldRef[]
151
165
  pagination?: boolean
152
- pageSize?: number
166
+ page_size?: number
153
167
  }
154
168
 
155
169
  export type KpiCardViewConfig = {
@@ -164,8 +178,8 @@ export type KpiCardViewConfig = {
164
178
  text?: string
165
179
  field?: string
166
180
  }
167
- comparison?: unknown
168
- sparkline?: unknown
181
+ comparison?: JsonValue
182
+ sparkline?: JsonValue
169
183
  }
170
184
 
171
185
  export type GaugeCardViewConfig = {
@@ -182,9 +196,9 @@ export type GaugeCardViewConfig = {
182
196
  label?: string
183
197
  }
184
198
  progress?: {
185
- valueField: string
186
- targetValue?: number
187
- targetField?: string
199
+ value_field: string
200
+ target_value?: number
201
+ target_field?: string
188
202
  format?: ValueFormat
189
203
  }
190
204
  color?: string
@@ -243,6 +257,14 @@ export type DashboardWidgetConfig =
243
257
  | GaugeCardWidgetConfig
244
258
  | PivotTableWidgetConfig
245
259
 
260
+ export type EditableDashboardWidgetConfig =
261
+ | Omit<EmptyWidgetConfig, 'id' | 'group_id' | 'order'>
262
+ | Omit<TableWidgetConfig, 'id' | 'group_id' | 'order'>
263
+ | Omit<ChartDashboardWidgetConfig, 'id' | 'group_id' | 'order'>
264
+ | Omit<KpiCardWidgetConfig, 'id' | 'group_id' | 'order'>
265
+ | Omit<GaugeCardWidgetConfig, 'id' | 'group_id' | 'order'>
266
+ | Omit<PivotTableWidgetConfig, 'id' | 'group_id' | 'order'>
267
+
246
268
  export type DashboardWidgetTableData = {
247
269
  kind?: 'table'
248
270
  columns: string[]
@@ -270,256 +292,19 @@ export type DashboardWidgetAggregateData = {
270
292
 
271
293
  export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData
272
294
 
273
- export function normalizeDashboardConfig(config: unknown): DashboardConfig {
274
- const value = isRecord(config) ? config : {}
275
-
276
- return {
277
- version: typeof value.version === 'number' ? value.version : 1,
278
- groups: Array.isArray(value.groups) ? (value.groups as DashboardGroupConfig[]) : [],
279
- widgets: Array.isArray(value.widgets)
280
- ? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget) as DashboardWidgetConfig)
281
- : [],
282
- }
283
- }
284
-
285
- export function normalizeDashboardWidgetConfig(config: unknown) {
286
- if (!isRecord(config)) {
287
- return config
288
- }
289
-
290
- const normalized: Record<string, unknown> = { ...config }
291
- normalizeWidgetLayoutConfig(normalized)
292
-
293
- if (normalized.query !== undefined) {
294
- normalized.query = normalizeQueryConfig(normalized.query)
295
- }
296
-
297
- if (normalized.table !== undefined) {
298
- normalized.table = normalizeTableConfig(normalized.table)
299
- }
300
-
301
- if (normalized.card !== undefined) {
302
- normalized.card = normalizeCardConfig(normalized.card)
303
- }
304
-
305
- if (normalized.pivot !== undefined) {
306
- normalized.pivot = normalizePivotConfig(normalized.pivot)
307
- }
308
-
309
- const target = normalizeDashboardWidgetTarget(normalized.target)
310
-
311
- if (target !== undefined) {
312
- normalized.target = target
313
- }
314
-
315
- return normalized
316
- }
317
-
318
- export function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig) {
319
- const serialized: Record<string, unknown> = { ...widget }
320
-
321
- if (Object.prototype.hasOwnProperty.call(serialized, 'minWidth')) {
322
- serialized.min_width = widget.minWidth
323
- delete serialized.minWidth
324
- }
325
-
326
- if (Object.prototype.hasOwnProperty.call(serialized, 'maxWidth')) {
327
- serialized.max_width = widget.maxWidth
328
- delete serialized.maxWidth
329
- }
330
-
331
- if ('query' in widget) {
332
- serialized.query = serializeQueryConfigForEditor(widget.query)
333
- }
334
-
335
- if ('table' in widget && widget.table !== undefined) {
336
- serialized.table = serializeTableConfigForEditor(widget.table)
337
- }
338
-
339
- if ('card' in widget && widget.card !== undefined) {
340
- serialized.card = serializeCardConfigForEditor(widget.card)
341
- }
295
+ export function serializeDashboardWidgetConfigForEditor(
296
+ widget: DashboardWidgetConfig,
297
+ ): unknown {
298
+ const {
299
+ id: _id,
300
+ group_id: _groupId,
301
+ order: _order,
302
+ ...editableWidget
303
+ } = widget
342
304
 
343
- if ('pivot' in widget && widget.pivot !== undefined) {
344
- serialized.pivot = serializePivotConfigForEditor(widget.pivot)
345
- }
346
-
347
- return serialized
305
+ return editableWidget
348
306
  }
349
307
 
350
308
  export function getFieldRefField(value: FieldRef | undefined) {
351
309
  return typeof value === 'string' ? value : value?.field
352
310
  }
353
-
354
- export function getFieldRefLabel(value: FieldRef | undefined) {
355
- return typeof value === 'string' ? value : value?.label
356
- }
357
-
358
- function normalizeDashboardWidgetTarget(value: unknown): DashboardWidgetTarget | undefined {
359
- switch (value) {
360
- case 'empty':
361
- case 'table':
362
- case 'chart':
363
- case 'kpi_card':
364
- case 'pivot_table':
365
- case 'gauge_card':
366
- return value
367
- default:
368
- return undefined
369
- }
370
- }
371
-
372
- function normalizeWidgetLayoutConfig(value: Record<string, unknown>) {
373
- if (value.min_width !== undefined) {
374
- value.minWidth = value.min_width
375
- }
376
-
377
- if (value.max_width !== undefined) {
378
- value.maxWidth = value.max_width
379
- }
380
- }
381
-
382
- function normalizeQueryConfig(value: unknown): unknown {
383
- if (!isRecord(value)) {
384
- return value
385
- }
386
-
387
- if (Array.isArray(value.steps)) {
388
- return removeUndefinedFields({
389
- steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
390
- calcs: Array.isArray(value.calcs) ? value.calcs as QueryCalcSelectItem[] : undefined,
391
- })
392
- }
393
-
394
- return {
395
- ...value,
396
- ...(Array.isArray(value.group_by) ? { groupBy: value.group_by } : {}),
397
- ...(Array.isArray(value.order_by) ? { orderBy: value.order_by } : {}),
398
- ...(value.time_series !== undefined ? { timeSeries: value.time_series } : {}),
399
- }
400
- }
401
-
402
- function normalizeFunnelQueryStep(value: unknown) {
403
- if (!isRecord(value)) {
404
- return value
405
- }
406
-
407
- const { resource_id, ...rest } = value
408
-
409
- return removeUndefinedFields({
410
- ...rest,
411
- resource: typeof resource_id === 'string' ? resource_id : rest.resource,
412
- })
413
- }
414
-
415
- function normalizeTableConfig(value: unknown) {
416
- if (!isRecord(value)) {
417
- return value
418
- }
419
-
420
- return {
421
- ...value,
422
- ...(value.page_size !== undefined ? { pageSize: value.page_size } : {}),
423
- }
424
- }
425
-
426
- function normalizeCardConfig(value: unknown): unknown {
427
- if (!isRecord(value)) {
428
- return value
429
- }
430
-
431
- const normalized = { ...value }
432
-
433
- if (isRecord(normalized.progress)) {
434
- normalized.progress = {
435
- ...normalized.progress,
436
- ...(normalized.progress.value_field !== undefined ? { valueField: normalized.progress.value_field } : {}),
437
- ...(normalized.progress.target_value !== undefined ? { targetValue: normalized.progress.target_value } : {}),
438
- ...(normalized.progress.target_field !== undefined ? { targetField: normalized.progress.target_field } : {}),
439
- }
440
- }
441
-
442
- if (isRecord(normalized.comparison)) {
443
- normalized.comparison = {
444
- ...normalized.comparison,
445
- ...(normalized.comparison.positive_is_good !== undefined ? { positiveIsGood: normalized.comparison.positive_is_good } : {}),
446
- }
447
- }
448
-
449
- return normalized
450
- }
451
-
452
- function normalizePivotConfig(value: unknown): unknown {
453
- return value
454
- }
455
-
456
- function serializeQueryConfigForEditor(value: QueryConfig | FunnelQueryConfig) {
457
- if ('steps' in value) {
458
- return removeUndefinedFields({
459
- steps: value.steps.map((step) => ({
460
- ...step,
461
- resource_id: step.resource,
462
- resource: undefined,
463
- })).map((step) => removeUndefinedFields(step)),
464
- calcs: value.calcs,
465
- })
466
- }
467
-
468
- return removeUndefinedFields({
469
- ...value,
470
- group_by: value.groupBy,
471
- groupBy: undefined,
472
- order_by: value.orderBy,
473
- orderBy: undefined,
474
- time_series: value.timeSeries,
475
- timeSeries: undefined,
476
- })
477
- }
478
-
479
- function serializeTableConfigForEditor(value: TableViewConfig) {
480
- return removeUndefinedFields({
481
- ...value,
482
- page_size: value.pageSize,
483
- pageSize: undefined,
484
- })
485
- }
486
-
487
- function serializeCardConfigForEditor(value: KpiCardViewConfig | GaugeCardViewConfig) {
488
- const serialized: Record<string, unknown> = { ...value }
489
-
490
- if (isRecord(serialized.progress)) {
491
- serialized.progress = removeUndefinedFields({
492
- ...serialized.progress,
493
- value_field: serialized.progress.valueField,
494
- valueField: undefined,
495
- target_value: serialized.progress.targetValue,
496
- targetValue: undefined,
497
- target_field: serialized.progress.targetField,
498
- targetField: undefined,
499
- })
500
- }
501
-
502
- if (isRecord(serialized.comparison)) {
503
- serialized.comparison = removeUndefinedFields({
504
- ...serialized.comparison,
505
- positive_is_good: serialized.comparison.positiveIsGood,
506
- positiveIsGood: undefined,
507
- })
508
- }
509
-
510
- return removeUndefinedFields(serialized)
511
- }
512
-
513
- function serializePivotConfigForEditor(value: PivotTableViewConfig) {
514
- return value
515
- }
516
-
517
- function removeUndefinedFields<T extends Record<string, unknown>>(value: T) {
518
- return Object.fromEntries(
519
- Object.entries(value).filter(([, item]) => item !== undefined),
520
- )
521
- }
522
-
523
- function isRecord(value: unknown): value is Record<string, any> {
524
- return typeof value === 'object' && value !== null
525
- }
@@ -0,0 +1,5 @@
1
+ export const DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX = '/opentopic/dashboard-config-updated'
2
+
3
+ export function getDashboardConfigUpdatedTopic(slug: string) {
4
+ return `${DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX}/${slug}`
5
+ }
@@ -2,6 +2,7 @@
2
2
  "name": "custom",
3
3
  "version": "1.0.0",
4
4
  "main": "index.js",
5
+ "type": "module",
5
6
  "license": "MIT",
6
7
  "dependencies": {
7
8
  "yaml": "^2.9.0"
@@ -112,8 +112,8 @@
112
112
  :layout="{
113
113
  size: widget.size,
114
114
  width: widget.width,
115
- minWidth: widget.minWidth,
116
- maxWidth: widget.maxWidth,
115
+ min_width: widget.min_width,
116
+ max_width: widget.max_width,
117
117
  height: widget.height,
118
118
  }"
119
119
  @edit="emit('edit-widget', widget)"
@@ -50,20 +50,31 @@ import { useRoute } from 'vue-router'
50
50
  import { Button } from '@/afcl'
51
51
  import { useCoreStore } from '@/stores/core'
52
52
  import websocket from '@/websocket'
53
+ import { getDashboardConfigUpdatedTopic } from '../model/dashboardTopics.js'
53
54
  import DashboardRuntime from './DashboardRuntime.vue'
54
55
  import { useDashboardConfig } from '../queries/useDashboardConfig.js'
55
56
 
56
57
  const route = useRoute()
57
58
  const coreStore = useCoreStore()
58
59
 
59
- const dashboardSlug = computed(() => {
60
- const slug = route.params.slug
60
+ function getDashboardSlugFromRouteParam(value: string | string[] | undefined) {
61
+ if (Array.isArray(value)) {
62
+ if (!value[0]) {
63
+ throw new Error('Dashboard slug route param is required')
64
+ }
65
+
66
+ return value[0]
67
+ }
61
68
 
62
- if (Array.isArray(slug)) {
63
- return slug[0] || 'default'
69
+ if (!value) {
70
+ throw new Error('Dashboard slug route param is required')
64
71
  }
65
72
 
66
- return (slug as string) || 'default'
73
+ return value
74
+ }
75
+
76
+ const dashboardSlug = computed(() => {
77
+ return getDashboardSlugFromRouteParam(route.params.slug as string | string[] | undefined)
67
78
  })
68
79
 
69
80
  const {
@@ -78,11 +89,10 @@ const isAdmin = computed(() => {
78
89
  return coreStore.adminUser?.dbUser.role === 'superadmin'
79
90
  })
80
91
 
81
- const DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX = '/opentopic/dashboard-config-updated'
82
92
  const subscribedTopic = ref<string | null>(null)
83
93
 
84
94
  const dashboardConfigUpdatedTopic = computed(() => {
85
- return `${DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX}/${dashboardSlug.value}`
95
+ return getDashboardConfigUpdatedTopic(dashboardSlug.value)
86
96
  })
87
97
 
88
98
  function handleDashboardConfigUpdated(data: { slug?: string; revision?: number }) {
@@ -207,6 +207,8 @@ import { DashboardApiError, dashboardApi, type DashboardResponse } from '../api/
207
207
  import type {
208
208
  DashboardConfig,
209
209
  DashboardGroupConfig,
210
+ EditableDashboardGroupConfig,
211
+ EditableDashboardWidgetConfig,
210
212
  DashboardGroupMoveDirection,
211
213
  DashboardWidgetConfig,
212
214
  DashboardWidgetMoveDirection,
@@ -252,13 +254,15 @@ const sortedGroups = computed(() => {
252
254
  return [...draftConfig.value.groups].sort((a, b) => a.order - b.order)
253
255
  })
254
256
 
255
- const widgetsByGroupId = computed<Map<string, DashboardWidgetConfig[]>>(() => {
257
+ function groupWidgetsByGroupId(widgets: DashboardWidgetConfig[]) {
256
258
  const result = new Map<string, DashboardWidgetConfig[]>()
257
259
 
258
- for (const widget of draftConfig.value.widgets) {
259
- const widgets = result.get(widget.group_id) ?? []
260
- widgets.push(widget)
261
- result.set(widget.group_id, widgets)
260
+ for (const widget of widgets) {
261
+ const nextWidgets = result.get(widget.group_id)
262
+ ? [...result.get(widget.group_id)!, widget]
263
+ : [widget]
264
+
265
+ result.set(widget.group_id, nextWidgets)
262
266
  }
263
267
 
264
268
  for (const [groupId, widgets] of result.entries()) {
@@ -266,6 +270,10 @@ const widgetsByGroupId = computed<Map<string, DashboardWidgetConfig[]>>(() => {
266
270
  }
267
271
 
268
272
  return result
273
+ }
274
+
275
+ const widgetsByGroupId = computed<Map<string, DashboardWidgetConfig[]>>(() => {
276
+ return groupWidgetsByGroupId(draftConfig.value.widgets as DashboardWidgetConfig[])
269
277
  })
270
278
 
271
279
  const visibleGroups = computed(() => {
@@ -329,8 +337,12 @@ async function removeGroup(groupId: string) {
329
337
  }
330
338
 
331
339
  function editGroup(group: DashboardGroupConfig) {
340
+ const editableGroupConfig: EditableDashboardGroupConfig = {
341
+ label: group.label,
342
+ }
343
+
332
344
  editingGroupId.value = group.id
333
- groupConfigCode.value = stringifyYaml(group)
345
+ groupConfigCode.value = stringifyYaml(editableGroupConfig)
334
346
  groupConfigError.value = ''
335
347
  }
336
348
 
@@ -340,7 +352,7 @@ async function saveGroupConfig() {
340
352
  }
341
353
 
342
354
  try {
343
- const groupConfig = parseYaml(groupConfigCode.value) as DashboardGroupConfig
355
+ const groupConfig = parseYaml(groupConfigCode.value) as EditableDashboardGroupConfig
344
356
 
345
357
  applyDashboardResponse(
346
358
  await dashboardApi.setDashboardGroupConfig(
@@ -402,7 +414,7 @@ async function saveWidgetConfig() {
402
414
  try {
403
415
  widgetConfigError.value = ''
404
416
  widgetConfigFieldErrors.value = []
405
- const widgetConfig = parseYaml(widgetConfigCode.value) as DashboardWidgetConfig
417
+ const widgetConfig = parseYaml(widgetConfigCode.value) as EditableDashboardWidgetConfig
406
418
 
407
419
  applyDashboardResponse(
408
420
  await dashboardApi.setWidgetConfig(
@@ -26,7 +26,6 @@
26
26
  <script setup lang="ts">
27
27
  import { computed } from 'vue'
28
28
  import type { DashboardWidgetConfig } from '../model/dashboard.types.js'
29
- import { normalizeChartWidgetConfig } from '../widgets/chart/chart.types.js'
30
29
  import { getWidgetLabel, getWidgetRegistration } from '../widgets/registry.js'
31
30
 
32
31
  const props = defineProps<{
@@ -53,7 +52,7 @@ const widgetTitle = computed(() => {
53
52
  }
54
53
 
55
54
  if (props.widget.target === 'chart') {
56
- return normalizeChartWidgetConfig(props.widget.chart)?.title || 'Untitled chart'
55
+ return props.widget.chart.title || 'Untitled chart'
57
56
  }
58
57
 
59
58
  return getWidgetLabel(props.widget.target)