@adminforth/dashboard 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/custom/model/dashboard.types.ts +25 -10
  2. package/custom/package.json +1 -0
  3. package/custom/pnpm-lock.yaml +31 -0
  4. package/custom/runtime/DashboardRuntime.vue +7 -12
  5. package/custom/runtime/YamlConfigEditor.vue +109 -0
  6. package/custom/skills/adminforth-dashboard/SKILL.md +26 -3
  7. package/dist/custom/model/dashboard.types.d.ts +19 -7
  8. package/dist/custom/model/dashboard.types.ts +25 -10
  9. package/dist/custom/package.json +1 -0
  10. package/dist/custom/pnpm-lock.yaml +31 -0
  11. package/dist/custom/queries/useDashboardConfig.d.ts +322 -4
  12. package/dist/custom/queries/useWidgetData.d.ts +322 -4
  13. package/dist/custom/runtime/DashboardRuntime.vue +7 -12
  14. package/dist/custom/runtime/YamlConfigEditor.vue +109 -0
  15. package/dist/custom/skills/adminforth-dashboard/SKILL.md +26 -3
  16. package/dist/schema/api.d.ts +8099 -1620
  17. package/dist/schema/api.js +2 -2
  18. package/dist/schema/widget.d.ts +622 -33
  19. package/dist/schema/widget.js +1 -1
  20. package/dist/schema/widgets/charts.d.ts +785 -39
  21. package/dist/schema/widgets/charts.js +2 -2
  22. package/dist/schema/widgets/common.d.ts +35 -6
  23. package/dist/schema/widgets/common.js +23 -5
  24. package/dist/schema/widgets/gauge-card.d.ts +56 -2
  25. package/dist/schema/widgets/kpi-card.d.ts +56 -2
  26. package/dist/schema/widgets/pivot-table.d.ts +56 -2
  27. package/dist/schema/widgets/table.d.ts +56 -2
  28. package/dist/services/widgetDataService.js +37 -32
  29. package/package.json +1 -1
  30. package/schema/api.ts +1 -2
  31. package/schema/widget.ts +0 -1
  32. package/schema/widgets/charts.ts +1 -2
  33. package/schema/widgets/common.ts +24 -5
  34. package/services/widgetDataService.ts +62 -50
  35. package/shims-vue.d.ts +11 -0
  36. package/tsconfig.json +3 -1
@@ -117,7 +117,8 @@ export type QueryOrderByItem = {
117
117
  direction?: 'asc' | 'desc'
118
118
  }
119
119
 
120
- export type QueryConfig = {
120
+ export type ResourceQueryConfig = {
121
+ source?: 'resource'
121
122
  resource: string
122
123
  select?: QuerySelectItem[]
123
124
  sparkline?: {
@@ -139,17 +140,31 @@ export type QueryConfig = {
139
140
  formatting?: Record<string, JsonValue>
140
141
  }
141
142
 
142
- export type FunnelQueryConfig = {
143
- steps: FunnelQueryStep[]
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
+ }
156
+
157
+ export type StepsQueryConfig = {
158
+ source: 'steps'
159
+ steps: StepsQueryStepConfig[]
144
160
  calcs?: QueryCalcSelectItem[]
161
+ order_by?: QueryOrderByItem[]
162
+ limit?: number
163
+ offset?: number
164
+ formatting?: Record<string, JsonValue>
145
165
  }
146
166
 
147
- export type FunnelQueryStep = {
148
- name: string
149
- resource: string
150
- metric: QueryAggregateSelectItem
151
- filters?: FilterExpression
152
- }
167
+ export type QueryConfig = ResourceQueryConfig | StepsQueryConfig
153
168
 
154
169
  export type FieldRef = string | {
155
170
  field: string
@@ -246,7 +261,7 @@ export type TableWidgetConfig = WidgetBaseConfig & {
246
261
  export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
247
262
  target: 'chart'
248
263
  chart: ChartWidgetConfig
249
- query: QueryConfig | FunnelQueryConfig
264
+ query: QueryConfig
250
265
  }
251
266
 
252
267
  export type KpiCardWidgetConfig = WidgetBaseConfig & {
@@ -5,6 +5,7 @@
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
+ "monaco-editor": "^0.55.1",
8
9
  "yaml": "^2.9.0"
9
10
  }
10
11
  }
@@ -8,12 +8,29 @@ importers:
8
8
 
9
9
  .:
10
10
  dependencies:
11
+ monaco-editor:
12
+ specifier: ^0.55.1
13
+ version: 0.55.1
11
14
  yaml:
12
15
  specifier: ^2.9.0
13
16
  version: 2.9.0
14
17
 
15
18
  packages:
16
19
 
20
+ '@types/trusted-types@2.0.7':
21
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
22
+
23
+ dompurify@3.2.7:
24
+ resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
25
+
26
+ marked@14.0.0:
27
+ resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
28
+ engines: {node: '>= 18'}
29
+ hasBin: true
30
+
31
+ monaco-editor@0.55.1:
32
+ resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
33
+
17
34
  yaml@2.9.0:
18
35
  resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
19
36
  engines: {node: '>= 14.6'}
@@ -21,4 +38,18 @@ packages:
21
38
 
22
39
  snapshots:
23
40
 
41
+ '@types/trusted-types@2.0.7':
42
+ optional: true
43
+
44
+ dompurify@3.2.7:
45
+ optionalDependencies:
46
+ '@types/trusted-types': 2.0.7
47
+
48
+ marked@14.0.0: {}
49
+
50
+ monaco-editor@0.55.1:
51
+ dependencies:
52
+ dompurify: 3.2.7
53
+ marked: 14.0.0
54
+
24
55
  yaml@2.9.0: {}
@@ -54,7 +54,7 @@
54
54
  class="fixed inset-0 z-50 flex items-center justify-center bg-black/35 p-4"
55
55
  @click.self="closeGroupConfigEditor"
56
56
  >
57
- <section class="w-full max-w-2xl rounded-lg border border-lightListBorder bg-lightDropdownOptionsBackground p-4 shadow-xl dark:border-darkListBorder dark:bg-darkDropdownOptionsBackground">
57
+ <section class="w-full max-w-5xl rounded-lg border border-lightListBorder bg-lightDropdownOptionsBackground p-4 shadow-xl dark:border-darkListBorder dark:bg-darkDropdownOptionsBackground">
58
58
  <header class="mb-3 flex items-center justify-between gap-3">
59
59
  <h2 class="m-0 text-base font-bold text-lightNavbarText dark:text-darkNavbarText">
60
60
  Group JSON
@@ -81,12 +81,9 @@
81
81
  </button>
82
82
  </header>
83
83
 
84
- <textarea
84
+ <YamlConfigEditor
85
85
  v-model="groupConfigCode"
86
- class="min-h-[500px] w-full resize-y rounded-lg border border-lightListBorder bg-lightListTable p-3 font-mono text-sm text-lightNavbarText outline-none focus:border-lightPrimaryButtonBackground dark:border-darkListBorder dark:bg-darkListTable dark:text-darkNavbarText dark:focus:border-darkPrimaryButtonBackground"
87
- spellcheck="false"
88
- @keydown.ctrl.enter.prevent="saveGroupConfig"
89
- @keydown.meta.enter.prevent="saveGroupConfig"
86
+ @save="saveGroupConfig"
90
87
  />
91
88
 
92
89
  <div
@@ -120,7 +117,7 @@
120
117
  class="fixed inset-0 z-50 flex items-center justify-center bg-black/35 p-4"
121
118
  @click.self="closeWidgetConfigEditor"
122
119
  >
123
- <section class="w-full max-w-2xl rounded-lg border border-lightListBorder bg-lightDropdownOptionsBackground p-4 shadow-xl dark:border-darkListBorder dark:bg-darkDropdownOptionsBackground">
120
+ <section class="w-full max-w-5xl rounded-lg border border-lightListBorder bg-lightDropdownOptionsBackground p-4 shadow-xl dark:border-darkListBorder dark:bg-darkDropdownOptionsBackground">
124
121
  <header class="mb-3 flex items-center justify-between gap-3">
125
122
  <h2 class="m-0 text-base font-bold text-lightNavbarText dark:text-darkNavbarText">
126
123
  Widget JSON
@@ -147,12 +144,9 @@
147
144
  </button>
148
145
  </header>
149
146
 
150
- <textarea
147
+ <YamlConfigEditor
151
148
  v-model="widgetConfigCode"
152
- class="min-h-[500px] w-full resize-y rounded-lg border border-lightListBorder bg-lightListTable p-3 font-mono text-sm text-lightNavbarText outline-none focus:border-lightPrimaryButtonBackground dark:border-darkListBorder dark:bg-darkListTable dark:text-darkNavbarText dark:focus:border-darkPrimaryButtonBackground"
153
- spellcheck="false"
154
- @keydown.ctrl.enter.prevent="saveWidgetConfig"
155
- @keydown.meta.enter.prevent="saveWidgetConfig"
149
+ @save="saveWidgetConfig"
156
150
  />
157
151
 
158
152
  <div
@@ -203,6 +197,7 @@ import { computed, ref, watch } from 'vue'
203
197
  import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'
204
198
  import { Button } from '@/afcl'
205
199
  import DashboardGroup from './DashboardGroup.vue'
200
+ import YamlConfigEditor from './YamlConfigEditor.vue'
206
201
  import { DashboardApiError, dashboardApi, type DashboardResponse } from '../api/dashboardApi.js'
207
202
  import type {
208
203
  DashboardConfig,
@@ -0,0 +1,109 @@
1
+ <template>
2
+ <div
3
+ ref="editorEl"
4
+ class="min-h-[500px] w-full overflow-hidden rounded-lg border border-lightListBorder bg-lightListTable text-sm dark:border-darkListBorder dark:bg-darkListTable"
5
+ />
6
+ </template>
7
+
8
+ <script setup lang="ts">
9
+ import 'monaco-editor/min/vs/editor/editor.main.css'
10
+ import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
11
+ import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
12
+ import type * as Monaco from 'monaco-editor'
13
+
14
+ declare global {
15
+ interface Window {
16
+ MonacoEnvironment?: {
17
+ getWorker: () => Worker
18
+ }
19
+ }
20
+ }
21
+
22
+ if (typeof window !== 'undefined' && !window.MonacoEnvironment) {
23
+ window.MonacoEnvironment = {
24
+ getWorker: () => new EditorWorker(),
25
+ }
26
+ }
27
+
28
+ const props = defineProps<{
29
+ modelValue: string
30
+ }>()
31
+
32
+ const emit = defineEmits<{
33
+ 'update:modelValue': [value: string]
34
+ save: []
35
+ }>()
36
+
37
+ const editorEl = ref<HTMLElement | null>(null)
38
+ let monaco: typeof Monaco | null = null
39
+ let editor: Monaco.editor.IStandaloneCodeEditor | null = null
40
+ let resizeObserver: ResizeObserver | null = null
41
+ let applyingExternalValue = false
42
+
43
+ watch(
44
+ () => props.modelValue,
45
+ (value) => {
46
+ if (!editor || value === editor.getValue()) {
47
+ return
48
+ }
49
+
50
+ applyingExternalValue = true
51
+ editor.setValue(value)
52
+ applyingExternalValue = false
53
+ },
54
+ )
55
+
56
+ onMounted(async () => {
57
+ const [loadedMonaco] = await Promise.all([
58
+ import('monaco-editor'),
59
+ import('monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'),
60
+ ])
61
+
62
+ monaco = loadedMonaco
63
+ await nextTick()
64
+
65
+ if (!editorEl.value) {
66
+ return
67
+ }
68
+
69
+ editor = monaco.editor.create(editorEl.value, {
70
+ value: props.modelValue,
71
+ language: 'yaml',
72
+ automaticLayout: true,
73
+ minimap: { enabled: false },
74
+ fontSize: 13,
75
+ lineNumbersMinChars: 3,
76
+ padding: { top: 12, bottom: 12 },
77
+ scrollBeyondLastLine: false,
78
+ tabSize: 2,
79
+ insertSpaces: true,
80
+ wordWrap: 'on',
81
+ wrappingIndent: 'same',
82
+ theme: document.documentElement.classList.contains('dark') ? 'vs-dark' : 'vs',
83
+ })
84
+
85
+ editor.onDidChangeModelContent(() => {
86
+ if (!editor || applyingExternalValue) {
87
+ return
88
+ }
89
+
90
+ emit('update:modelValue', editor.getValue())
91
+ })
92
+
93
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => {
94
+ emit('save')
95
+ })
96
+
97
+ resizeObserver = new ResizeObserver(() => {
98
+ editor?.layout()
99
+ })
100
+ resizeObserver.observe(editorEl.value)
101
+ })
102
+
103
+ onBeforeUnmount(() => {
104
+ resizeObserver?.disconnect()
105
+ resizeObserver = null
106
+ editor?.dispose()
107
+ editor = null
108
+ })
109
+ </script>
@@ -151,8 +151,8 @@ Use resource, not resourceId.
151
151
 
152
152
  ## Query shape rules
153
153
 
154
- Use dashboard_configure_funnel_chart_widget for funnel charts and set query.steps.
155
- Do not use query.steps for kpi_card, gauge_card, table, pivot_table, line, bar, stacked bar, pie, or histogram charts.
154
+ All chart widgets, including funnel charts, use the same query shape.
155
+ Use a single-resource query by default.
156
156
 
157
157
  For kpi_card and normal charts, use:
158
158
  - query.resource
@@ -162,6 +162,29 @@ For kpi_card and normal charts, use:
162
162
  - optional query.order_by
163
163
  - optional query.calcs
164
164
 
165
+ For multi-resource charts or widgets, use the general steps source:
166
+
167
+ query:
168
+ source: steps
169
+ steps:
170
+ - name: Leads
171
+ resource: leads
172
+ metric:
173
+ agg: count
174
+ as: value
175
+ - name: Customers
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
185
+
186
+ Do not use bare query.steps without source: steps.
187
+
165
188
  ## Date range rules
166
189
 
167
190
  Use only query.filters for time ranges.
@@ -203,7 +226,7 @@ select raw token totals:
203
226
  then query.calcs:
204
227
  - calculate total_spend from those aliases and lookup variables
205
228
 
206
- For today vs yesterday KPI, use multiple aggregate select items with filters and distinct aliases, then calcs. Do not use query.steps.
229
+ For today vs yesterday KPI, use multiple aggregate select items with filters and distinct aliases, then calcs.
207
230
 
208
231
  ## Calc variables
209
232
 
@@ -87,7 +87,8 @@ export type QueryOrderByItem = {
87
87
  field: string;
88
88
  direction?: 'asc' | 'desc';
89
89
  };
90
- export type QueryConfig = {
90
+ export type ResourceQueryConfig = {
91
+ source?: 'resource';
91
92
  resource: string;
92
93
  select?: QuerySelectItem[];
93
94
  sparkline?: {
@@ -112,16 +113,27 @@ export type QueryConfig = {
112
113
  calcs?: QueryCalcSelectItem[];
113
114
  formatting?: Record<string, JsonValue>;
114
115
  };
115
- export type FunnelQueryConfig = {
116
- steps: FunnelQueryStep[];
117
- calcs?: QueryCalcSelectItem[];
118
- };
119
- export type FunnelQueryStep = {
116
+ export type StepsQueryStepConfig = {
120
117
  name: string;
121
118
  resource: string;
122
119
  metric: QueryAggregateSelectItem;
123
120
  filters?: FilterExpression;
121
+ } | {
122
+ name: string;
123
+ resource: string;
124
+ select: QueryAggregateSelectItem[];
125
+ filters?: FilterExpression;
124
126
  };
127
+ export type StepsQueryConfig = {
128
+ source: 'steps';
129
+ steps: StepsQueryStepConfig[];
130
+ calcs?: QueryCalcSelectItem[];
131
+ order_by?: QueryOrderByItem[];
132
+ limit?: number;
133
+ offset?: number;
134
+ formatting?: Record<string, JsonValue>;
135
+ };
136
+ export type QueryConfig = ResourceQueryConfig | StepsQueryConfig;
125
137
  export type FieldRef = string | {
126
138
  field: string;
127
139
  label?: string;
@@ -210,7 +222,7 @@ export type TableWidgetConfig = WidgetBaseConfig & {
210
222
  export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
211
223
  target: 'chart';
212
224
  chart: ChartWidgetConfig;
213
- query: QueryConfig | FunnelQueryConfig;
225
+ query: QueryConfig;
214
226
  };
215
227
  export type KpiCardWidgetConfig = WidgetBaseConfig & {
216
228
  target: 'kpi_card';
@@ -117,7 +117,8 @@ export type QueryOrderByItem = {
117
117
  direction?: 'asc' | 'desc'
118
118
  }
119
119
 
120
- export type QueryConfig = {
120
+ export type ResourceQueryConfig = {
121
+ source?: 'resource'
121
122
  resource: string
122
123
  select?: QuerySelectItem[]
123
124
  sparkline?: {
@@ -139,17 +140,31 @@ export type QueryConfig = {
139
140
  formatting?: Record<string, JsonValue>
140
141
  }
141
142
 
142
- export type FunnelQueryConfig = {
143
- steps: FunnelQueryStep[]
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
+ }
156
+
157
+ export type StepsQueryConfig = {
158
+ source: 'steps'
159
+ steps: StepsQueryStepConfig[]
144
160
  calcs?: QueryCalcSelectItem[]
161
+ order_by?: QueryOrderByItem[]
162
+ limit?: number
163
+ offset?: number
164
+ formatting?: Record<string, JsonValue>
145
165
  }
146
166
 
147
- export type FunnelQueryStep = {
148
- name: string
149
- resource: string
150
- metric: QueryAggregateSelectItem
151
- filters?: FilterExpression
152
- }
167
+ export type QueryConfig = ResourceQueryConfig | StepsQueryConfig
153
168
 
154
169
  export type FieldRef = string | {
155
170
  field: string
@@ -246,7 +261,7 @@ export type TableWidgetConfig = WidgetBaseConfig & {
246
261
  export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
247
262
  target: 'chart'
248
263
  chart: ChartWidgetConfig
249
- query: QueryConfig | FunnelQueryConfig
264
+ query: QueryConfig
250
265
  }
251
266
 
252
267
  export type KpiCardWidgetConfig = WidgetBaseConfig & {
@@ -5,6 +5,7 @@
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
+ "monaco-editor": "^0.55.1",
8
9
  "yaml": "^2.9.0"
9
10
  }
10
11
  }
@@ -8,12 +8,29 @@ importers:
8
8
 
9
9
  .:
10
10
  dependencies:
11
+ monaco-editor:
12
+ specifier: ^0.55.1
13
+ version: 0.55.1
11
14
  yaml:
12
15
  specifier: ^2.9.0
13
16
  version: 2.9.0
14
17
 
15
18
  packages:
16
19
 
20
+ '@types/trusted-types@2.0.7':
21
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
22
+
23
+ dompurify@3.2.7:
24
+ resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
25
+
26
+ marked@14.0.0:
27
+ resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
28
+ engines: {node: '>= 18'}
29
+ hasBin: true
30
+
31
+ monaco-editor@0.55.1:
32
+ resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
33
+
17
34
  yaml@2.9.0:
18
35
  resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
19
36
  engines: {node: '>= 14.6'}
@@ -21,4 +38,18 @@ packages:
21
38
 
22
39
  snapshots:
23
40
 
41
+ '@types/trusted-types@2.0.7':
42
+ optional: true
43
+
44
+ dompurify@3.2.7:
45
+ optionalDependencies:
46
+ '@types/trusted-types': 2.0.7
47
+
48
+ marked@14.0.0: {}
49
+
50
+ monaco-editor@0.55.1:
51
+ dependencies:
52
+ dompurify: 3.2.7
53
+ marked: 14.0.0
54
+
24
55
  yaml@2.9.0: {}