@adminforth/dashboard 1.11.2 → 1.12.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 +43 -2
- package/custom/api/dashboardApi.ts +72 -10
- package/custom/model/dashboard.types.ts +6 -0
- package/custom/runtime/DashboardGroup.vue +15 -68
- package/custom/runtime/DashboardToolbarButton.vue +32 -0
- package/custom/runtime/DashboardToolbarIcon.vue +52 -0
- package/custom/runtime/WidgetShell.vue +15 -68
- package/custom/skills/adminforth-dashboard/SKILL.md +63 -13
- package/custom/widgets/chart/StackedBarChart.vue +64 -7
- package/dist/custom/api/dashboardApi.js +59 -10
- package/dist/custom/api/dashboardApi.ts +72 -10
- package/dist/custom/model/dashboard.types.d.ts +9 -0
- package/dist/custom/model/dashboard.types.ts +6 -0
- package/dist/custom/queries/useDashboardConfig.d.ts +80 -0
- package/dist/custom/queries/useWidgetData.d.ts +80 -0
- package/dist/custom/runtime/DashboardGroup.vue +15 -68
- package/dist/custom/runtime/DashboardToolbarButton.vue +32 -0
- package/dist/custom/runtime/DashboardToolbarIcon.vue +52 -0
- package/dist/custom/runtime/WidgetShell.vue +15 -68
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +63 -13
- package/dist/custom/widgets/chart/StackedBarChart.vue +64 -7
- package/dist/schema/api.d.ts +742 -802
- package/dist/schema/api.js +2 -2
- package/dist/schema/widget.d.ts +75 -81
- package/dist/schema/widget.js +1 -1
- package/dist/schema/widgets/charts.d.ts +84 -160
- package/dist/schema/widgets/charts.js +2 -2
- package/dist/schema/widgets/common.d.ts +115 -0
- package/dist/schema/widgets/common.js +17 -1
- package/dist/schema/widgets/gauge-card.d.ts +8 -0
- package/dist/schema/widgets/kpi-card.d.ts +8 -0
- package/dist/schema/widgets/pivot-table.d.ts +8 -0
- package/dist/schema/widgets/table.d.ts +8 -0
- package/dist/services/widgetDataService.js +42 -0
- package/package.json +2 -2
- package/schema/api.ts +2 -1
- package/schema/widget.ts +2 -0
- package/schema/widgets/charts.ts +2 -1
- package/schema/widgets/common.ts +19 -1
- package/services/widgetDataService.ts +68 -0
|
@@ -14,91 +14,36 @@
|
|
|
14
14
|
v-if="isAdmin"
|
|
15
15
|
class="absolute right-3 top-3 flex gap-1 opacity-0 transition-opacity group-hover/dashboard:opacity-100"
|
|
16
16
|
>
|
|
17
|
-
<
|
|
18
|
-
type="button"
|
|
19
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightListViewButtonBorder bg-lightListViewButtonBackground text-lightListViewButtonText shadow-sm hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover dark:border-darkListViewButtonBorder dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:hover:bg-darkListViewButtonBackgroundHover dark:hover:text-darkListViewButtonTextHover"
|
|
17
|
+
<DashboardToolbarButton
|
|
20
18
|
title="Edit JSON"
|
|
21
19
|
@click="emit('edit-group', group)"
|
|
22
20
|
>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
viewBox="0 0 24 24"
|
|
26
|
-
fill="none"
|
|
27
|
-
stroke="currentColor"
|
|
28
|
-
stroke-width="1.8"
|
|
29
|
-
stroke-linecap="round"
|
|
30
|
-
stroke-linejoin="round"
|
|
31
|
-
aria-hidden="true"
|
|
32
|
-
>
|
|
33
|
-
<path d="M15.5 7.5a3 3 0 1 1 1 2.2l-6.8 6.8H7.5v2.2H5.3v2.2H2.8v-2.5l7.5-7.5a5.5 5.5 0 1 1 5.2 1.6" />
|
|
34
|
-
</svg>
|
|
35
|
-
</button>
|
|
21
|
+
<DashboardToolbarIcon name="edit" />
|
|
22
|
+
</DashboardToolbarButton>
|
|
36
23
|
|
|
37
|
-
<
|
|
38
|
-
type="button"
|
|
39
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightListViewButtonBorder bg-lightListViewButtonBackground text-lightListViewButtonText shadow-sm hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover disabled:opacity-45 dark:border-darkListViewButtonBorder dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:hover:bg-darkListViewButtonBackgroundHover dark:hover:text-darkListViewButtonTextHover"
|
|
24
|
+
<DashboardToolbarButton
|
|
40
25
|
title="Move up"
|
|
41
26
|
:disabled="!canMoveUp"
|
|
42
27
|
@click="emit('move-up')"
|
|
43
28
|
>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
viewBox="0 0 24 24"
|
|
47
|
-
fill="none"
|
|
48
|
-
stroke="currentColor"
|
|
49
|
-
stroke-width="2"
|
|
50
|
-
stroke-linecap="round"
|
|
51
|
-
stroke-linejoin="round"
|
|
52
|
-
aria-hidden="true"
|
|
53
|
-
>
|
|
54
|
-
<path d="m18 15-6-6-6 6" />
|
|
55
|
-
</svg>
|
|
56
|
-
</button>
|
|
29
|
+
<DashboardToolbarIcon name="move-up" />
|
|
30
|
+
</DashboardToolbarButton>
|
|
57
31
|
|
|
58
|
-
<
|
|
59
|
-
type="button"
|
|
60
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightListViewButtonBorder bg-lightListViewButtonBackground text-lightListViewButtonText shadow-sm hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover disabled:opacity-45 dark:border-darkListViewButtonBorder dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:hover:bg-darkListViewButtonBackgroundHover dark:hover:text-darkListViewButtonTextHover"
|
|
32
|
+
<DashboardToolbarButton
|
|
61
33
|
title="Move down"
|
|
62
34
|
:disabled="!canMoveDown"
|
|
63
35
|
@click="emit('move-down')"
|
|
64
36
|
>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
viewBox="0 0 24 24"
|
|
68
|
-
fill="none"
|
|
69
|
-
stroke="currentColor"
|
|
70
|
-
stroke-width="2"
|
|
71
|
-
stroke-linecap="round"
|
|
72
|
-
stroke-linejoin="round"
|
|
73
|
-
aria-hidden="true"
|
|
74
|
-
>
|
|
75
|
-
<path d="m6 9 6 6 6-6" />
|
|
76
|
-
</svg>
|
|
77
|
-
</button>
|
|
37
|
+
<DashboardToolbarIcon name="move-down" />
|
|
38
|
+
</DashboardToolbarButton>
|
|
78
39
|
|
|
79
|
-
<
|
|
80
|
-
type="button"
|
|
81
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightInputErrorColor/30 bg-lightSecondary text-lightInputErrorColor shadow-sm hover:bg-lightListViewButtonBackgroundHover dark:bg-darkSecondary dark:hover:bg-darkListViewButtonBackgroundHover"
|
|
40
|
+
<DashboardToolbarButton
|
|
82
41
|
title="Remove"
|
|
42
|
+
variant="danger"
|
|
83
43
|
@click="emit('remove-group')"
|
|
84
44
|
>
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
viewBox="0 0 24 24"
|
|
88
|
-
fill="none"
|
|
89
|
-
stroke="currentColor"
|
|
90
|
-
stroke-width="2"
|
|
91
|
-
stroke-linecap="round"
|
|
92
|
-
stroke-linejoin="round"
|
|
93
|
-
aria-hidden="true"
|
|
94
|
-
>
|
|
95
|
-
<path d="M3 6h18" />
|
|
96
|
-
<path d="M8 6V4h8v2" />
|
|
97
|
-
<path d="M19 6l-1 14H6L5 6" />
|
|
98
|
-
<path d="M10 11v5" />
|
|
99
|
-
<path d="M14 11v5" />
|
|
100
|
-
</svg>
|
|
101
|
-
</button>
|
|
45
|
+
<DashboardToolbarIcon name="remove" />
|
|
46
|
+
</DashboardToolbarButton>
|
|
102
47
|
</div>
|
|
103
48
|
</header>
|
|
104
49
|
|
|
@@ -158,6 +103,8 @@
|
|
|
158
103
|
|
|
159
104
|
<script setup lang="ts">
|
|
160
105
|
import { Button } from '@/afcl'
|
|
106
|
+
import DashboardToolbarButton from './DashboardToolbarButton.vue'
|
|
107
|
+
import DashboardToolbarIcon from './DashboardToolbarIcon.vue'
|
|
161
108
|
import WidgetRenderer from './WidgetRenderer.vue'
|
|
162
109
|
import WidgetShell from './WidgetShell.vue'
|
|
163
110
|
import type { DashboardGroupConfig, DashboardWidgetConfig } from '../model/dashboard.types.js'
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class="flex h-8 w-8 items-center justify-center rounded-lg border shadow-sm"
|
|
5
|
+
:class="buttonClass"
|
|
6
|
+
:title="title"
|
|
7
|
+
:disabled="disabled"
|
|
8
|
+
>
|
|
9
|
+
<slot />
|
|
10
|
+
</button>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { computed } from 'vue'
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(defineProps<{
|
|
17
|
+
title: string
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
variant?: 'default' | 'danger'
|
|
20
|
+
}>(), {
|
|
21
|
+
disabled: false,
|
|
22
|
+
variant: 'default',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const buttonClass = computed(() => {
|
|
26
|
+
if (props.variant === 'danger') {
|
|
27
|
+
return 'border-lightInputErrorColor/30 bg-lightSecondary text-lightInputErrorColor hover:bg-lightListViewButtonBackgroundHover dark:bg-darkSecondary dark:hover:bg-darkListViewButtonBackgroundHover'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return 'border-lightListViewButtonBorder bg-lightListViewButtonBackground text-lightListViewButtonText hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover disabled:opacity-45 dark:border-darkListViewButtonBorder dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:hover:bg-darkListViewButtonBackgroundHover dark:hover:text-darkListViewButtonTextHover'
|
|
31
|
+
})
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<svg
|
|
3
|
+
v-if="name === 'edit'"
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="16"
|
|
6
|
+
height="16"
|
|
7
|
+
viewBox="0 0 90 90"
|
|
8
|
+
fill="none"
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
>
|
|
11
|
+
<g transform="translate(90 0) scale(-1 1)">
|
|
12
|
+
<path
|
|
13
|
+
fill="currentColor"
|
|
14
|
+
d="M69.243 90c-5.389 0-10.67-2.107-14.643-6.081-5.433-5.432-7.391-13.514-5.115-20.831L26.912 40.515c-7.313 2.276-15.397.32-20.832-5.114C.147 29.468-1.625 20.617 1.566 12.852l.846-2.059 12.493 12.493c2.311 2.31 6.07 2.311 8.381 0 2.31-2.311 2.31-6.071 0-8.381L10.794 2.413l2.059-.846c7.766-3.191 16.616-1.418 22.549 4.514 5.433 5.433 7.39 13.516 5.114 20.831l22.572 22.573c7.314-2.278 15.398-.32 20.832 5.114 5.934 5.933 7.704 14.784 4.514 22.549l-.847 2.059-12.492-12.493c-1.113-1.113-2.601-1.726-4.191-1.726s-3.077.613-4.191 1.726c-2.31 2.311-2.31 6.071 0 8.381l12.493 12.492-2.06.847C74.582 89.487 71.899 89.999 69.243 90zM27.692 37.1l25.206 25.207-.322.887c-2.345 6.469-.728 13.779 4.119 18.626 4.538 4.538 11.069 6.235 17.152 4.603l-9.232-9.232c-3.466-3.467-3.466-9.109 0-12.576 3.466-3.468 9.109-3.468 12.576 0l9.232 9.232c1.633-6.083-.064-12.615-4.602-17.153-4.847-4.847-12.161-6.464-18.627-4.118l-.887.322L37.101 27.692l.322-.887c2.345-6.469.729-13.78-4.118-18.627-4.539-4.538-11.07-6.235-17.152-4.603l9.232 9.232c3.467 3.467 3.467 9.109 0 12.576s-9.109 3.468-12.576 0l-9.233-9.232c-1.634 6.083.064 12.614 4.602 17.153 4.848 4.848 12.16 6.464 18.628 4.118l.886-.322z"
|
|
15
|
+
/>
|
|
16
|
+
</g>
|
|
17
|
+
</svg>
|
|
18
|
+
|
|
19
|
+
<svg
|
|
20
|
+
v-else
|
|
21
|
+
class="h-4 w-4"
|
|
22
|
+
viewBox="0 0 24 24"
|
|
23
|
+
fill="none"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
stroke-width="2"
|
|
26
|
+
stroke-linecap="round"
|
|
27
|
+
stroke-linejoin="round"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
>
|
|
30
|
+
<path
|
|
31
|
+
v-if="name === 'move-up'"
|
|
32
|
+
d="m18 15-6-6-6 6"
|
|
33
|
+
/>
|
|
34
|
+
<path
|
|
35
|
+
v-else-if="name === 'move-down'"
|
|
36
|
+
d="m6 9 6 6 6-6"
|
|
37
|
+
/>
|
|
38
|
+
<template v-else>
|
|
39
|
+
<path d="M3 6h18" />
|
|
40
|
+
<path d="M8 6V4h8v2" />
|
|
41
|
+
<path d="M19 6l-1 14H6L5 6" />
|
|
42
|
+
<path d="M10 11v5" />
|
|
43
|
+
<path d="M14 11v5" />
|
|
44
|
+
</template>
|
|
45
|
+
</svg>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
defineProps<{
|
|
50
|
+
name: 'edit' | 'move-up' | 'move-down' | 'remove'
|
|
51
|
+
}>()
|
|
52
|
+
</script>
|
|
@@ -10,91 +10,36 @@
|
|
|
10
10
|
v-if="isAdmin"
|
|
11
11
|
class="absolute right-2 top-2 flex gap-1 opacity-0 transition-opacity group-hover:opacity-100"
|
|
12
12
|
>
|
|
13
|
-
<
|
|
14
|
-
type="button"
|
|
15
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightListViewButtonBorder bg-lightListViewButtonBackground text-lightListViewButtonText shadow-sm hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover dark:border-darkListViewButtonBorder dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:hover:bg-darkListViewButtonBackgroundHover dark:hover:text-darkListViewButtonTextHover"
|
|
13
|
+
<DashboardToolbarButton
|
|
16
14
|
title="Edit JSON"
|
|
17
15
|
@click="emit('edit')"
|
|
18
16
|
>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
viewBox="0 0 24 24"
|
|
22
|
-
fill="none"
|
|
23
|
-
stroke="currentColor"
|
|
24
|
-
stroke-width="1.8"
|
|
25
|
-
stroke-linecap="round"
|
|
26
|
-
stroke-linejoin="round"
|
|
27
|
-
aria-hidden="true"
|
|
28
|
-
>
|
|
29
|
-
<path d="M15.5 7.5a3 3 0 1 1 1 2.2l-6.8 6.8H7.5v2.2H5.3v2.2H2.8v-2.5l7.5-7.5a5.5 5.5 0 1 1 5.2 1.6" />
|
|
30
|
-
</svg>
|
|
31
|
-
</button>
|
|
17
|
+
<DashboardToolbarIcon name="edit" />
|
|
18
|
+
</DashboardToolbarButton>
|
|
32
19
|
|
|
33
|
-
<
|
|
34
|
-
type="button"
|
|
35
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightListViewButtonBorder bg-lightListViewButtonBackground text-lightListViewButtonText shadow-sm hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover disabled:opacity-45 dark:border-darkListViewButtonBorder dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:hover:bg-darkListViewButtonBackgroundHover dark:hover:text-darkListViewButtonTextHover"
|
|
20
|
+
<DashboardToolbarButton
|
|
36
21
|
title="Move up"
|
|
37
22
|
:disabled="!canMoveUp"
|
|
38
23
|
@click="emit('move-up')"
|
|
39
24
|
>
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
viewBox="0 0 24 24"
|
|
43
|
-
fill="none"
|
|
44
|
-
stroke="currentColor"
|
|
45
|
-
stroke-width="2"
|
|
46
|
-
stroke-linecap="round"
|
|
47
|
-
stroke-linejoin="round"
|
|
48
|
-
aria-hidden="true"
|
|
49
|
-
>
|
|
50
|
-
<path d="m18 15-6-6-6 6" />
|
|
51
|
-
</svg>
|
|
52
|
-
</button>
|
|
25
|
+
<DashboardToolbarIcon name="move-up" />
|
|
26
|
+
</DashboardToolbarButton>
|
|
53
27
|
|
|
54
|
-
<
|
|
55
|
-
type="button"
|
|
56
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightListViewButtonBorder bg-lightListViewButtonBackground text-lightListViewButtonText shadow-sm hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover disabled:opacity-45 dark:border-darkListViewButtonBorder dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:hover:bg-darkListViewButtonBackgroundHover dark:hover:text-darkListViewButtonTextHover"
|
|
28
|
+
<DashboardToolbarButton
|
|
57
29
|
title="Move down"
|
|
58
30
|
:disabled="!canMoveDown"
|
|
59
31
|
@click="emit('move-down')"
|
|
60
32
|
>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
viewBox="0 0 24 24"
|
|
64
|
-
fill="none"
|
|
65
|
-
stroke="currentColor"
|
|
66
|
-
stroke-width="2"
|
|
67
|
-
stroke-linecap="round"
|
|
68
|
-
stroke-linejoin="round"
|
|
69
|
-
aria-hidden="true"
|
|
70
|
-
>
|
|
71
|
-
<path d="m6 9 6 6 6-6" />
|
|
72
|
-
</svg>
|
|
73
|
-
</button>
|
|
33
|
+
<DashboardToolbarIcon name="move-down" />
|
|
34
|
+
</DashboardToolbarButton>
|
|
74
35
|
|
|
75
|
-
<
|
|
76
|
-
type="button"
|
|
77
|
-
class="flex h-8 w-8 items-center justify-center rounded-lg border border-lightInputErrorColor/30 bg-lightSecondary text-lightInputErrorColor shadow-sm hover:bg-lightListViewButtonBackgroundHover dark:bg-darkSecondary dark:hover:bg-darkListViewButtonBackgroundHover"
|
|
36
|
+
<DashboardToolbarButton
|
|
78
37
|
title="Remove"
|
|
38
|
+
variant="danger"
|
|
79
39
|
@click="emit('remove')"
|
|
80
40
|
>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
viewBox="0 0 24 24"
|
|
84
|
-
fill="none"
|
|
85
|
-
stroke="currentColor"
|
|
86
|
-
stroke-width="2"
|
|
87
|
-
stroke-linecap="round"
|
|
88
|
-
stroke-linejoin="round"
|
|
89
|
-
aria-hidden="true"
|
|
90
|
-
>
|
|
91
|
-
<path d="M3 6h18" />
|
|
92
|
-
<path d="M8 6V4h8v2" />
|
|
93
|
-
<path d="M19 6l-1 14H6L5 6" />
|
|
94
|
-
<path d="M10 11v5" />
|
|
95
|
-
<path d="M14 11v5" />
|
|
96
|
-
</svg>
|
|
97
|
-
</button>
|
|
41
|
+
<DashboardToolbarIcon name="remove" />
|
|
42
|
+
</DashboardToolbarButton>
|
|
98
43
|
</div>
|
|
99
44
|
</div>
|
|
100
45
|
</template>
|
|
@@ -105,6 +50,8 @@
|
|
|
105
50
|
import { computed } from 'vue'
|
|
106
51
|
import type { CSSProperties } from 'vue'
|
|
107
52
|
import type { WidgetLayout } from '../model/dashboard.types.js'
|
|
53
|
+
import DashboardToolbarButton from './DashboardToolbarButton.vue'
|
|
54
|
+
import DashboardToolbarIcon from './DashboardToolbarIcon.vue'
|
|
108
55
|
|
|
109
56
|
const DEFAULT_WIDGET_HEIGHT = 500
|
|
110
57
|
|
|
@@ -253,6 +253,50 @@ query:
|
|
|
253
253
|
direction: asc
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
Note: use `stacked_bar` with a normal single-resource grouped query when you need a dynamic series dimension such as `series.field: purpose`. For the same numeric buckets across multiple resources, use `query.source: steps` with `query.bucket`; set `chart.x.field: label`, `chart.y.field` to the aggregate alias, and `chart.series.field: name`.
|
|
257
|
+
|
|
258
|
+
Example bucketed multi-resource stacked bar:
|
|
259
|
+
|
|
260
|
+
```yaml
|
|
261
|
+
target: chart
|
|
262
|
+
label: Cars by price range and database
|
|
263
|
+
size: wide
|
|
264
|
+
chart:
|
|
265
|
+
type: stacked_bar
|
|
266
|
+
x:
|
|
267
|
+
field: label
|
|
268
|
+
label: Price range
|
|
269
|
+
y:
|
|
270
|
+
field: count
|
|
271
|
+
label: Cars
|
|
272
|
+
series:
|
|
273
|
+
field: name
|
|
274
|
+
label: Database
|
|
275
|
+
query:
|
|
276
|
+
source: steps
|
|
277
|
+
bucket:
|
|
278
|
+
field: price
|
|
279
|
+
buckets:
|
|
280
|
+
- label: Budget
|
|
281
|
+
max: 3500
|
|
282
|
+
- label: Mid-range
|
|
283
|
+
min: 3500
|
|
284
|
+
max: 7000
|
|
285
|
+
- label: Premium
|
|
286
|
+
min: 7000
|
|
287
|
+
steps:
|
|
288
|
+
- name: SQLite
|
|
289
|
+
resource: cars_sl
|
|
290
|
+
select:
|
|
291
|
+
- agg: count
|
|
292
|
+
as: count
|
|
293
|
+
- name: MySQL
|
|
294
|
+
resource: cars_mysql
|
|
295
|
+
select:
|
|
296
|
+
- agg: count
|
|
297
|
+
as: count
|
|
298
|
+
```
|
|
299
|
+
|
|
256
300
|
Example `dashboard_configure_pie_chart_widget` config:
|
|
257
301
|
|
|
258
302
|
```yaml
|
|
@@ -291,26 +335,24 @@ chart:
|
|
|
291
335
|
field: total_tokens
|
|
292
336
|
label: Tokens
|
|
293
337
|
y:
|
|
294
|
-
field:
|
|
338
|
+
field: count
|
|
295
339
|
label: Requests
|
|
340
|
+
buckets:
|
|
341
|
+
- label: Small
|
|
342
|
+
max: 1000
|
|
343
|
+
- label: Medium
|
|
344
|
+
min: 1000
|
|
345
|
+
max: 10000
|
|
346
|
+
- label: Large
|
|
347
|
+
min: 10000
|
|
296
348
|
query:
|
|
297
349
|
resource: llm_usage
|
|
298
350
|
select:
|
|
299
351
|
- 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
352
|
```
|
|
313
353
|
|
|
354
|
+
Note: for the current dashboard runtime, histogram buckets are computed on the frontend from raw rows using `chart.buckets`. Do not rely on `query.bucket` for histogram widgets, do not use `query.source: steps`, and do not aggregate the source rows first. Histogram widgets should use a single-resource plain query with raw numeric rows, for example `select: - field: total_tokens`; the histogram component will derive the per-bucket `count` values itself.
|
|
355
|
+
|
|
314
356
|
Example `dashboard_configure_funnel_chart_widget` config:
|
|
315
357
|
|
|
316
358
|
```yaml
|
|
@@ -476,6 +518,14 @@ query:
|
|
|
476
518
|
|
|
477
519
|
Do not use bare query.steps without source: steps.
|
|
478
520
|
Do not use metric. Use select even when a step has only one aggregate.
|
|
521
|
+
Each `steps[]` item supports only:
|
|
522
|
+
- name
|
|
523
|
+
- resource
|
|
524
|
+
- select with aggregate items only, for example `agg: count`, `agg: sum`, `agg: avg`
|
|
525
|
+
- optional filters
|
|
526
|
+
Do not put `field` selects, `calc` selects, `group_by`, `order_by`, `limit`, `offset`, or `bucket` inside a step.
|
|
527
|
+
Without `query.bucket`, `query.source: steps` produces one output row per step, with built-in `name` and `resource` fields plus the aggregate aliases from that step.
|
|
528
|
+
For per-bucket comparisons across multiple resources, put `bucket` at the query level, not inside a step. Use stacked_bar with `chart.x.field: label`, `chart.series.field: name`, and `chart.y.field` set to the aggregate alias such as `count`.
|
|
479
529
|
All filters, including aggregate select item filters, must use filter expression shape.
|
|
480
530
|
Use `filters: { field: model, eq: gpt-5.4 }`, not shorthand maps like `filters: { model: gpt-5.4 }`.
|
|
481
531
|
When grouping by a derived date alias, repeat the source field object in `group_by`.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
ref="rootEl"
|
|
4
|
-
class="grid h-full min-h-0 w-full grid-rows-[auto_minmax(0,1fr)] gap-3 overflow-hidden"
|
|
4
|
+
class="relative grid h-full min-h-0 w-full grid-rows-[auto_minmax(0,1fr)] gap-3 overflow-hidden"
|
|
5
|
+
@mouseleave="hideTooltip"
|
|
5
6
|
>
|
|
6
7
|
<div
|
|
7
8
|
v-if="showLegend"
|
|
@@ -70,9 +71,9 @@
|
|
|
70
71
|
:height="segment.height"
|
|
71
72
|
:fill="segment.color"
|
|
72
73
|
rx="3"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
@mouseenter="showTooltip($event, bar)"
|
|
75
|
+
@mousemove="moveTooltip($event)"
|
|
76
|
+
/>
|
|
76
77
|
|
|
77
78
|
<text
|
|
78
79
|
v-if="visibleLabelIndexes.has(barIndex)"
|
|
@@ -88,13 +89,29 @@
|
|
|
88
89
|
</g>
|
|
89
90
|
</svg>
|
|
90
91
|
</div>
|
|
92
|
+
|
|
93
|
+
<Teleport to="body">
|
|
94
|
+
<div
|
|
95
|
+
v-if="tooltip"
|
|
96
|
+
class="pointer-events-none fixed z-[1000] min-w-44 rounded border border-lightTableBorder bg-lightTableBackground px-3 py-2 text-xs leading-5 text-lightListTableText shadow-lg dark:border-darkTableBorder dark:bg-darkTableBackground dark:text-darkListTableText"
|
|
97
|
+
:style="{ left: `${tooltip.x}px`, top: `${tooltip.y}px` }"
|
|
98
|
+
>
|
|
99
|
+
<div
|
|
100
|
+
v-for="line in tooltip.lines"
|
|
101
|
+
:key="line"
|
|
102
|
+
class="whitespace-nowrap"
|
|
103
|
+
>
|
|
104
|
+
{{ line }}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</Teleport>
|
|
91
108
|
</div>
|
|
92
109
|
</template>
|
|
93
110
|
|
|
94
111
|
|
|
95
112
|
|
|
96
113
|
<script setup lang="ts">
|
|
97
|
-
import { computed } from 'vue'
|
|
114
|
+
import { computed, ref } from 'vue'
|
|
98
115
|
import { useElementSize } from '../../composables/useElementSize.js'
|
|
99
116
|
import {
|
|
100
117
|
CHART_COLORS,
|
|
@@ -120,6 +137,17 @@ const { el: rootEl, width: rootWidth } = useElementSize<HTMLDivElement>()
|
|
|
120
137
|
const { el: svgEl, width: svgWidth, height: svgHeight } = useElementSize<HTMLDivElement>()
|
|
121
138
|
|
|
122
139
|
const barGap = 10
|
|
140
|
+
type StackedBarTooltipBar = {
|
|
141
|
+
label: string
|
|
142
|
+
total: number
|
|
143
|
+
segments: Array<{ name: string, value: number }>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const tooltip = ref<{
|
|
147
|
+
x: number
|
|
148
|
+
y: number
|
|
149
|
+
lines: string[]
|
|
150
|
+
} | null>(null)
|
|
123
151
|
const seriesNames = computed(() => Array.from(new Set(props.rows.map((row) => formatSeriesLabel(row[props.seriesField])))))
|
|
124
152
|
const normalizedSeries = computed(() => seriesNames.value.map((name, index) => ({
|
|
125
153
|
name,
|
|
@@ -223,7 +251,36 @@ const yTicks = computed(() => [0, 0.5, 1].map((ratio) => ({
|
|
|
223
251
|
y: padding.value.top + innerHeight.value * ratio,
|
|
224
252
|
})))
|
|
225
253
|
|
|
226
|
-
function
|
|
254
|
+
function showTooltip(event: MouseEvent, bar: StackedBarTooltipBar) {
|
|
255
|
+
tooltip.value = {
|
|
256
|
+
...getTooltipPosition(event),
|
|
257
|
+
lines: getBarTooltipLines(bar),
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function moveTooltip(event: MouseEvent) {
|
|
262
|
+
if (!tooltip.value) {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
tooltip.value = {
|
|
267
|
+
...tooltip.value,
|
|
268
|
+
...getTooltipPosition(event),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function hideTooltip() {
|
|
273
|
+
tooltip.value = null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function getTooltipPosition(event: MouseEvent) {
|
|
277
|
+
return {
|
|
278
|
+
x: event.clientX + 12,
|
|
279
|
+
y: event.clientY + 12,
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function getBarTooltipLines(bar: StackedBarTooltipBar) {
|
|
227
284
|
const percentFormatter = new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 })
|
|
228
285
|
|
|
229
286
|
const segmentLines = bar.segments.map((segment) => {
|
|
@@ -238,7 +295,7 @@ function getBarTooltip(bar: { label: string, total: number, segments: Array<{ na
|
|
|
238
295
|
`${bar.label}`,
|
|
239
296
|
`Total: ${formatChartValue(bar.total)}`,
|
|
240
297
|
...segmentLines,
|
|
241
|
-
]
|
|
298
|
+
]
|
|
242
299
|
}
|
|
243
300
|
|
|
244
301
|
function formatSeriesLabel(value: unknown) {
|