@adminforth/dashboard 1.8.0 → 1.9.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 CHANGED
@@ -29,7 +29,7 @@ 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
+ | `variables` | Optional widget variables passed to widget data loading. Variables are not available inside `query.calcs`. |
33
33
  | `size` | Preset width: `small`, `medium`, `large`, `wide`, or `full`. |
34
34
  | `width`, `height`, `min_width`, `max_width` | Optional explicit layout constraints. |
35
35
  | `query` | Data query definition. |
@@ -39,7 +39,7 @@ Each widget has common fields:
39
39
  | Widget target | Config field | Main settings | Data usage |
40
40
  | --- | --- | --- | --- |
41
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`. |
42
+ | `chart` | `chart` | `type`, `x`, `y`, `label`, `value`, `series`, `buckets`, `color`, `colors` | Uses the same `query` shape for every chart type. Multi-resource charts use `query.source: steps`. |
43
43
  | `kpi_card` | `card` | `value`, `subtitle`, `comparison`, `sparkline` | Reads the first returned query row. |
44
44
  | `gauge_card` | `card` | `value`, `target`, `progress`, `color` | Reads the first returned query row. |
45
45
  | `pivot_table` | `pivot` | `rows`, `columns`, `values` | Uses query rows to build a pivot table. |
@@ -52,13 +52,14 @@ Chart widget types:
52
52
  | `pie` | Uses `label` and `value`. |
53
53
  | `bar` | Uses `x` and `y`. |
54
54
  | `stacked_bar` | Uses `x`, `y`, and `series`. |
55
- | `funnel` | Uses `query.steps` and optional `label`, `value`, `colors`. |
55
+ | `funnel` | Uses `label`, `value`, and optional `colors`. Data comes from the same `query` shapes as every other chart. |
56
56
  | `histogram` | Uses `x`, `y`, and optional `buckets`. |
57
57
 
58
58
  ## Query Shape
59
59
 
60
60
  ```ts
61
61
  type QueryConfig = {
62
+ source?: 'resource'
62
63
  resource: string
63
64
  select?: Array<
64
65
  | { field: string; as?: string; grain?: 'day' | 'week' | 'month' | 'year' }
@@ -70,6 +71,22 @@ type QueryConfig = {
70
71
  order_by?: Array<{ field: string; direction?: 'asc' | 'desc' }>
71
72
  limit?: number
72
73
  offset?: number
74
+ bucket?: { field: string; buckets: Array<{ label: string; min?: number; max?: number }> }
75
+ calcs?: Array<{ calc: string; as: string }>
76
+ formatting?: Record<string, JsonValue>
77
+ } | {
78
+ source: 'steps'
79
+ steps: Array<{
80
+ name: string
81
+ resource: string
82
+ select: Array<{ agg: 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'; field?: string; as: string; filters?: DashboardFilter | DashboardFilter[] }>
83
+ filters?: DashboardFilter | DashboardFilter[]
84
+ }>
85
+ calcs?: Array<{ calc: string; as: string }>
86
+ order_by?: Array<{ field: string; direction?: 'asc' | 'desc' }>
87
+ limit?: number
88
+ offset?: number
89
+ formatting?: Record<string, JsonValue>
73
90
  }
74
91
 
75
92
  type DashboardFilter =
@@ -77,91 +94,97 @@ type DashboardFilter =
77
94
  | { or: DashboardFilter[] }
78
95
  | {
79
96
  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
97
+ eq?: FilterValue
98
+ neq?: FilterValue
99
+ gt?: FilterValue
100
+ gte?: FilterValue
101
+ lt?: FilterValue
102
+ lte?: FilterValue
103
+ in?: FilterValue[]
104
+ not_in?: FilterValue[]
105
+ like?: FilterValue
106
+ ilike?: FilterValue
90
107
  }
91
108
 
92
109
  type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
110
+ type RelativeDateValue = { now: true } | { now_minus: `${number}${'h' | 'd' | 'w' | 'mo' | 'y'}` }
111
+ type FilterValue = JsonValue | RelativeDateValue
112
+ ```
113
+
114
+ Use `filters` for rolling date ranges. Do not hard-code dates for dashboards that should move with time:
115
+
116
+ ```yaml
117
+ query:
118
+ resource: orders
119
+ filters:
120
+ and:
121
+ - field: created_at
122
+ gte:
123
+ now_minus: 30d
124
+ - field: created_at
125
+ lt:
126
+ now: true
93
127
  ```
94
128
 
95
- Step-based chart queries use `steps` and may include `calcs`:
129
+ Multi-resource queries use `source: steps`. Each step uses `select`, even if it has only one aggregate:
96
130
 
97
131
  ```yaml
98
132
  target: chart
99
133
  label: Average price by database
100
- variables:
101
- price_multipliers:
102
- cars_sl: 0.84
103
- cars_mysql: 1.12
104
- cars_pg: 0.91
105
- cars_mongo: 1.07
106
- cars_ch: 0.76
107
134
  chart:
108
135
  type: bar
109
136
  title: Average price by database
110
137
  x:
111
138
  field: name
112
139
  y:
113
- field: adjusted_value
140
+ field: value
114
141
  query:
142
+ source: steps
115
143
  steps:
116
144
  - name: SQLite
117
145
  resource: cars_sl
118
- metric:
119
- agg: avg
120
- field: price
121
- as: value
146
+ select:
147
+ - agg: avg
148
+ field: price
149
+ as: value
122
150
  - name: MySQL
123
151
  resource: cars_mysql
124
- metric:
125
- agg: avg
126
- field: price
127
- as: value
128
- calcs:
129
- - calc: value * lookup($variables.price_multipliers, resource, 1)
130
- as: adjusted_value
152
+ select:
153
+ - agg: avg
154
+ field: price
155
+ as: value
131
156
  ```
132
157
 
133
- Widget-level variables example:
158
+ Cost calculation example:
134
159
 
135
160
  ```yaml
136
161
  target: chart
137
162
  label: Model costs
138
- variables:
139
- token_prices_per_1m:
140
- input:
141
- gpt-4.1: 2.00
142
- gpt-4.1-mini: 0.40
143
- gpt-4o-mini: 0.15
144
- output:
145
- gpt-4.1: 8.00
146
- gpt-4.1-mini: 1.60
147
- gpt-4o-mini: 0.60
148
- cached:
149
- gpt-4.1: 0.50
150
- gpt-4.1-mini: 0.10
151
- gpt-4o-mini: 0.075
152
163
  chart:
153
164
  type: stacked_bar
154
- title: LLM costs by model
165
+ title: GPT-5.4 costs by day
155
166
  x:
156
- field: model
167
+ field: day
157
168
  y:
158
169
  - field: input_cost
159
170
  - field: output_cost
160
171
  - field: cached_cost
161
172
  query:
162
173
  resource: model_usage
174
+ filters:
175
+ and:
176
+ - field: model
177
+ eq: gpt-5.4
178
+ - field: used_at
179
+ gte:
180
+ now_minus: 7d
181
+ - field: used_at
182
+ lt:
183
+ now: true
163
184
  select:
164
- - field: model
185
+ - field: used_at
186
+ as: day
187
+ grain: day
165
188
  - agg: sum
166
189
  field: input_tokens
167
190
  as: input_tokens
@@ -172,16 +195,19 @@ query:
172
195
  field: cached_tokens
173
196
  as: cached_tokens
174
197
  group_by:
175
- - model
198
+ - field: used_at
199
+ as: day
200
+ grain: day
176
201
  calcs:
177
- - calc: input_tokens / 1000000 * lookup($variables.token_prices_per_1m.input, model, 0)
202
+ - calc: input_tokens / 1000000 * 2.5
178
203
  as: input_cost
179
- - calc: output_tokens / 1000000 * lookup($variables.token_prices_per_1m.output, model, 0)
204
+ - calc: output_tokens / 1000000 * 15
180
205
  as: output_cost
181
- - calc: cached_tokens / 1000000 * lookup($variables.token_prices_per_1m.cached, model, 0)
206
+ - calc: cached_tokens / 1000000 * 0.25
182
207
  as: cached_cost
183
208
  ```
184
209
 
210
+
185
211
  ## Runtime Structure
186
212
 
187
213
  ```text
@@ -140,19 +140,12 @@ export type ResourceQueryConfig = {
140
140
  formatting?: Record<string, JsonValue>
141
141
  }
142
142
 
143
- export type StepsQueryStepConfig =
144
- | {
145
- name: string
146
- resource: string
147
- metric: QueryAggregateSelectItem
148
- filters?: FilterExpression
149
- }
150
- | {
151
- name: string
152
- resource: string
153
- select: QueryAggregateSelectItem[]
154
- filters?: FilterExpression
155
- }
143
+ export type StepsQueryStepConfig = {
144
+ name: string
145
+ resource: string
146
+ select: QueryAggregateSelectItem[]
147
+ filters?: FilterExpression
148
+ }
156
149
 
157
150
  export type StepsQueryConfig = {
158
151
  source: 'steps'
@@ -169,21 +169,18 @@ query:
169
169
  steps:
170
170
  - name: Leads
171
171
  resource: leads
172
- metric:
173
- agg: count
174
- as: value
172
+ select:
173
+ - agg: count
174
+ as: value
175
175
  - name: Customers
176
176
  resource: orders
177
- metric:
178
- agg: count_distinct
179
- field: customer_id
180
- as: value
181
-
182
- Each step may use either:
183
- - metric for one aggregate
184
- - select for multiple aggregate fields
177
+ select:
178
+ - agg: count_distinct
179
+ field: customer_id
180
+ as: value
185
181
 
186
182
  Do not use bare query.steps without source: steps.
183
+ Do not use metric. Use select even when a step has only one aggregate.
187
184
 
188
185
  ## Date range rules
189
186
 
@@ -224,22 +221,18 @@ select raw token totals:
224
221
  - sum output_tokens as output_tokens
225
222
 
226
223
  then query.calcs:
227
- - calculate total_spend from those aliases and lookup variables
224
+ - calculate total_spend from those aliases with explicit constants
228
225
 
229
226
  For today vs yesterday KPI, use multiple aggregate select items with filters and distinct aliases, then calcs.
230
227
 
231
- ## Calc variables
228
+ ## Calc rules
232
229
 
233
- Use variables for static maps/rates.
234
- Use lookup($variables.some.map, row_field, default_number) in query.calcs.
230
+ Calcs can reference only fields already present in the current row.
231
+ Use explicit constants for rates.
235
232
 
236
233
  Minimal example:
237
234
 
238
- variables:
239
- prices:
240
- gpt-5.4: 2.5
241
-
242
235
  query:
243
236
  calcs:
244
- - calc: tokens / 1000000 * lookup($variables.prices, model, 0)
237
+ - calc: tokens / 1000000 * 2.5
245
238
  as: cost
@@ -114,11 +114,6 @@ export type ResourceQueryConfig = {
114
114
  formatting?: Record<string, JsonValue>;
115
115
  };
116
116
  export type StepsQueryStepConfig = {
117
- name: string;
118
- resource: string;
119
- metric: QueryAggregateSelectItem;
120
- filters?: FilterExpression;
121
- } | {
122
117
  name: string;
123
118
  resource: string;
124
119
  select: QueryAggregateSelectItem[];
@@ -140,19 +140,12 @@ export type ResourceQueryConfig = {
140
140
  formatting?: Record<string, JsonValue>
141
141
  }
142
142
 
143
- export type StepsQueryStepConfig =
144
- | {
145
- name: string
146
- resource: string
147
- metric: QueryAggregateSelectItem
148
- filters?: FilterExpression
149
- }
150
- | {
151
- name: string
152
- resource: string
153
- select: QueryAggregateSelectItem[]
154
- filters?: FilterExpression
155
- }
143
+ export type StepsQueryStepConfig = {
144
+ name: string
145
+ resource: string
146
+ select: QueryAggregateSelectItem[]
147
+ filters?: FilterExpression
148
+ }
156
149
 
157
150
  export type StepsQueryConfig = {
158
151
  source: 'steps'
@@ -95,17 +95,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
95
95
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
96
96
  } | {
97
97
  source: "steps";
98
- steps: ({
99
- name: string;
100
- resource: string;
101
- metric: {
102
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
103
- field?: string | undefined;
104
- as: string;
105
- filters?: any;
106
- };
107
- filters?: any;
108
- } | {
98
+ steps: {
109
99
  name: string;
110
100
  resource: string;
111
101
  select: {
@@ -115,7 +105,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
115
105
  filters?: any;
116
106
  }[];
117
107
  filters?: any;
118
- })[];
108
+ }[];
119
109
  calcs?: {
120
110
  calc: string;
121
111
  as: string;
@@ -230,17 +220,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
230
220
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
231
221
  } | {
232
222
  source: "steps";
233
- steps: ({
234
- name: string;
235
- resource: string;
236
- metric: {
237
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
238
- field?: string | undefined;
239
- as: string;
240
- filters?: any;
241
- };
242
- filters?: any;
243
- } | {
223
+ steps: {
244
224
  name: string;
245
225
  resource: string;
246
226
  select: {
@@ -250,7 +230,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
250
230
  filters?: any;
251
231
  }[];
252
232
  filters?: any;
253
- })[];
233
+ }[];
254
234
  calcs?: {
255
235
  calc: string;
256
236
  as: string;
@@ -361,17 +341,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
361
341
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
362
342
  } | {
363
343
  source: "steps";
364
- steps: ({
365
- name: string;
366
- resource: string;
367
- metric: {
368
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
369
- field?: string | undefined;
370
- as: string;
371
- filters?: any;
372
- };
373
- filters?: any;
374
- } | {
344
+ steps: {
375
345
  name: string;
376
346
  resource: string;
377
347
  select: {
@@ -381,7 +351,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
381
351
  filters?: any;
382
352
  }[];
383
353
  filters?: any;
384
- })[];
354
+ }[];
385
355
  calcs?: {
386
356
  calc: string;
387
357
  as: string;
@@ -477,17 +447,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
477
447
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
478
448
  } | {
479
449
  source: "steps";
480
- steps: ({
481
- name: string;
482
- resource: string;
483
- metric: {
484
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
485
- field?: string | undefined;
486
- as: string;
487
- filters?: any;
488
- };
489
- filters?: any;
490
- } | {
450
+ steps: {
491
451
  name: string;
492
452
  resource: string;
493
453
  select: {
@@ -497,7 +457,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
497
457
  filters?: any;
498
458
  }[];
499
459
  filters?: any;
500
- })[];
460
+ }[];
501
461
  calcs?: {
502
462
  calc: string;
503
463
  as: string;
@@ -590,17 +550,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
590
550
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
591
551
  } | {
592
552
  source: "steps";
593
- steps: ({
594
- name: string;
595
- resource: string;
596
- metric: {
597
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
598
- field?: string | undefined;
599
- as: string;
600
- filters?: any;
601
- };
602
- filters?: any;
603
- } | {
553
+ steps: {
604
554
  name: string;
605
555
  resource: string;
606
556
  select: {
@@ -610,7 +560,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
610
560
  filters?: any;
611
561
  }[];
612
562
  filters?: any;
613
- })[];
563
+ }[];
614
564
  calcs?: {
615
565
  calc: string;
616
566
  as: string;
@@ -720,17 +670,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
720
670
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
721
671
  } | {
722
672
  source: "steps";
723
- steps: ({
724
- name: string;
725
- resource: string;
726
- metric: {
727
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
728
- field?: string | undefined;
729
- as: string;
730
- filters?: any;
731
- };
732
- filters?: any;
733
- } | {
673
+ steps: {
734
674
  name: string;
735
675
  resource: string;
736
676
  select: {
@@ -740,7 +680,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
740
680
  filters?: any;
741
681
  }[];
742
682
  filters?: any;
743
- })[];
683
+ }[];
744
684
  calcs?: {
745
685
  calc: string;
746
686
  as: string;
@@ -855,17 +795,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
855
795
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
856
796
  } | {
857
797
  source: "steps";
858
- steps: ({
859
- name: string;
860
- resource: string;
861
- metric: {
862
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
863
- field?: string | undefined;
864
- as: string;
865
- filters?: any;
866
- };
867
- filters?: any;
868
- } | {
798
+ steps: {
869
799
  name: string;
870
800
  resource: string;
871
801
  select: {
@@ -875,7 +805,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
875
805
  filters?: any;
876
806
  }[];
877
807
  filters?: any;
878
- })[];
808
+ }[];
879
809
  calcs?: {
880
810
  calc: string;
881
811
  as: string;
@@ -986,17 +916,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
986
916
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
987
917
  } | {
988
918
  source: "steps";
989
- steps: ({
990
- name: string;
991
- resource: string;
992
- metric: {
993
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
994
- field?: string | undefined;
995
- as: string;
996
- filters?: any;
997
- };
998
- filters?: any;
999
- } | {
919
+ steps: {
1000
920
  name: string;
1001
921
  resource: string;
1002
922
  select: {
@@ -1006,7 +926,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
1006
926
  filters?: any;
1007
927
  }[];
1008
928
  filters?: any;
1009
- })[];
929
+ }[];
1010
930
  calcs?: {
1011
931
  calc: string;
1012
932
  as: string;
@@ -1102,17 +1022,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
1102
1022
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
1103
1023
  } | {
1104
1024
  source: "steps";
1105
- steps: ({
1106
- name: string;
1107
- resource: string;
1108
- metric: {
1109
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
1110
- field?: string | undefined;
1111
- as: string;
1112
- filters?: any;
1113
- };
1114
- filters?: any;
1115
- } | {
1025
+ steps: {
1116
1026
  name: string;
1117
1027
  resource: string;
1118
1028
  select: {
@@ -1122,7 +1032,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
1122
1032
  filters?: any;
1123
1033
  }[];
1124
1034
  filters?: any;
1125
- })[];
1035
+ }[];
1126
1036
  calcs?: {
1127
1037
  calc: string;
1128
1038
  as: string;
@@ -1215,17 +1125,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
1215
1125
  formatting?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
1216
1126
  } | {
1217
1127
  source: "steps";
1218
- steps: ({
1219
- name: string;
1220
- resource: string;
1221
- metric: {
1222
- agg: import("../model/dashboard.types.js").QueryAggregateOperation;
1223
- field?: string | undefined;
1224
- as: string;
1225
- filters?: any;
1226
- };
1227
- filters?: any;
1228
- } | {
1128
+ steps: {
1229
1129
  name: string;
1230
1130
  resource: string;
1231
1131
  select: {
@@ -1235,7 +1135,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
1235
1135
  filters?: any;
1236
1136
  }[];
1237
1137
  filters?: any;
1238
- })[];
1138
+ }[];
1239
1139
  calcs?: {
1240
1140
  calc: string;
1241
1141
  as: string;