@adminforth/dashboard 1.9.0 → 1.11.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/custom/api/dashboardApi.ts +73 -36
- package/custom/runtime/DashboardRuntime.vue +26 -22
- package/custom/skills/adminforth-dashboard/SKILL.md +305 -10
- package/dist/custom/api/dashboardApi.d.ts +24 -18
- package/dist/custom/api/dashboardApi.js +42 -18
- package/dist/custom/api/dashboardApi.ts +73 -36
- package/dist/custom/runtime/DashboardRuntime.vue +26 -22
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +305 -10
- package/dist/endpoint/dashboard.d.ts +1 -0
- package/dist/endpoint/dashboard.js +15 -1
- package/dist/endpoint/groups.js +22 -20
- package/dist/endpoint/widgets.js +28 -26
- package/dist/schema/api.d.ts +151 -1869
- package/dist/schema/api.js +12 -12
- package/dist/services/calc-evaluator.d.ts +2 -1
- package/dist/services/calc-evaluator.js +29 -3
- package/dist/services/dashboardConfigService.d.ts +2 -0
- package/dist/services/dashboardConfigService.js +6 -0
- package/dist/services/widgetDataService.js +2 -2
- package/endpoint/dashboard.ts +17 -0
- package/endpoint/groups.ts +22 -20
- package/endpoint/widgets.ts +28 -26
- package/package.json +1 -1
- package/schema/api.ts +13 -12
- package/services/calc-evaluator.ts +41 -3
- package/services/dashboardConfigService.ts +9 -0
- package/services/widgetDataService.ts +2 -2
|
@@ -25,6 +25,13 @@ export type DashboardWidgetDataResponse = {
|
|
|
25
25
|
data: unknown
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export type DashboardMutationResponse = {
|
|
29
|
+
ok: boolean
|
|
30
|
+
error?: string
|
|
31
|
+
groupId?: string
|
|
32
|
+
widgetId?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
export type DashboardWidgetDataRequest = {
|
|
29
36
|
pagination?: {
|
|
30
37
|
page: number
|
|
@@ -119,6 +126,36 @@ async function callDashboardApi(path: string, body: Record<string, unknown>): Pr
|
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
128
|
|
|
129
|
+
async function callDashboardMutationApi(path: string, body: Record<string, unknown>): Promise<DashboardMutationResponse> {
|
|
130
|
+
const rawResponse = await fetch(path, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'accept-language': localStorage.getItem('af_lang') || 'en',
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify(body),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const response = await parseDashboardResponse(rawResponse)
|
|
140
|
+
|
|
141
|
+
if (!rawResponse.ok) {
|
|
142
|
+
throw new DashboardApiError(
|
|
143
|
+
response?.error || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`,
|
|
144
|
+
normalizeValidationErrors(response),
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!response || response.error || response.ok === false) {
|
|
149
|
+
throw new DashboardApiError(response?.error || 'Dashboard request failed', normalizeValidationErrors(response))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
groupId: response.groupId,
|
|
155
|
+
widgetId: response.widgetId,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
122
159
|
async function callDashboardWidgetDataApi(
|
|
123
160
|
path: string,
|
|
124
161
|
body: Record<string, unknown>,
|
|
@@ -156,39 +193,39 @@ export const dashboardApi = {
|
|
|
156
193
|
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
|
|
157
194
|
},
|
|
158
195
|
|
|
159
|
-
async addDashboardGroup(slug: string): Promise<
|
|
160
|
-
return
|
|
196
|
+
async addDashboardGroup(slug: string): Promise<DashboardMutationResponse> {
|
|
197
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
|
|
161
198
|
},
|
|
162
199
|
|
|
163
200
|
async moveDashboardGroup(
|
|
164
201
|
slug: string,
|
|
165
202
|
groupId: string,
|
|
166
203
|
direction: DashboardGroupMoveDirection,
|
|
167
|
-
): Promise<
|
|
168
|
-
return
|
|
204
|
+
): Promise<DashboardMutationResponse> {
|
|
205
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/move_dashboard_group', {
|
|
169
206
|
slug,
|
|
170
207
|
groupId,
|
|
171
208
|
direction,
|
|
172
209
|
})
|
|
173
210
|
},
|
|
174
211
|
|
|
175
|
-
async removeDashboardGroup(slug: string, groupId: string): Promise<
|
|
176
|
-
return
|
|
212
|
+
async removeDashboardGroup(slug: string, groupId: string): Promise<DashboardMutationResponse> {
|
|
213
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/remove_dashboard_group', {
|
|
177
214
|
slug,
|
|
178
215
|
groupId,
|
|
179
216
|
})
|
|
180
217
|
},
|
|
181
218
|
|
|
182
|
-
async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<
|
|
183
|
-
return
|
|
219
|
+
async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<DashboardMutationResponse> {
|
|
220
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
|
|
184
221
|
slug,
|
|
185
222
|
groupId,
|
|
186
223
|
config,
|
|
187
224
|
})
|
|
188
225
|
},
|
|
189
226
|
|
|
190
|
-
async addDashboardWidget(slug: string, groupId: string): Promise<
|
|
191
|
-
return
|
|
227
|
+
async addDashboardWidget(slug: string, groupId: string): Promise<DashboardMutationResponse> {
|
|
228
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/add_dashboard_widget', {
|
|
192
229
|
slug,
|
|
193
230
|
groupId,
|
|
194
231
|
})
|
|
@@ -198,23 +235,23 @@ export const dashboardApi = {
|
|
|
198
235
|
slug: string,
|
|
199
236
|
widgetId: string,
|
|
200
237
|
direction: DashboardWidgetMoveDirection,
|
|
201
|
-
): Promise<
|
|
202
|
-
return
|
|
238
|
+
): Promise<DashboardMutationResponse> {
|
|
239
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/move_dashboard_widget', {
|
|
203
240
|
slug,
|
|
204
241
|
widgetId,
|
|
205
242
|
direction,
|
|
206
243
|
})
|
|
207
244
|
},
|
|
208
245
|
|
|
209
|
-
async removeDashboardWidget(slug: string, widgetId: string): Promise<
|
|
210
|
-
return
|
|
246
|
+
async removeDashboardWidget(slug: string, widgetId: string): Promise<DashboardMutationResponse> {
|
|
247
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/remove_dashboard_widget', {
|
|
211
248
|
slug,
|
|
212
249
|
widgetId,
|
|
213
250
|
})
|
|
214
251
|
},
|
|
215
252
|
|
|
216
|
-
async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<
|
|
217
|
-
return
|
|
253
|
+
async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<DashboardMutationResponse> {
|
|
254
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/set_widget_config', {
|
|
218
255
|
slug,
|
|
219
256
|
widgetId,
|
|
220
257
|
config,
|
|
@@ -225,8 +262,8 @@ export const dashboardApi = {
|
|
|
225
262
|
slug: string,
|
|
226
263
|
widgetId: string,
|
|
227
264
|
config: ConfigurableTableWidgetConfig,
|
|
228
|
-
): Promise<
|
|
229
|
-
return
|
|
265
|
+
): Promise<DashboardMutationResponse> {
|
|
266
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_table_widget', {
|
|
230
267
|
slug,
|
|
231
268
|
widgetId,
|
|
232
269
|
config,
|
|
@@ -237,8 +274,8 @@ export const dashboardApi = {
|
|
|
237
274
|
slug: string,
|
|
238
275
|
widgetId: string,
|
|
239
276
|
config: ConfigurableKpiCardWidgetConfig,
|
|
240
|
-
): Promise<
|
|
241
|
-
return
|
|
277
|
+
): Promise<DashboardMutationResponse> {
|
|
278
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_kpi_card_widget', {
|
|
242
279
|
slug,
|
|
243
280
|
widgetId,
|
|
244
281
|
config,
|
|
@@ -249,8 +286,8 @@ export const dashboardApi = {
|
|
|
249
286
|
slug: string,
|
|
250
287
|
widgetId: string,
|
|
251
288
|
config: ConfigurableGaugeCardWidgetConfig,
|
|
252
|
-
): Promise<
|
|
253
|
-
return
|
|
289
|
+
): Promise<DashboardMutationResponse> {
|
|
290
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_gauge_card_widget', {
|
|
254
291
|
slug,
|
|
255
292
|
widgetId,
|
|
256
293
|
config,
|
|
@@ -261,8 +298,8 @@ export const dashboardApi = {
|
|
|
261
298
|
slug: string,
|
|
262
299
|
widgetId: string,
|
|
263
300
|
config: ConfigurableLineChartWidgetConfig,
|
|
264
|
-
): Promise<
|
|
265
|
-
return
|
|
301
|
+
): Promise<DashboardMutationResponse> {
|
|
302
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_line_chart_widget', {
|
|
266
303
|
slug,
|
|
267
304
|
widgetId,
|
|
268
305
|
config,
|
|
@@ -273,8 +310,8 @@ export const dashboardApi = {
|
|
|
273
310
|
slug: string,
|
|
274
311
|
widgetId: string,
|
|
275
312
|
config: ConfigurableBarChartWidgetConfig,
|
|
276
|
-
): Promise<
|
|
277
|
-
return
|
|
313
|
+
): Promise<DashboardMutationResponse> {
|
|
314
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_bar_chart_widget', {
|
|
278
315
|
slug,
|
|
279
316
|
widgetId,
|
|
280
317
|
config,
|
|
@@ -285,8 +322,8 @@ export const dashboardApi = {
|
|
|
285
322
|
slug: string,
|
|
286
323
|
widgetId: string,
|
|
287
324
|
config: ConfigurableStackedBarChartWidgetConfig,
|
|
288
|
-
): Promise<
|
|
289
|
-
return
|
|
325
|
+
): Promise<DashboardMutationResponse> {
|
|
326
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_stacked_bar_chart_widget', {
|
|
290
327
|
slug,
|
|
291
328
|
widgetId,
|
|
292
329
|
config,
|
|
@@ -297,8 +334,8 @@ export const dashboardApi = {
|
|
|
297
334
|
slug: string,
|
|
298
335
|
widgetId: string,
|
|
299
336
|
config: ConfigurablePieChartWidgetConfig,
|
|
300
|
-
): Promise<
|
|
301
|
-
return
|
|
337
|
+
): Promise<DashboardMutationResponse> {
|
|
338
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_pie_chart_widget', {
|
|
302
339
|
slug,
|
|
303
340
|
widgetId,
|
|
304
341
|
config,
|
|
@@ -309,8 +346,8 @@ export const dashboardApi = {
|
|
|
309
346
|
slug: string,
|
|
310
347
|
widgetId: string,
|
|
311
348
|
config: ConfigurableHistogramChartWidgetConfig,
|
|
312
|
-
): Promise<
|
|
313
|
-
return
|
|
349
|
+
): Promise<DashboardMutationResponse> {
|
|
350
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_histogram_chart_widget', {
|
|
314
351
|
slug,
|
|
315
352
|
widgetId,
|
|
316
353
|
config,
|
|
@@ -321,8 +358,8 @@ export const dashboardApi = {
|
|
|
321
358
|
slug: string,
|
|
322
359
|
widgetId: string,
|
|
323
360
|
config: ConfigurableFunnelChartWidgetConfig,
|
|
324
|
-
): Promise<
|
|
325
|
-
return
|
|
361
|
+
): Promise<DashboardMutationResponse> {
|
|
362
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_funnel_chart_widget', {
|
|
326
363
|
slug,
|
|
327
364
|
widgetId,
|
|
328
365
|
config,
|
|
@@ -333,8 +370,8 @@ export const dashboardApi = {
|
|
|
333
370
|
slug: string,
|
|
334
371
|
widgetId: string,
|
|
335
372
|
config: ConfigurablePivotTableWidgetConfig,
|
|
336
|
-
): Promise<
|
|
337
|
-
return
|
|
373
|
+
): Promise<DashboardMutationResponse> {
|
|
374
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_pivot_table_widget', {
|
|
338
375
|
slug,
|
|
339
376
|
widgetId,
|
|
340
377
|
config,
|
|
@@ -286,7 +286,8 @@ async function addGroup() {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
try {
|
|
289
|
-
|
|
289
|
+
await dashboardApi.addDashboardGroup(props.dashboardSlug)
|
|
290
|
+
await refreshDashboardConfig()
|
|
290
291
|
} catch (error) {
|
|
291
292
|
console.error('Failed to add dashboard group', error)
|
|
292
293
|
}
|
|
@@ -298,7 +299,8 @@ async function addWidget(groupId: string) {
|
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
try {
|
|
301
|
-
|
|
302
|
+
await dashboardApi.addDashboardWidget(props.dashboardSlug, groupId)
|
|
303
|
+
await refreshDashboardConfig()
|
|
302
304
|
} catch (error) {
|
|
303
305
|
console.error('Failed to add dashboard widget', error)
|
|
304
306
|
}
|
|
@@ -310,9 +312,8 @@ async function moveGroup(groupId: string, direction: DashboardGroupMoveDirection
|
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
try {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
315
|
+
await dashboardApi.moveDashboardGroup(props.dashboardSlug, groupId, direction)
|
|
316
|
+
await refreshDashboardConfig()
|
|
316
317
|
} catch (error) {
|
|
317
318
|
console.error('Failed to move dashboard group', error)
|
|
318
319
|
}
|
|
@@ -324,7 +325,8 @@ async function removeGroup(groupId: string) {
|
|
|
324
325
|
}
|
|
325
326
|
|
|
326
327
|
try {
|
|
327
|
-
|
|
328
|
+
await dashboardApi.removeDashboardGroup(props.dashboardSlug, groupId)
|
|
329
|
+
await refreshDashboardConfig()
|
|
328
330
|
} catch (error) {
|
|
329
331
|
console.error('Failed to remove dashboard group', error)
|
|
330
332
|
}
|
|
@@ -348,13 +350,12 @@ async function saveGroupConfig() {
|
|
|
348
350
|
try {
|
|
349
351
|
const groupConfig = parseYaml(groupConfigCode.value) as EditableDashboardGroupConfig
|
|
350
352
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
groupConfig,
|
|
356
|
-
),
|
|
353
|
+
await dashboardApi.setDashboardGroupConfig(
|
|
354
|
+
props.dashboardSlug,
|
|
355
|
+
editingGroupId.value,
|
|
356
|
+
groupConfig,
|
|
357
357
|
)
|
|
358
|
+
await refreshDashboardConfig()
|
|
358
359
|
closeGroupConfigEditor()
|
|
359
360
|
} catch (error) {
|
|
360
361
|
groupConfigError.value = error instanceof Error ? error.message : 'Invalid group config'
|
|
@@ -373,9 +374,8 @@ async function moveWidget(widgetId: string, direction: DashboardWidgetMoveDirect
|
|
|
373
374
|
}
|
|
374
375
|
|
|
375
376
|
try {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
)
|
|
377
|
+
await dashboardApi.moveDashboardWidget(props.dashboardSlug, widgetId, direction)
|
|
378
|
+
await refreshDashboardConfig()
|
|
379
379
|
} catch (error) {
|
|
380
380
|
console.error('Failed to move dashboard widget', error)
|
|
381
381
|
}
|
|
@@ -387,7 +387,8 @@ async function removeWidget(widgetId: string) {
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
try {
|
|
390
|
-
|
|
390
|
+
await dashboardApi.removeDashboardWidget(props.dashboardSlug, widgetId)
|
|
391
|
+
await refreshDashboardConfig()
|
|
391
392
|
} catch (error) {
|
|
392
393
|
console.error('Failed to remove dashboard widget', error)
|
|
393
394
|
}
|
|
@@ -410,13 +411,12 @@ async function saveWidgetConfig() {
|
|
|
410
411
|
widgetConfigFieldErrors.value = []
|
|
411
412
|
const widgetConfig = parseYaml(widgetConfigCode.value) as DashboardWidgetConfig
|
|
412
413
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
serializeDashboardWidgetConfigForEditor(widgetConfig),
|
|
418
|
-
),
|
|
414
|
+
await dashboardApi.setWidgetConfig(
|
|
415
|
+
props.dashboardSlug,
|
|
416
|
+
editingWidgetId.value,
|
|
417
|
+
serializeDashboardWidgetConfigForEditor(widgetConfig),
|
|
419
418
|
)
|
|
419
|
+
await refreshDashboardConfig()
|
|
420
420
|
closeWidgetConfigEditor()
|
|
421
421
|
} catch (error) {
|
|
422
422
|
widgetConfigError.value = error instanceof Error ? error.message : 'Invalid widget config'
|
|
@@ -431,6 +431,10 @@ function closeWidgetConfigEditor() {
|
|
|
431
431
|
widgetConfigFieldErrors.value = []
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
async function refreshDashboardConfig() {
|
|
435
|
+
applyDashboardResponse(await dashboardApi.getDashboardConfig(props.dashboardSlug))
|
|
436
|
+
}
|
|
437
|
+
|
|
434
438
|
function applyDashboardResponse(response: DashboardResponse) {
|
|
435
439
|
draftConfig.value = cloneConfig(response.config)
|
|
436
440
|
currentRevision.value = response.revision
|
|
@@ -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
|
|