@adminforth/dashboard 1.2.0 → 1.4.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 (60) hide show
  1. package/README.md +116 -39
  2. package/custom/api/dashboardApi.ts +4 -0
  3. package/custom/composables/useElementSize.ts +17 -2
  4. package/custom/model/dashboard.types.ts +337 -236
  5. package/custom/skills/adminforth-dashboard/SKILL.md +113 -2
  6. package/custom/widgets/chart/ChartWidget.vue +38 -53
  7. package/custom/widgets/chart/bar/BarChart.vue +20 -12
  8. package/custom/widgets/chart/chart.types.ts +17 -66
  9. package/custom/widgets/chart/chart.utils.ts +11 -0
  10. package/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  11. package/custom/widgets/chart/line/LineChart.vue +23 -15
  12. package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  13. package/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
  14. package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  15. package/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
  16. package/custom/widgets/table/TableWidget.vue +8 -3
  17. package/dist/custom/api/dashboardApi.d.ts +1 -0
  18. package/dist/custom/api/dashboardApi.js +5 -0
  19. package/dist/custom/api/dashboardApi.ts +4 -0
  20. package/dist/custom/composables/useElementSize.js +14 -2
  21. package/dist/custom/composables/useElementSize.ts +17 -2
  22. package/dist/custom/model/dashboard.types.d.ts +181 -61
  23. package/dist/custom/model/dashboard.types.js +82 -93
  24. package/dist/custom/model/dashboard.types.ts +337 -236
  25. package/dist/custom/queries/useDashboardConfig.d.ts +852 -66
  26. package/dist/custom/queries/useWidgetData.d.ts +848 -62
  27. package/dist/custom/skills/adminforth-dashboard/SKILL.md +113 -2
  28. package/dist/custom/widgets/chart/ChartWidget.vue +38 -53
  29. package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
  30. package/dist/custom/widgets/chart/chart.types.d.ts +13 -22
  31. package/dist/custom/widgets/chart/chart.types.js +2 -25
  32. package/dist/custom/widgets/chart/chart.types.ts +17 -66
  33. package/dist/custom/widgets/chart/chart.utils.d.ts +1 -0
  34. package/dist/custom/widgets/chart/chart.utils.js +7 -0
  35. package/dist/custom/widgets/chart/chart.utils.ts +11 -0
  36. package/dist/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  37. package/dist/custom/widgets/chart/line/LineChart.vue +23 -15
  38. package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  39. package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
  40. package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  41. package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
  42. package/dist/custom/widgets/table/TableWidget.vue +8 -3
  43. package/dist/endpoint/dashboard.d.ts +7 -2
  44. package/dist/endpoint/dashboard.js +45 -1
  45. package/dist/endpoint/widgets.d.ts +2 -1
  46. package/dist/endpoint/widgets.js +6 -2
  47. package/dist/schema/api.d.ts +2773 -736
  48. package/dist/schema/api.js +5 -0
  49. package/dist/schema/widget.d.ts +1648 -476
  50. package/dist/schema/widget.js +208 -139
  51. package/dist/services/widgetConfigValidator.js +16 -40
  52. package/dist/services/widgetDataService.d.ts +2 -1
  53. package/dist/services/widgetDataService.js +389 -82
  54. package/endpoint/dashboard.ts +77 -4
  55. package/endpoint/widgets.ts +11 -4
  56. package/package.json +1 -1
  57. package/schema/api.ts +6 -0
  58. package/schema/widget.ts +225 -139
  59. package/services/widgetConfigValidator.ts +29 -53
  60. package/services/widgetDataService.ts +522 -100
package/README.md CHANGED
@@ -29,62 +29,139 @@ Each widget has common fields:
29
29
  | `label` | Optional widget title. |
30
30
  | `target` | Widget type: `table`, `chart`, `kpi_card`, `pivot_table`, or `gauge_card`. |
31
31
  | `order` | Widget order inside its group. |
32
+ | `variables` | Optional static maps/constants available inside widget `query.calcs` via `lookup($variables.path, field, default)`. |
32
33
  | `size` | Preset width: `small`, `medium`, `large`, `wide`, or `full`. |
33
34
  | `width`, `height`, `min_width`, `max_width` | Optional explicit layout constraints. |
34
- | `data_source` | Resource or aggregate data source definition. |
35
+ | `query` | Data query definition. |
35
36
 
36
37
  ## Widget Support Matrix
37
38
 
38
39
  | Widget target | Config field | Main settings | Data usage |
39
40
  | --- | --- | --- | --- |
40
- | `table` | `table` | `pagination`, `page_size` | Uses `data_source.type = 'resource'` to display resource rows with backend pagination unless `pagination` is `false`. |
41
- | `chart` | `chart` | `type`, `x_field`, `y_field`, `label_field`, `value_field`, `bucket_field`, `buckets`, `series`, `series_name`, `color`, `colors` | Uses `data_source.type = 'aggregate'` with `group_by`. |
42
- | `kpi_card` | `kpi_card` | `value_field`, `label_field`, `prefix`, `suffix` | Reads aggregate values or the first returned row from `data_source`. |
43
- | `gauge_card` | `gauge_card` | `value_field`, `min`, `max`, `min_field`, `max_field`, `suffix`, `color` | Reads aggregate values or the first returned row from `data_source` and renders progress between static or field-driven bounds. |
44
- | `pivot_table` | `pivot_table` | `row_field`, `column_field`, `value_field`, `aggregation` | Uses grouped aggregate rows from `data_source.type = 'aggregate'`. `aggregation` supports `count` and `sum`. |
41
+ | `table` | `table` | `pagination`, `page_size`, `columns` | Uses `query` to display raw or aggregate rows. |
42
+ | `chart` | `chart` | `type`, `x`, `y`, `label`, `value`, `series`, `buckets`, `color`, `colors` | Uses `query`; step-based charts may use `query.steps` with optional `calcs`. |
43
+ | `kpi_card` | `card` | `value`, `subtitle`, `comparison`, `sparkline` | Reads the first returned query row. |
44
+ | `gauge_card` | `card` | `value`, `target`, `progress`, `color` | Reads the first returned query row. |
45
+ | `pivot_table` | `pivot` | `rows`, `columns`, `values` | Uses query rows to build a pivot table. |
45
46
 
46
47
  Chart widget types:
47
48
 
48
49
  | Chart type | Notes |
49
50
  | --- | --- |
50
- | `line` | Uses `x_field` and `y_field`; optional `series_name` and `color`. |
51
- | `pie` | Uses `label_field` and optional `value_field`; without `value_field`, rows are counted by label. |
52
- | `bar` | Uses `label_field` and `value_field`, or `bucket_field` with `buckets`. |
53
- | `stacked_bar` | Uses `x_field` and `series`; if `series` is omitted, non-x columns become series. |
54
- | `funnel` | Uses `label_field`, `value_field`, and optional `colors`. |
55
- | `histogram` | Uses the same bucket settings as `bar`. |
51
+ | `line` | Uses `x` and `y`; `y` may contain multiple fields in config. |
52
+ | `pie` | Uses `label` and `value`. |
53
+ | `bar` | Uses `x` and `y`. |
54
+ | `stacked_bar` | Uses `x`, `y`, and `series`. |
55
+ | `funnel` | Uses `query.steps` and optional `label`, `value`, `colors`. |
56
+ | `histogram` | Uses `x`, `y`, and optional `buckets`. |
56
57
 
57
- ## Data Source Shape
58
+ ## Query Shape
58
59
 
59
60
  ```ts
60
- type WidgetDataSource =
61
- | {
62
- type: 'resource'
63
- resource_id: string
64
- columns?: string[]
65
- filters?: unknown
66
- sort?: unknown
67
- }
68
- | {
69
- type: 'aggregate'
70
- resource_id: string
71
- aggregations: Record<string, {
72
- operation: 'sum' | 'count' | 'avg' | 'min' | 'max' | 'median'
73
- field?: string
74
- }>
75
- group_by?:
76
- | { type: 'field'; field: string }
77
- | {
78
- type: 'date_trunc'
79
- field: string
80
- truncation: 'day' | 'week' | 'month' | 'year'
81
- timezone?: string
82
- }
83
- filters?: unknown
84
- }
61
+ type QueryConfig = {
62
+ resource: string
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 }
66
+ | { calc: string; as: string }
67
+ >
68
+ filters?: unknown
69
+ group_by?: Array<string | { field: string; as?: string; grain?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; timezone?: string }>
70
+ order_by?: Array<{ field: string; direction?: 'asc' | 'desc' }>
71
+ limit?: number
72
+ offset?: number
73
+ }
74
+ ```
75
+
76
+ Step-based chart queries use `steps` and may include `calcs`:
77
+
78
+ ```yaml
79
+ target: chart
80
+ label: Average price by database
81
+ variables:
82
+ price_multipliers:
83
+ cars_sl: 0.84
84
+ cars_mysql: 1.12
85
+ cars_pg: 0.91
86
+ cars_mongo: 1.07
87
+ cars_ch: 0.76
88
+ chart:
89
+ type: bar
90
+ title: Average price by database
91
+ x:
92
+ field: name
93
+ y:
94
+ field: adjusted_value
95
+ query:
96
+ steps:
97
+ - name: SQLite
98
+ resource: cars_sl
99
+ metric:
100
+ agg: avg
101
+ field: price
102
+ as: value
103
+ - name: MySQL
104
+ resource: cars_mysql
105
+ metric:
106
+ agg: avg
107
+ field: price
108
+ as: value
109
+ calcs:
110
+ - calc: value * lookup($variables.price_multipliers, resource, 1)
111
+ as: adjusted_value
85
112
  ```
86
113
 
87
- `resource_id` is an AdminForth `resourceId`. The data source is executed through AdminForth resources, so widgets use the same resource contracts as the rest of the application.
114
+ Widget-level variables example:
115
+
116
+ ```yaml
117
+ target: chart
118
+ label: Model costs
119
+ variables:
120
+ token_prices_per_1m:
121
+ input:
122
+ gpt-4.1: 2.00
123
+ gpt-4.1-mini: 0.40
124
+ gpt-4o-mini: 0.15
125
+ output:
126
+ gpt-4.1: 8.00
127
+ gpt-4.1-mini: 1.60
128
+ gpt-4o-mini: 0.60
129
+ cached:
130
+ gpt-4.1: 0.50
131
+ gpt-4.1-mini: 0.10
132
+ gpt-4o-mini: 0.075
133
+ chart:
134
+ type: stacked_bar
135
+ title: LLM costs by model
136
+ x:
137
+ field: model
138
+ y:
139
+ - field: input_cost
140
+ - field: output_cost
141
+ - field: cached_cost
142
+ query:
143
+ resource: model_usage
144
+ select:
145
+ - field: model
146
+ - agg: sum
147
+ field: input_tokens
148
+ as: input_tokens
149
+ - agg: sum
150
+ field: output_tokens
151
+ as: output_tokens
152
+ - agg: sum
153
+ field: cached_tokens
154
+ as: cached_tokens
155
+ group_by:
156
+ - model
157
+ calcs:
158
+ - calc: input_tokens / 1000000 * lookup($variables.token_prices_per_1m.input, model, 0)
159
+ as: input_cost
160
+ - calc: output_tokens / 1000000 * lookup($variables.token_prices_per_1m.output, model, 0)
161
+ as: output_cost
162
+ - calc: cached_tokens / 1000000 * lookup($variables.token_prices_per_1m.cached, model, 0)
163
+ as: cached_cost
164
+ ```
88
165
 
89
166
  ## Runtime Structure
90
167
 
@@ -143,6 +143,10 @@ export const dashboardApi = {
143
143
  return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
144
144
  },
145
145
 
146
+ async setDashboardConfig(slug: string, config: DashboardConfig): Promise<DashboardResponse> {
147
+ return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
148
+ },
149
+
146
150
  async addDashboardGroup(slug: string): Promise<DashboardResponse> {
147
151
  return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
148
152
  },
@@ -13,6 +13,7 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
13
13
  const height = ref(0)
14
14
 
15
15
  let observer: ResizeObserver | undefined
16
+ let frameId: number | undefined
16
17
 
17
18
  onMounted(() => {
18
19
  observer = new ResizeObserver(([entry]) => {
@@ -20,8 +21,18 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
20
21
  return
21
22
  }
22
23
 
23
- width.value = Math.floor(entry.contentRect.width)
24
- height.value = Math.floor(entry.contentRect.height)
24
+ const nextWidth = Math.floor(entry.contentRect.width)
25
+ const nextHeight = Math.floor(entry.contentRect.height)
26
+
27
+ if (frameId !== undefined) {
28
+ cancelAnimationFrame(frameId)
29
+ }
30
+
31
+ frameId = requestAnimationFrame(() => {
32
+ frameId = undefined
33
+ width.value = nextWidth
34
+ height.value = nextHeight
35
+ })
25
36
  })
26
37
 
27
38
  if (el.value) {
@@ -30,6 +41,10 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
30
41
  })
31
42
 
32
43
  onBeforeUnmount(() => {
44
+ if (frameId !== undefined) {
45
+ cancelAnimationFrame(frameId)
46
+ }
47
+
33
48
  observer?.disconnect()
34
49
  })
35
50