@adminforth/dashboard 1.10.0 → 1.11.1
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/custom/skills/adminforth-dashboard/SKILL.md +305 -10
- package/custom/widgets/KpiCardWidget.vue +3 -3
- package/custom/widgets/chart/StackedBarChart.vue +6 -2
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +305 -10
- package/dist/custom/widgets/KpiCardWidget.vue +3 -3
- package/dist/custom/widgets/chart/StackedBarChart.vue +6 -2
- package/dist/endpoint/dashboard.d.ts +1 -0
- package/dist/endpoint/dashboard.js +15 -1
- package/dist/schema/api.d.ts +8 -0
- package/dist/schema/api.js +5 -0
- package/dist/services/dashboardConfigService.d.ts +2 -0
- package/dist/services/dashboardConfigService.js +6 -0
- package/endpoint/dashboard.ts +17 -0
- package/package.json +1 -1
- package/schema/api.ts +6 -0
- package/services/dashboardConfigService.ts +9 -0
|
@@ -22,26 +22,320 @@ Dashboard root, groups, and widgets are different entities.
|
|
|
22
22
|
|
|
23
23
|
## Tool routing
|
|
24
24
|
|
|
25
|
+
- Get dashboard slugs: dashboard_get_slugs
|
|
25
26
|
- Read dashboard: dashboard_get_config
|
|
26
27
|
- Add group: dashboard_add_dashboard_group
|
|
27
28
|
- Rename group: dashboard_set_dashboard_group_config
|
|
28
29
|
- Add widget slot: dashboard_add_dashboard_widget
|
|
29
|
-
- Configure
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
30
|
+
- Configure widget:
|
|
31
|
+
- table: dashboard_configure_table_widget
|
|
32
|
+
- kpi_card: dashboard_configure_kpi_card_widget
|
|
33
|
+
- gauge_card: dashboard_configure_gauge_card_widget
|
|
34
|
+
- pivot_table: dashboard_configure_pivot_table_widget
|
|
35
|
+
- line_chart: dashboard_configure_line_chart_widget
|
|
36
|
+
- bar_chart: dashboard_configure_bar_chart_widget
|
|
37
|
+
- stacked_bar_chart: dashboard_configure_stacked_bar_chart_widget
|
|
38
|
+
- pie_chart: dashboard_configure_pie_chart_widget
|
|
39
|
+
- histogram_chart: dashboard_configure_histogram_chart_widget
|
|
40
|
+
- funnel_chart: dashboard_configure_funnel_chart_widget
|
|
39
41
|
- Move/remove widget/group: matching move/remove tool
|
|
40
42
|
- Load widget data: dashboard_get_dashboard_widget_data
|
|
41
43
|
|
|
42
44
|
If a known dashboard tool schema is missing, call fetch_tool_schema for that exact tool.
|
|
43
45
|
If fetch_tool_schema returns but the intended tool is still not callable, stop and report a tool-routing error. Do not substitute another mutation tool.
|
|
44
46
|
|
|
47
|
+
## Configure schema examples
|
|
48
|
+
|
|
49
|
+
These examples show the expected shape only. Do not copy them one-to-one: adapt resource names, fields, aggregations, labels, filters, formats, and calculations to the actual dashboard request and available resource columns.
|
|
50
|
+
|
|
51
|
+
Important:
|
|
52
|
+
- `config.target` is the widget target, never a resource path.
|
|
53
|
+
- Never use values like `/resource/llm_usage` in `config.target`.
|
|
54
|
+
- Put the data resource in `config.query.resource`, for example `query.resource: "llm_usage"`.
|
|
55
|
+
- For chart widgets, `config.target` is always `chart`; the concrete chart kind is `config.chart.type`.
|
|
56
|
+
- `query.calcs[].calc` is an expression over already selected fields/aliases, not raw SQL. Do not use SQL syntax such as `CASE WHEN`.
|
|
57
|
+
|
|
58
|
+
Example `dashboard_configure_table_widget` config:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
target: table
|
|
62
|
+
label: Recent usage
|
|
63
|
+
size: wide
|
|
64
|
+
table:
|
|
65
|
+
columns:
|
|
66
|
+
- field: used_at
|
|
67
|
+
label: Date
|
|
68
|
+
- field: model
|
|
69
|
+
label: Model
|
|
70
|
+
- field: total_tokens
|
|
71
|
+
label: Tokens
|
|
72
|
+
format: integer
|
|
73
|
+
pagination: true
|
|
74
|
+
page_size: 20
|
|
75
|
+
query:
|
|
76
|
+
resource: llm_usage
|
|
77
|
+
select:
|
|
78
|
+
- field: used_at
|
|
79
|
+
- field: model
|
|
80
|
+
- field: total_tokens
|
|
81
|
+
order_by:
|
|
82
|
+
- field: used_at
|
|
83
|
+
direction: desc
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Example `dashboard_configure_kpi_card_widget` config:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
target: kpi_card
|
|
90
|
+
label: Total spend
|
|
91
|
+
size: medium
|
|
92
|
+
card:
|
|
93
|
+
title: Total spend
|
|
94
|
+
value:
|
|
95
|
+
field: spend
|
|
96
|
+
format: currency
|
|
97
|
+
query:
|
|
98
|
+
resource: llm_usage
|
|
99
|
+
select:
|
|
100
|
+
- agg: sum
|
|
101
|
+
field: cost
|
|
102
|
+
as: spend
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Example `dashboard_configure_gauge_card_widget` config:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
target: gauge_card
|
|
109
|
+
label: Budget usage
|
|
110
|
+
size: medium
|
|
111
|
+
card:
|
|
112
|
+
title: Budget usage
|
|
113
|
+
value:
|
|
114
|
+
field: spend
|
|
115
|
+
format: currency
|
|
116
|
+
progress:
|
|
117
|
+
value_field: spend
|
|
118
|
+
target_value: 1000
|
|
119
|
+
format: percent
|
|
120
|
+
query:
|
|
121
|
+
resource: llm_usage
|
|
122
|
+
select:
|
|
123
|
+
- agg: sum
|
|
124
|
+
field: cost
|
|
125
|
+
as: spend
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Example `dashboard_configure_pivot_table_widget` config:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
target: pivot_table
|
|
132
|
+
label: Spend by model and purpose
|
|
133
|
+
size: wide
|
|
134
|
+
pivot:
|
|
135
|
+
rows:
|
|
136
|
+
- field: model
|
|
137
|
+
label: Model
|
|
138
|
+
columns:
|
|
139
|
+
- field: purpose
|
|
140
|
+
label: Purpose
|
|
141
|
+
values:
|
|
142
|
+
- field: spend
|
|
143
|
+
label: Spend
|
|
144
|
+
format: currency
|
|
145
|
+
aggregation: sum
|
|
146
|
+
query:
|
|
147
|
+
resource: llm_usage
|
|
148
|
+
select:
|
|
149
|
+
- field: model
|
|
150
|
+
- field: purpose
|
|
151
|
+
- agg: sum
|
|
152
|
+
field: cost
|
|
153
|
+
as: spend
|
|
154
|
+
group_by:
|
|
155
|
+
- model
|
|
156
|
+
- purpose
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Example `dashboard_configure_line_chart_widget` config:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
target: chart
|
|
163
|
+
label: Daily spend
|
|
164
|
+
size: wide
|
|
165
|
+
chart:
|
|
166
|
+
type: line
|
|
167
|
+
x:
|
|
168
|
+
field: day
|
|
169
|
+
label: Day
|
|
170
|
+
y:
|
|
171
|
+
- field: spend
|
|
172
|
+
label: Spend
|
|
173
|
+
format: currency
|
|
174
|
+
query:
|
|
175
|
+
resource: llm_usage
|
|
176
|
+
select:
|
|
177
|
+
- field: used_at
|
|
178
|
+
grain: day
|
|
179
|
+
as: day
|
|
180
|
+
- agg: sum
|
|
181
|
+
field: cost
|
|
182
|
+
as: spend
|
|
183
|
+
group_by:
|
|
184
|
+
- field: used_at
|
|
185
|
+
grain: day
|
|
186
|
+
as: day
|
|
187
|
+
order_by:
|
|
188
|
+
- field: day
|
|
189
|
+
direction: asc
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Example `dashboard_configure_bar_chart_widget` config:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
target: chart
|
|
196
|
+
label: Spend by model
|
|
197
|
+
size: wide
|
|
198
|
+
chart:
|
|
199
|
+
type: bar
|
|
200
|
+
x:
|
|
201
|
+
field: model
|
|
202
|
+
label: Model
|
|
203
|
+
y:
|
|
204
|
+
field: spend
|
|
205
|
+
label: Spend
|
|
206
|
+
format: currency
|
|
207
|
+
query:
|
|
208
|
+
resource: llm_usage
|
|
209
|
+
select:
|
|
210
|
+
- field: model
|
|
211
|
+
- agg: sum
|
|
212
|
+
field: cost
|
|
213
|
+
as: spend
|
|
214
|
+
group_by:
|
|
215
|
+
- model
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Example `dashboard_configure_stacked_bar_chart_widget` config:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
target: chart
|
|
222
|
+
label: Daily spend by purpose
|
|
223
|
+
size: wide
|
|
224
|
+
chart:
|
|
225
|
+
type: stacked_bar
|
|
226
|
+
x:
|
|
227
|
+
field: day
|
|
228
|
+
label: Day
|
|
229
|
+
y:
|
|
230
|
+
field: spend
|
|
231
|
+
label: Spend
|
|
232
|
+
format: currency
|
|
233
|
+
series:
|
|
234
|
+
field: purpose
|
|
235
|
+
label: Purpose
|
|
236
|
+
query:
|
|
237
|
+
resource: llm_usage
|
|
238
|
+
select:
|
|
239
|
+
- field: used_at
|
|
240
|
+
grain: day
|
|
241
|
+
as: day
|
|
242
|
+
- field: purpose
|
|
243
|
+
- agg: sum
|
|
244
|
+
field: cost
|
|
245
|
+
as: spend
|
|
246
|
+
group_by:
|
|
247
|
+
- field: used_at
|
|
248
|
+
grain: day
|
|
249
|
+
as: day
|
|
250
|
+
- purpose
|
|
251
|
+
order_by:
|
|
252
|
+
- field: day
|
|
253
|
+
direction: asc
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Example `dashboard_configure_pie_chart_widget` config:
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
target: chart
|
|
260
|
+
label: Spend share by model
|
|
261
|
+
size: medium
|
|
262
|
+
chart:
|
|
263
|
+
type: pie
|
|
264
|
+
label:
|
|
265
|
+
field: model
|
|
266
|
+
label: Model
|
|
267
|
+
value:
|
|
268
|
+
field: spend
|
|
269
|
+
label: Spend
|
|
270
|
+
format: currency
|
|
271
|
+
query:
|
|
272
|
+
resource: llm_usage
|
|
273
|
+
select:
|
|
274
|
+
- field: model
|
|
275
|
+
- agg: sum
|
|
276
|
+
field: cost
|
|
277
|
+
as: spend
|
|
278
|
+
group_by:
|
|
279
|
+
- model
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Example `dashboard_configure_histogram_chart_widget` config:
|
|
283
|
+
|
|
284
|
+
```yaml
|
|
285
|
+
target: chart
|
|
286
|
+
label: Request size distribution
|
|
287
|
+
size: wide
|
|
288
|
+
chart:
|
|
289
|
+
type: histogram
|
|
290
|
+
x:
|
|
291
|
+
field: total_tokens
|
|
292
|
+
label: Tokens
|
|
293
|
+
y:
|
|
294
|
+
field: requests
|
|
295
|
+
label: Requests
|
|
296
|
+
query:
|
|
297
|
+
resource: llm_usage
|
|
298
|
+
select:
|
|
299
|
+
- field: total_tokens
|
|
300
|
+
- agg: count
|
|
301
|
+
as: requests
|
|
302
|
+
bucket:
|
|
303
|
+
field: total_tokens
|
|
304
|
+
buckets:
|
|
305
|
+
- label: Small
|
|
306
|
+
max: 1000
|
|
307
|
+
- label: Medium
|
|
308
|
+
min: 1000
|
|
309
|
+
max: 10000
|
|
310
|
+
- label: Large
|
|
311
|
+
min: 10000
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Example `dashboard_configure_funnel_chart_widget` config:
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
target: chart
|
|
318
|
+
label: Request funnel
|
|
319
|
+
size: wide
|
|
320
|
+
chart:
|
|
321
|
+
type: funnel
|
|
322
|
+
label:
|
|
323
|
+
field: stage
|
|
324
|
+
label: Stage
|
|
325
|
+
value:
|
|
326
|
+
field: count
|
|
327
|
+
label: Count
|
|
328
|
+
query:
|
|
329
|
+
resource: llm_usage
|
|
330
|
+
select:
|
|
331
|
+
- field: stage
|
|
332
|
+
- agg: count
|
|
333
|
+
as: count
|
|
334
|
+
group_by:
|
|
335
|
+
- stage
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
|
|
45
339
|
## Group creation guard
|
|
46
340
|
|
|
47
341
|
Before creating a group, call dashboard_get_config and check existing groups.
|
|
@@ -148,6 +442,7 @@ Use target, not type.
|
|
|
148
442
|
For charts, use target: chart and chart.type for the concrete chart kind.
|
|
149
443
|
Use query, not dataSource.
|
|
150
444
|
Use resource, not resourceId.
|
|
445
|
+
Never use AdminForth routes such as /resource/llm_usage as resource or target values.
|
|
151
446
|
|
|
152
447
|
## Query shape rules
|
|
153
448
|
|
|
@@ -124,9 +124,9 @@ const label = computed(() => kpiConfig.value?.subtitle?.field
|
|
|
124
124
|
.join(': ')
|
|
125
125
|
: kpiConfig.value?.subtitle?.text ?? kpiConfig.value?.title ?? props.widget.label)
|
|
126
126
|
const formattedValue = computed(() => `${kpiConfig.value?.value.prefix ?? ''}${formatValue(value.value, kpiConfig.value?.value.format)}${kpiConfig.value?.value.suffix ?? ''}`)
|
|
127
|
-
const comparisonValue = computed(() =>
|
|
128
|
-
? firstRow.value[kpiConfig.value.comparison.field]
|
|
129
|
-
:
|
|
127
|
+
const comparisonValue = computed(() => kpiConfig.value?.comparison?.field
|
|
128
|
+
? value.value - toFiniteNumber(firstRow.value[kpiConfig.value.comparison.field])
|
|
129
|
+
: 0)
|
|
130
130
|
const comparison = computed(() => {
|
|
131
131
|
const config = kpiConfig.value?.comparison
|
|
132
132
|
|
|
@@ -120,7 +120,7 @@ const { el: rootEl, width: rootWidth } = useElementSize<HTMLDivElement>()
|
|
|
120
120
|
const { el: svgEl, width: svgWidth, height: svgHeight } = useElementSize<HTMLDivElement>()
|
|
121
121
|
|
|
122
122
|
const barGap = 10
|
|
123
|
-
const seriesNames = computed(() => Array.from(new Set(props.rows.map((row) =>
|
|
123
|
+
const seriesNames = computed(() => Array.from(new Set(props.rows.map((row) => formatSeriesLabel(row[props.seriesField])))))
|
|
124
124
|
const normalizedSeries = computed(() => seriesNames.value.map((name, index) => ({
|
|
125
125
|
name,
|
|
126
126
|
color: props.colors?.[index] || CHART_COLORS[index % CHART_COLORS.length],
|
|
@@ -141,7 +141,7 @@ const groupedRows = computed(() => {
|
|
|
141
141
|
for (const row of props.rows) {
|
|
142
142
|
const label = formatChartLabel(row[props.xField])
|
|
143
143
|
const item = grouped.get(label) ?? { [props.xField]: label }
|
|
144
|
-
const seriesName =
|
|
144
|
+
const seriesName = formatSeriesLabel(row[props.seriesField])
|
|
145
145
|
|
|
146
146
|
item[seriesName] = toFiniteNumber(item[seriesName]) + toFiniteNumber(row[props.yField])
|
|
147
147
|
|
|
@@ -240,4 +240,8 @@ function getBarTooltip(bar: { label: string, total: number, segments: Array<{ na
|
|
|
240
240
|
...segmentLines,
|
|
241
241
|
].join('\n')
|
|
242
242
|
}
|
|
243
|
+
|
|
244
|
+
function formatSeriesLabel(value: unknown) {
|
|
245
|
+
return typeof value === 'string' ? value : String(value)
|
|
246
|
+
}
|
|
243
247
|
</script>
|
|
@@ -22,26 +22,320 @@ Dashboard root, groups, and widgets are different entities.
|
|
|
22
22
|
|
|
23
23
|
## Tool routing
|
|
24
24
|
|
|
25
|
+
- Get dashboard slugs: dashboard_get_slugs
|
|
25
26
|
- Read dashboard: dashboard_get_config
|
|
26
27
|
- Add group: dashboard_add_dashboard_group
|
|
27
28
|
- Rename group: dashboard_set_dashboard_group_config
|
|
28
29
|
- Add widget slot: dashboard_add_dashboard_widget
|
|
29
|
-
- Configure
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
30
|
+
- Configure widget:
|
|
31
|
+
- table: dashboard_configure_table_widget
|
|
32
|
+
- kpi_card: dashboard_configure_kpi_card_widget
|
|
33
|
+
- gauge_card: dashboard_configure_gauge_card_widget
|
|
34
|
+
- pivot_table: dashboard_configure_pivot_table_widget
|
|
35
|
+
- line_chart: dashboard_configure_line_chart_widget
|
|
36
|
+
- bar_chart: dashboard_configure_bar_chart_widget
|
|
37
|
+
- stacked_bar_chart: dashboard_configure_stacked_bar_chart_widget
|
|
38
|
+
- pie_chart: dashboard_configure_pie_chart_widget
|
|
39
|
+
- histogram_chart: dashboard_configure_histogram_chart_widget
|
|
40
|
+
- funnel_chart: dashboard_configure_funnel_chart_widget
|
|
39
41
|
- Move/remove widget/group: matching move/remove tool
|
|
40
42
|
- Load widget data: dashboard_get_dashboard_widget_data
|
|
41
43
|
|
|
42
44
|
If a known dashboard tool schema is missing, call fetch_tool_schema for that exact tool.
|
|
43
45
|
If fetch_tool_schema returns but the intended tool is still not callable, stop and report a tool-routing error. Do not substitute another mutation tool.
|
|
44
46
|
|
|
47
|
+
## Configure schema examples
|
|
48
|
+
|
|
49
|
+
These examples show the expected shape only. Do not copy them one-to-one: adapt resource names, fields, aggregations, labels, filters, formats, and calculations to the actual dashboard request and available resource columns.
|
|
50
|
+
|
|
51
|
+
Important:
|
|
52
|
+
- `config.target` is the widget target, never a resource path.
|
|
53
|
+
- Never use values like `/resource/llm_usage` in `config.target`.
|
|
54
|
+
- Put the data resource in `config.query.resource`, for example `query.resource: "llm_usage"`.
|
|
55
|
+
- For chart widgets, `config.target` is always `chart`; the concrete chart kind is `config.chart.type`.
|
|
56
|
+
- `query.calcs[].calc` is an expression over already selected fields/aliases, not raw SQL. Do not use SQL syntax such as `CASE WHEN`.
|
|
57
|
+
|
|
58
|
+
Example `dashboard_configure_table_widget` config:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
target: table
|
|
62
|
+
label: Recent usage
|
|
63
|
+
size: wide
|
|
64
|
+
table:
|
|
65
|
+
columns:
|
|
66
|
+
- field: used_at
|
|
67
|
+
label: Date
|
|
68
|
+
- field: model
|
|
69
|
+
label: Model
|
|
70
|
+
- field: total_tokens
|
|
71
|
+
label: Tokens
|
|
72
|
+
format: integer
|
|
73
|
+
pagination: true
|
|
74
|
+
page_size: 20
|
|
75
|
+
query:
|
|
76
|
+
resource: llm_usage
|
|
77
|
+
select:
|
|
78
|
+
- field: used_at
|
|
79
|
+
- field: model
|
|
80
|
+
- field: total_tokens
|
|
81
|
+
order_by:
|
|
82
|
+
- field: used_at
|
|
83
|
+
direction: desc
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Example `dashboard_configure_kpi_card_widget` config:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
target: kpi_card
|
|
90
|
+
label: Total spend
|
|
91
|
+
size: medium
|
|
92
|
+
card:
|
|
93
|
+
title: Total spend
|
|
94
|
+
value:
|
|
95
|
+
field: spend
|
|
96
|
+
format: currency
|
|
97
|
+
query:
|
|
98
|
+
resource: llm_usage
|
|
99
|
+
select:
|
|
100
|
+
- agg: sum
|
|
101
|
+
field: cost
|
|
102
|
+
as: spend
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Example `dashboard_configure_gauge_card_widget` config:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
target: gauge_card
|
|
109
|
+
label: Budget usage
|
|
110
|
+
size: medium
|
|
111
|
+
card:
|
|
112
|
+
title: Budget usage
|
|
113
|
+
value:
|
|
114
|
+
field: spend
|
|
115
|
+
format: currency
|
|
116
|
+
progress:
|
|
117
|
+
value_field: spend
|
|
118
|
+
target_value: 1000
|
|
119
|
+
format: percent
|
|
120
|
+
query:
|
|
121
|
+
resource: llm_usage
|
|
122
|
+
select:
|
|
123
|
+
- agg: sum
|
|
124
|
+
field: cost
|
|
125
|
+
as: spend
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Example `dashboard_configure_pivot_table_widget` config:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
target: pivot_table
|
|
132
|
+
label: Spend by model and purpose
|
|
133
|
+
size: wide
|
|
134
|
+
pivot:
|
|
135
|
+
rows:
|
|
136
|
+
- field: model
|
|
137
|
+
label: Model
|
|
138
|
+
columns:
|
|
139
|
+
- field: purpose
|
|
140
|
+
label: Purpose
|
|
141
|
+
values:
|
|
142
|
+
- field: spend
|
|
143
|
+
label: Spend
|
|
144
|
+
format: currency
|
|
145
|
+
aggregation: sum
|
|
146
|
+
query:
|
|
147
|
+
resource: llm_usage
|
|
148
|
+
select:
|
|
149
|
+
- field: model
|
|
150
|
+
- field: purpose
|
|
151
|
+
- agg: sum
|
|
152
|
+
field: cost
|
|
153
|
+
as: spend
|
|
154
|
+
group_by:
|
|
155
|
+
- model
|
|
156
|
+
- purpose
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Example `dashboard_configure_line_chart_widget` config:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
target: chart
|
|
163
|
+
label: Daily spend
|
|
164
|
+
size: wide
|
|
165
|
+
chart:
|
|
166
|
+
type: line
|
|
167
|
+
x:
|
|
168
|
+
field: day
|
|
169
|
+
label: Day
|
|
170
|
+
y:
|
|
171
|
+
- field: spend
|
|
172
|
+
label: Spend
|
|
173
|
+
format: currency
|
|
174
|
+
query:
|
|
175
|
+
resource: llm_usage
|
|
176
|
+
select:
|
|
177
|
+
- field: used_at
|
|
178
|
+
grain: day
|
|
179
|
+
as: day
|
|
180
|
+
- agg: sum
|
|
181
|
+
field: cost
|
|
182
|
+
as: spend
|
|
183
|
+
group_by:
|
|
184
|
+
- field: used_at
|
|
185
|
+
grain: day
|
|
186
|
+
as: day
|
|
187
|
+
order_by:
|
|
188
|
+
- field: day
|
|
189
|
+
direction: asc
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Example `dashboard_configure_bar_chart_widget` config:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
target: chart
|
|
196
|
+
label: Spend by model
|
|
197
|
+
size: wide
|
|
198
|
+
chart:
|
|
199
|
+
type: bar
|
|
200
|
+
x:
|
|
201
|
+
field: model
|
|
202
|
+
label: Model
|
|
203
|
+
y:
|
|
204
|
+
field: spend
|
|
205
|
+
label: Spend
|
|
206
|
+
format: currency
|
|
207
|
+
query:
|
|
208
|
+
resource: llm_usage
|
|
209
|
+
select:
|
|
210
|
+
- field: model
|
|
211
|
+
- agg: sum
|
|
212
|
+
field: cost
|
|
213
|
+
as: spend
|
|
214
|
+
group_by:
|
|
215
|
+
- model
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Example `dashboard_configure_stacked_bar_chart_widget` config:
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
target: chart
|
|
222
|
+
label: Daily spend by purpose
|
|
223
|
+
size: wide
|
|
224
|
+
chart:
|
|
225
|
+
type: stacked_bar
|
|
226
|
+
x:
|
|
227
|
+
field: day
|
|
228
|
+
label: Day
|
|
229
|
+
y:
|
|
230
|
+
field: spend
|
|
231
|
+
label: Spend
|
|
232
|
+
format: currency
|
|
233
|
+
series:
|
|
234
|
+
field: purpose
|
|
235
|
+
label: Purpose
|
|
236
|
+
query:
|
|
237
|
+
resource: llm_usage
|
|
238
|
+
select:
|
|
239
|
+
- field: used_at
|
|
240
|
+
grain: day
|
|
241
|
+
as: day
|
|
242
|
+
- field: purpose
|
|
243
|
+
- agg: sum
|
|
244
|
+
field: cost
|
|
245
|
+
as: spend
|
|
246
|
+
group_by:
|
|
247
|
+
- field: used_at
|
|
248
|
+
grain: day
|
|
249
|
+
as: day
|
|
250
|
+
- purpose
|
|
251
|
+
order_by:
|
|
252
|
+
- field: day
|
|
253
|
+
direction: asc
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Example `dashboard_configure_pie_chart_widget` config:
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
target: chart
|
|
260
|
+
label: Spend share by model
|
|
261
|
+
size: medium
|
|
262
|
+
chart:
|
|
263
|
+
type: pie
|
|
264
|
+
label:
|
|
265
|
+
field: model
|
|
266
|
+
label: Model
|
|
267
|
+
value:
|
|
268
|
+
field: spend
|
|
269
|
+
label: Spend
|
|
270
|
+
format: currency
|
|
271
|
+
query:
|
|
272
|
+
resource: llm_usage
|
|
273
|
+
select:
|
|
274
|
+
- field: model
|
|
275
|
+
- agg: sum
|
|
276
|
+
field: cost
|
|
277
|
+
as: spend
|
|
278
|
+
group_by:
|
|
279
|
+
- model
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Example `dashboard_configure_histogram_chart_widget` config:
|
|
283
|
+
|
|
284
|
+
```yaml
|
|
285
|
+
target: chart
|
|
286
|
+
label: Request size distribution
|
|
287
|
+
size: wide
|
|
288
|
+
chart:
|
|
289
|
+
type: histogram
|
|
290
|
+
x:
|
|
291
|
+
field: total_tokens
|
|
292
|
+
label: Tokens
|
|
293
|
+
y:
|
|
294
|
+
field: requests
|
|
295
|
+
label: Requests
|
|
296
|
+
query:
|
|
297
|
+
resource: llm_usage
|
|
298
|
+
select:
|
|
299
|
+
- field: total_tokens
|
|
300
|
+
- agg: count
|
|
301
|
+
as: requests
|
|
302
|
+
bucket:
|
|
303
|
+
field: total_tokens
|
|
304
|
+
buckets:
|
|
305
|
+
- label: Small
|
|
306
|
+
max: 1000
|
|
307
|
+
- label: Medium
|
|
308
|
+
min: 1000
|
|
309
|
+
max: 10000
|
|
310
|
+
- label: Large
|
|
311
|
+
min: 10000
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Example `dashboard_configure_funnel_chart_widget` config:
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
target: chart
|
|
318
|
+
label: Request funnel
|
|
319
|
+
size: wide
|
|
320
|
+
chart:
|
|
321
|
+
type: funnel
|
|
322
|
+
label:
|
|
323
|
+
field: stage
|
|
324
|
+
label: Stage
|
|
325
|
+
value:
|
|
326
|
+
field: count
|
|
327
|
+
label: Count
|
|
328
|
+
query:
|
|
329
|
+
resource: llm_usage
|
|
330
|
+
select:
|
|
331
|
+
- field: stage
|
|
332
|
+
- agg: count
|
|
333
|
+
as: count
|
|
334
|
+
group_by:
|
|
335
|
+
- stage
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
|
|
45
339
|
## Group creation guard
|
|
46
340
|
|
|
47
341
|
Before creating a group, call dashboard_get_config and check existing groups.
|
|
@@ -148,6 +442,7 @@ Use target, not type.
|
|
|
148
442
|
For charts, use target: chart and chart.type for the concrete chart kind.
|
|
149
443
|
Use query, not dataSource.
|
|
150
444
|
Use resource, not resourceId.
|
|
445
|
+
Never use AdminForth routes such as /resource/llm_usage as resource or target values.
|
|
151
446
|
|
|
152
447
|
## Query shape rules
|
|
153
448
|
|
|
@@ -124,9 +124,9 @@ const label = computed(() => kpiConfig.value?.subtitle?.field
|
|
|
124
124
|
.join(': ')
|
|
125
125
|
: kpiConfig.value?.subtitle?.text ?? kpiConfig.value?.title ?? props.widget.label)
|
|
126
126
|
const formattedValue = computed(() => `${kpiConfig.value?.value.prefix ?? ''}${formatValue(value.value, kpiConfig.value?.value.format)}${kpiConfig.value?.value.suffix ?? ''}`)
|
|
127
|
-
const comparisonValue = computed(() =>
|
|
128
|
-
? firstRow.value[kpiConfig.value.comparison.field]
|
|
129
|
-
:
|
|
127
|
+
const comparisonValue = computed(() => kpiConfig.value?.comparison?.field
|
|
128
|
+
? value.value - toFiniteNumber(firstRow.value[kpiConfig.value.comparison.field])
|
|
129
|
+
: 0)
|
|
130
130
|
const comparison = computed(() => {
|
|
131
131
|
const config = kpiConfig.value?.comparison
|
|
132
132
|
|
|
@@ -120,7 +120,7 @@ const { el: rootEl, width: rootWidth } = useElementSize<HTMLDivElement>()
|
|
|
120
120
|
const { el: svgEl, width: svgWidth, height: svgHeight } = useElementSize<HTMLDivElement>()
|
|
121
121
|
|
|
122
122
|
const barGap = 10
|
|
123
|
-
const seriesNames = computed(() => Array.from(new Set(props.rows.map((row) =>
|
|
123
|
+
const seriesNames = computed(() => Array.from(new Set(props.rows.map((row) => formatSeriesLabel(row[props.seriesField])))))
|
|
124
124
|
const normalizedSeries = computed(() => seriesNames.value.map((name, index) => ({
|
|
125
125
|
name,
|
|
126
126
|
color: props.colors?.[index] || CHART_COLORS[index % CHART_COLORS.length],
|
|
@@ -141,7 +141,7 @@ const groupedRows = computed(() => {
|
|
|
141
141
|
for (const row of props.rows) {
|
|
142
142
|
const label = formatChartLabel(row[props.xField])
|
|
143
143
|
const item = grouped.get(label) ?? { [props.xField]: label }
|
|
144
|
-
const seriesName =
|
|
144
|
+
const seriesName = formatSeriesLabel(row[props.seriesField])
|
|
145
145
|
|
|
146
146
|
item[seriesName] = toFiniteNumber(item[seriesName]) + toFiniteNumber(row[props.yField])
|
|
147
147
|
|
|
@@ -240,4 +240,8 @@ function getBarTooltip(bar: { label: string, total: number, segments: Array<{ na
|
|
|
240
240
|
...segmentLines,
|
|
241
241
|
].join('\n')
|
|
242
242
|
}
|
|
243
|
+
|
|
244
|
+
function formatSeriesLabel(value: unknown) {
|
|
245
|
+
return typeof value === 'string' ? value : String(value)
|
|
246
|
+
}
|
|
243
247
|
</script>
|
|
@@ -3,6 +3,7 @@ import type { DashboardConfig } from '../custom/model/dashboard.types.js';
|
|
|
3
3
|
import type { DashboardRecord } from '../services/dashboardConfigService.js';
|
|
4
4
|
type DashboardEndpointsContext = {
|
|
5
5
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
6
|
+
getAllDashboardRecords: () => Promise<DashboardRecord[]>;
|
|
6
7
|
parseStoredDashboardConfig: (config: unknown) => DashboardConfig;
|
|
7
8
|
};
|
|
8
9
|
export declare function registerDashboardEndpoints(server: IHttpServer, ctx: DashboardEndpointsContext): void;
|
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { DashboardApiResponseSchema, SlugRequestSchema, } from '../schema/api.js';
|
|
10
|
+
import { DashboardApiResponseSchema, GetSlugsResponseSchema, SlugRequestSchema, } from '../schema/api.js';
|
|
11
11
|
export function registerDashboardEndpoints(server, ctx) {
|
|
12
12
|
server.endpoint({
|
|
13
13
|
method: 'POST',
|
|
@@ -30,4 +30,18 @@ export function registerDashboardEndpoints(server, ctx) {
|
|
|
30
30
|
};
|
|
31
31
|
}),
|
|
32
32
|
});
|
|
33
|
+
server.endpoint({
|
|
34
|
+
method: 'GET',
|
|
35
|
+
path: '/dashboard/get-slugs',
|
|
36
|
+
description: 'Returns a list of all dashboard slugs and labels for listing purposes.',
|
|
37
|
+
request_schema: undefined,
|
|
38
|
+
response_schema: GetSlugsResponseSchema,
|
|
39
|
+
handler: () => __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
const dashboards = yield ctx.getAllDashboardRecords();
|
|
41
|
+
return dashboards.map((dashboard) => ({
|
|
42
|
+
slug: dashboard.slug,
|
|
43
|
+
label: dashboard.label,
|
|
44
|
+
}));
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
33
47
|
}
|
package/dist/schema/api.d.ts
CHANGED
|
@@ -6829,6 +6829,10 @@ export declare const DashboardWidgetDataResponseZodSchema: z.ZodUnion<readonly [
|
|
|
6829
6829
|
export declare const SlugRequestZodSchema: z.ZodObject<{
|
|
6830
6830
|
slug: z.ZodString;
|
|
6831
6831
|
}, z.core.$strict>;
|
|
6832
|
+
export declare const GetSlugsResponseZodSchema: z.ZodArray<z.ZodObject<{
|
|
6833
|
+
slug: z.ZodString;
|
|
6834
|
+
label: z.ZodString;
|
|
6835
|
+
}, z.core.$strip>>;
|
|
6832
6836
|
export declare const GroupIdRequestZodSchema: z.ZodObject<{
|
|
6833
6837
|
slug: z.ZodString;
|
|
6834
6838
|
groupId: z.ZodString;
|
|
@@ -13563,6 +13567,10 @@ export declare const DashboardWidgetDataResponseSchema: z.core.ZodStandardJSONSc
|
|
|
13563
13567
|
export declare const SlugRequestSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
|
|
13564
13568
|
slug: z.ZodString;
|
|
13565
13569
|
}, z.core.$strict>>;
|
|
13570
|
+
export declare const GetSlugsResponseSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodArray<z.ZodObject<{
|
|
13571
|
+
slug: z.ZodString;
|
|
13572
|
+
label: z.ZodString;
|
|
13573
|
+
}, z.core.$strip>>>;
|
|
13566
13574
|
export declare const GroupIdRequestSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
|
|
13567
13575
|
slug: z.ZodString;
|
|
13568
13576
|
groupId: z.ZodString;
|
package/dist/schema/api.js
CHANGED
|
@@ -38,6 +38,10 @@ export const DashboardWidgetDataResponseZodSchema = z.union([
|
|
|
38
38
|
export const SlugRequestZodSchema = z.object({
|
|
39
39
|
slug: z.string(),
|
|
40
40
|
}).strict();
|
|
41
|
+
export const GetSlugsResponseZodSchema = z.array(z.object({
|
|
42
|
+
slug: z.string(),
|
|
43
|
+
label: z.string(),
|
|
44
|
+
}));
|
|
41
45
|
export const GroupIdRequestZodSchema = z.object({
|
|
42
46
|
slug: z.string(),
|
|
43
47
|
groupId: z.string(),
|
|
@@ -168,6 +172,7 @@ export const DashboardMutationResponseZodSchema = z.object({
|
|
|
168
172
|
export const DashboardApiResponseSchema = toJSONSchema(z.unknown(), { target: 'draft-07' });
|
|
169
173
|
export const DashboardWidgetDataResponseSchema = toJSONSchema(DashboardWidgetDataResponseZodSchema, { target: 'draft-07' });
|
|
170
174
|
export const SlugRequestSchema = toJSONSchema(SlugRequestZodSchema, { target: 'draft-07' });
|
|
175
|
+
export const GetSlugsResponseSchema = toJSONSchema(GetSlugsResponseZodSchema, { target: 'draft-07' });
|
|
171
176
|
export const GroupIdRequestSchema = toJSONSchema(GroupIdRequestZodSchema, { target: 'draft-07' });
|
|
172
177
|
export const MoveGroupRequestSchema = toJSONSchema(MoveGroupRequestZodSchema, { target: 'draft-07' });
|
|
173
178
|
export const SetGroupConfigRequestSchema = toJSONSchema(SetGroupConfigRequestZodSchema, { target: 'draft-07' });
|
|
@@ -17,10 +17,12 @@ export type PersistedDashboardResponse = {
|
|
|
17
17
|
};
|
|
18
18
|
type DashboardConfigMutator = (config: DashboardConfig, dashboard: DashboardRecord) => DashboardConfig | null | Promise<DashboardConfig | null>;
|
|
19
19
|
export declare function getDashboardRecord(adminforth: IAdminForth, dashboardConfigsResourceId: string, slug: string): Promise<DashboardRecord | null>;
|
|
20
|
+
export declare function getAllDashboardRecords(adminforth: IAdminForth, dashboardConfigsResourceId: string): Promise<DashboardRecord[]>;
|
|
20
21
|
export declare function persistDashboardConfig(adminforth: IAdminForth, dashboardConfigsResourceId: string, dashboard: DashboardRecord, config: DashboardConfig): Promise<PersistedDashboardResponse>;
|
|
21
22
|
export declare function updateDashboardConfig(adminforth: IAdminForth, dashboardConfigsResourceId: string, slug: string, mutateConfig: DashboardConfigMutator): Promise<PersistedDashboardResponse | null>;
|
|
22
23
|
export type DashboardConfigService = {
|
|
23
24
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
25
|
+
getAllDashboardRecords: () => Promise<DashboardRecord[]>;
|
|
24
26
|
parseStoredDashboardConfig: typeof parseStoredDashboardConfig;
|
|
25
27
|
persistDashboardConfig: (dashboard: DashboardRecord, config: DashboardConfig) => Promise<PersistedDashboardResponse>;
|
|
26
28
|
updateDashboardConfig: (slug: string, mutateConfig: DashboardConfigMutator) => Promise<PersistedDashboardResponse | null>;
|
|
@@ -57,6 +57,11 @@ export function getDashboardRecord(adminforth, dashboardConfigsResourceId, slug)
|
|
|
57
57
|
return dashboard || null;
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
+
export function getAllDashboardRecords(adminforth, dashboardConfigsResourceId) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
return yield adminforth.resource(dashboardConfigsResourceId).list([]);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
60
65
|
export function persistDashboardConfig(adminforth, dashboardConfigsResourceId, dashboard, config) {
|
|
61
66
|
return __awaiter(this, void 0, void 0, function* () {
|
|
62
67
|
const normalizedConfig = normalizeDashboardOrder(config);
|
|
@@ -103,6 +108,7 @@ export function updateDashboardConfig(adminforth, dashboardConfigsResourceId, sl
|
|
|
103
108
|
export function createDashboardConfigService(adminforth, dashboardConfigsResourceId) {
|
|
104
109
|
return {
|
|
105
110
|
getDashboardRecord: (slug) => getDashboardRecord(adminforth, dashboardConfigsResourceId, slug),
|
|
111
|
+
getAllDashboardRecords: () => getAllDashboardRecords(adminforth, dashboardConfigsResourceId),
|
|
106
112
|
parseStoredDashboardConfig,
|
|
107
113
|
persistDashboardConfig: (dashboard, config) => persistDashboardConfig(adminforth, dashboardConfigsResourceId, dashboard, config),
|
|
108
114
|
updateDashboardConfig: (slug, mutateConfig) => updateDashboardConfig(adminforth, dashboardConfigsResourceId, slug, mutateConfig),
|
package/endpoint/dashboard.ts
CHANGED
|
@@ -2,12 +2,14 @@ import type { IHttpServer } from 'adminforth';
|
|
|
2
2
|
import type { DashboardConfig } from '../custom/model/dashboard.types.js';
|
|
3
3
|
import {
|
|
4
4
|
DashboardApiResponseSchema,
|
|
5
|
+
GetSlugsResponseSchema,
|
|
5
6
|
SlugRequestSchema,
|
|
6
7
|
} from '../schema/api.js';
|
|
7
8
|
import type { DashboardRecord } from '../services/dashboardConfigService.js';
|
|
8
9
|
|
|
9
10
|
type DashboardEndpointsContext = {
|
|
10
11
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
12
|
+
getAllDashboardRecords: () => Promise<DashboardRecord[]>;
|
|
11
13
|
parseStoredDashboardConfig: (config: unknown) => DashboardConfig;
|
|
12
14
|
};
|
|
13
15
|
|
|
@@ -38,4 +40,19 @@ export function registerDashboardEndpoints(
|
|
|
38
40
|
};
|
|
39
41
|
},
|
|
40
42
|
});
|
|
43
|
+
|
|
44
|
+
server.endpoint({
|
|
45
|
+
method: 'GET',
|
|
46
|
+
path: '/dashboard/get-slugs',
|
|
47
|
+
description: 'Returns a list of all dashboard slugs and labels for listing purposes.',
|
|
48
|
+
request_schema: undefined,
|
|
49
|
+
response_schema: GetSlugsResponseSchema,
|
|
50
|
+
handler: async () => {
|
|
51
|
+
const dashboards = await ctx.getAllDashboardRecords();
|
|
52
|
+
return dashboards.map((dashboard) => ({
|
|
53
|
+
slug: dashboard.slug,
|
|
54
|
+
label: dashboard.label,
|
|
55
|
+
}));
|
|
56
|
+
},
|
|
57
|
+
});
|
|
41
58
|
}
|
package/package.json
CHANGED
package/schema/api.ts
CHANGED
|
@@ -60,6 +60,11 @@ export const SlugRequestZodSchema = z.object({
|
|
|
60
60
|
slug: z.string(),
|
|
61
61
|
}).strict()
|
|
62
62
|
|
|
63
|
+
export const GetSlugsResponseZodSchema = z.array(z.object({
|
|
64
|
+
slug: z.string(),
|
|
65
|
+
label: z.string(),
|
|
66
|
+
}))
|
|
67
|
+
|
|
63
68
|
export const GroupIdRequestZodSchema = z.object({
|
|
64
69
|
slug: z.string(),
|
|
65
70
|
groupId: z.string(),
|
|
@@ -213,6 +218,7 @@ export const DashboardMutationResponseZodSchema = z.object({
|
|
|
213
218
|
export const DashboardApiResponseSchema = toJSONSchema(z.unknown(), { target: 'draft-07' })
|
|
214
219
|
export const DashboardWidgetDataResponseSchema = toJSONSchema(DashboardWidgetDataResponseZodSchema, { target: 'draft-07' })
|
|
215
220
|
export const SlugRequestSchema = toJSONSchema(SlugRequestZodSchema, { target: 'draft-07' })
|
|
221
|
+
export const GetSlugsResponseSchema = toJSONSchema(GetSlugsResponseZodSchema, { target: 'draft-07' })
|
|
216
222
|
export const GroupIdRequestSchema = toJSONSchema(GroupIdRequestZodSchema, { target: 'draft-07' })
|
|
217
223
|
export const MoveGroupRequestSchema = toJSONSchema(MoveGroupRequestZodSchema, { target: 'draft-07' })
|
|
218
224
|
export const SetGroupConfigRequestSchema = toJSONSchema(SetGroupConfigRequestZodSchema, { target: 'draft-07' })
|
|
@@ -96,6 +96,13 @@ export async function getDashboardRecord(
|
|
|
96
96
|
return dashboard || null;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export async function getAllDashboardRecords(
|
|
100
|
+
adminforth: IAdminForth,
|
|
101
|
+
dashboardConfigsResourceId: string,
|
|
102
|
+
): Promise<DashboardRecord[]> {
|
|
103
|
+
return await adminforth.resource(dashboardConfigsResourceId).list([]);
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
export async function persistDashboardConfig(
|
|
100
107
|
adminforth: IAdminForth,
|
|
101
108
|
dashboardConfigsResourceId: string,
|
|
@@ -156,6 +163,7 @@ export async function updateDashboardConfig(
|
|
|
156
163
|
|
|
157
164
|
export type DashboardConfigService = {
|
|
158
165
|
getDashboardRecord: (slug: string) => Promise<DashboardRecord | null>;
|
|
166
|
+
getAllDashboardRecords: () => Promise<DashboardRecord[]>;
|
|
159
167
|
parseStoredDashboardConfig: typeof parseStoredDashboardConfig;
|
|
160
168
|
persistDashboardConfig: (
|
|
161
169
|
dashboard: DashboardRecord,
|
|
@@ -173,6 +181,7 @@ export function createDashboardConfigService(
|
|
|
173
181
|
): DashboardConfigService {
|
|
174
182
|
return {
|
|
175
183
|
getDashboardRecord: (slug) => getDashboardRecord(adminforth, dashboardConfigsResourceId, slug),
|
|
184
|
+
getAllDashboardRecords: () => getAllDashboardRecords(adminforth, dashboardConfigsResourceId),
|
|
176
185
|
parseStoredDashboardConfig,
|
|
177
186
|
persistDashboardConfig: (dashboard, config) => persistDashboardConfig(
|
|
178
187
|
adminforth,
|