@adminforth/dashboard 1.0.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 (107) hide show
  1. package/.woodpecker/buildRelease.sh +13 -0
  2. package/.woodpecker/buildSlackNotify.sh +46 -0
  3. package/.woodpecker/release.yml +57 -0
  4. package/README.md +59 -0
  5. package/custom/api/dashboardApi.ts +213 -0
  6. package/custom/composables/useElementSize.ts +41 -0
  7. package/custom/model/dashboard.types.ts +73 -0
  8. package/custom/package.json +9 -0
  9. package/custom/pnpm-lock.yaml +24 -0
  10. package/custom/queries/useDashboardConfig.ts +51 -0
  11. package/custom/queries/useWidgetData.ts +51 -0
  12. package/custom/runtime/DashboardGroup.vue +185 -0
  13. package/custom/runtime/DashboardPage.vue +122 -0
  14. package/custom/runtime/DashboardRuntime.vue +435 -0
  15. package/custom/runtime/WidgetRenderer.vue +60 -0
  16. package/custom/runtime/WidgetShell.vue +152 -0
  17. package/custom/skills/adminforth-dashboard/SKILL.md +125 -0
  18. package/custom/widgets/chart/ChartWidget.vue +188 -0
  19. package/custom/widgets/chart/bar/BarChart.vue +167 -0
  20. package/custom/widgets/chart/chart.types.ts +34 -0
  21. package/custom/widgets/chart/chart.utils.ts +54 -0
  22. package/custom/widgets/chart/funnel/FunnelChart.vue +197 -0
  23. package/custom/widgets/chart/histogram/HistogramChart.vue +21 -0
  24. package/custom/widgets/chart/line/LineChart.vue +175 -0
  25. package/custom/widgets/chart/pie/PieChart.vue +161 -0
  26. package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +256 -0
  27. package/custom/widgets/gauge-card/GaugeCardWidget.vue +107 -0
  28. package/custom/widgets/kpi-card/KpiCardWidget.vue +73 -0
  29. package/custom/widgets/pivot-table/PivotTableWidget.vue +122 -0
  30. package/custom/widgets/registry.ts +51 -0
  31. package/custom/widgets/table/TableWidget.vue +110 -0
  32. package/dist/custom/api/dashboardApi.d.ts +32 -0
  33. package/dist/custom/api/dashboardApi.js +179 -0
  34. package/dist/custom/api/dashboardApi.ts +213 -0
  35. package/dist/custom/composables/useElementSize.d.ts +8 -0
  36. package/dist/custom/composables/useElementSize.js +30 -0
  37. package/dist/custom/composables/useElementSize.ts +41 -0
  38. package/dist/custom/model/dashboard.types.d.ts +45 -0
  39. package/dist/custom/model/dashboard.types.js +14 -0
  40. package/dist/custom/model/dashboard.types.ts +73 -0
  41. package/dist/custom/package.json +9 -0
  42. package/dist/custom/pnpm-lock.yaml +24 -0
  43. package/dist/custom/queries/useDashboardConfig.d.ts +112 -0
  44. package/dist/custom/queries/useDashboardConfig.js +57 -0
  45. package/dist/custom/queries/useDashboardConfig.ts +51 -0
  46. package/dist/custom/queries/useWidgetData.d.ts +90 -0
  47. package/dist/custom/queries/useWidgetData.js +57 -0
  48. package/dist/custom/queries/useWidgetData.ts +51 -0
  49. package/dist/custom/runtime/DashboardGroup.vue +185 -0
  50. package/dist/custom/runtime/DashboardPage.vue +122 -0
  51. package/dist/custom/runtime/DashboardRuntime.vue +435 -0
  52. package/dist/custom/runtime/WidgetRenderer.vue +60 -0
  53. package/dist/custom/runtime/WidgetShell.vue +152 -0
  54. package/dist/custom/skills/adminforth-dashboard/SKILL.md +125 -0
  55. package/dist/custom/widgets/chart/ChartWidget.vue +188 -0
  56. package/dist/custom/widgets/chart/bar/BarChart.vue +167 -0
  57. package/dist/custom/widgets/chart/chart.types.d.ts +25 -0
  58. package/dist/custom/widgets/chart/chart.types.js +2 -0
  59. package/dist/custom/widgets/chart/chart.types.ts +34 -0
  60. package/dist/custom/widgets/chart/chart.utils.d.ts +5 -0
  61. package/dist/custom/widgets/chart/chart.utils.js +52 -0
  62. package/dist/custom/widgets/chart/chart.utils.ts +54 -0
  63. package/dist/custom/widgets/chart/funnel/FunnelChart.vue +197 -0
  64. package/dist/custom/widgets/chart/histogram/HistogramChart.vue +21 -0
  65. package/dist/custom/widgets/chart/line/LineChart.vue +175 -0
  66. package/dist/custom/widgets/chart/pie/PieChart.vue +161 -0
  67. package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +256 -0
  68. package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +107 -0
  69. package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +73 -0
  70. package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +122 -0
  71. package/dist/custom/widgets/registry.d.ts +11 -0
  72. package/dist/custom/widgets/registry.js +47 -0
  73. package/dist/custom/widgets/registry.ts +51 -0
  74. package/dist/custom/widgets/table/TableWidget.vue +110 -0
  75. package/dist/endpoint/dashboard.d.ts +7 -0
  76. package/dist/endpoint/dashboard.js +29 -0
  77. package/dist/endpoint/groups.d.ts +30 -0
  78. package/dist/endpoint/groups.js +131 -0
  79. package/dist/endpoint/widgets.d.ts +15 -0
  80. package/dist/endpoint/widgets.js +182 -0
  81. package/dist/index.d.ts +13 -0
  82. package/dist/index.js +124 -0
  83. package/dist/schema/api.d.ts +1205 -0
  84. package/dist/schema/api.js +84 -0
  85. package/dist/schema/widget.d.ts +514 -0
  86. package/dist/schema/widget.js +133 -0
  87. package/dist/services/dashboardConfigService.d.ts +35 -0
  88. package/dist/services/dashboardConfigService.js +79 -0
  89. package/dist/services/widgetConfigValidator.d.ts +8 -0
  90. package/dist/services/widgetConfigValidator.js +65 -0
  91. package/dist/services/widgetDataService.d.ts +20 -0
  92. package/dist/services/widgetDataService.js +32 -0
  93. package/dist/types.d.ts +8 -0
  94. package/dist/types.js +1 -0
  95. package/endpoint/dashboard.ts +32 -0
  96. package/endpoint/groups.ts +213 -0
  97. package/endpoint/widgets.ts +255 -0
  98. package/index.ts +141 -0
  99. package/package.json +64 -0
  100. package/schema/api.ts +99 -0
  101. package/schema/widget.ts +159 -0
  102. package/services/dashboardConfigService.ts +136 -0
  103. package/services/widgetConfigValidator.ts +93 -0
  104. package/services/widgetDataService.ts +57 -0
  105. package/shims-vue.d.ts +5 -0
  106. package/tsconfig.json +18 -0
  107. package/types.ts +8 -0
@@ -0,0 +1,13 @@
1
+
2
+ #!/bin/bash
3
+
4
+ # write npm run output both to console and to build.log
5
+ npm run build 2>&1 | tee build.log
6
+ build_status=${PIPESTATUS[0]}
7
+
8
+ # if exist status from the npm run build is not 0
9
+ # then exit with the status code from the npm run build
10
+ if [ $build_status -ne 0 ]; then
11
+ echo "Build failed. Exiting with status code $build_status"
12
+ exit $build_status
13
+ fi
@@ -0,0 +1,46 @@
1
+ #!/bin/sh
2
+
3
+ set -x
4
+
5
+ COMMIT_SHORT_SHA=$(echo $CI_COMMIT_SHA | cut -c1-8)
6
+
7
+ STATUS=${1}
8
+
9
+
10
+ if [ "$STATUS" = "success" ]; then
11
+ MESSAGE="Did a build without issues on \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\`. Commit: _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
12
+
13
+ curl -s -X POST -H "Content-Type: application/json" -d '{
14
+ "username": "'"$CI_COMMIT_AUTHOR"'",
15
+ "icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
16
+ "attachments": [
17
+ {
18
+ "mrkdwn_in": ["text", "pretext"],
19
+ "color": "#36a64f",
20
+ "text": "'"$MESSAGE"'"
21
+ }
22
+ ]
23
+ }' "$DEVELOPERS_SLACK_WEBHOOK"
24
+ exit 0
25
+ fi
26
+ export BUILD_LOG=$(cat ./build.log)
27
+
28
+ BUILD_LOG=$(echo $BUILD_LOG | sed 's/"/\\"/g')
29
+
30
+ MESSAGE="Broke \`$CI_REPO_NAME/$CI_COMMIT_BRANCH\` with commit _${CI_COMMIT_MESSAGE}_ (<$CI_COMMIT_URL|$COMMIT_SHORT_SHA>)"
31
+ CODE_BLOCK="\`\`\`$BUILD_LOG\n\`\`\`"
32
+
33
+ echo "Sending slack message to developers $MESSAGE"
34
+ # Send the message
35
+ curl -sS -X POST -H "Content-Type: application/json" -d '{
36
+ "username": "'"$CI_COMMIT_AUTHOR"'",
37
+ "icon_url": "'"$CI_COMMIT_AUTHOR_AVATAR"'",
38
+ "attachments": [
39
+ {
40
+ "mrkdwn_in": ["text", "pretext"],
41
+ "color": "#8A1C12",
42
+ "text": "'"$CODE_BLOCK"'",
43
+ "pretext": "'"$MESSAGE"'"
44
+ }
45
+ ]
46
+ }' "$DEVELOPERS_SLACK_WEBHOOK" 2>&1
@@ -0,0 +1,57 @@
1
+ clone:
2
+ git:
3
+ image: woodpeckerci/plugin-git
4
+ settings:
5
+ partial: false
6
+ depth: 5
7
+
8
+ steps:
9
+ init-secrets:
10
+ when:
11
+ - event: push
12
+ image: infisical/cli
13
+ environment:
14
+ INFISICAL_TOKEN:
15
+ from_secret: VAULT_TOKEN
16
+ commands:
17
+ - infisical export --domain https://vault.devforth.io/api --format=dotenv-export --env="prod" > /woodpecker/deploy.vault.env
18
+
19
+ build:
20
+ image: devforth/node20-pnpm:latest
21
+ when:
22
+ - event: push
23
+ commands:
24
+ - apt update && apt install -y rsync
25
+ - . /woodpecker/deploy.vault.env
26
+ - pnpm install
27
+ - /bin/bash ./.woodpecker/buildRelease.sh
28
+ - npm audit signatures
29
+
30
+ release:
31
+ image: devforth/node20-pnpm:latest
32
+ when:
33
+ - event:
34
+ - push
35
+ branch:
36
+ - main
37
+ commands:
38
+ - . /woodpecker/deploy.vault.env
39
+ - pnpm exec semantic-release
40
+
41
+ slack-on-failure:
42
+ image: curlimages/curl
43
+ when:
44
+ - event: push
45
+ status: [failure]
46
+ commands:
47
+ - . /woodpecker/deploy.vault.env
48
+ - /bin/sh ./.woodpecker/buildSlackNotify.sh failure
49
+
50
+ slack-on-success:
51
+ image: curlimages/curl
52
+ when:
53
+ - event: push
54
+ status: [success]
55
+ commands:
56
+ - . /woodpecker/deploy.vault.env
57
+ - /bin/sh ./.woodpecker/buildSlackNotify.sh success
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @adminforth/dashboard
2
+
3
+ Dashboard plugin for AdminForth.
4
+
5
+ DashboardPage.vue
6
+ └── DashboardRuntime.vue
7
+ ├── DashboardGroup.vue
8
+ │ └── WidgetShell.vue
9
+ │ └── WidgetRenderer.vue
10
+ │ ├── TableWidget.vue
11
+ │ ├── ChartWidget.vue
12
+ │ ├── KpiCardWidget.vue
13
+ │ ├── PivotTableWidget.vue
14
+ │ └── GaugeCardWidget.vue
15
+ └── DashboardEditorPanel.vue
16
+
17
+ src/features/dashboards/
18
+
19
+ runtime/
20
+ DashboardPage.vue
21
+ DashboardRuntime.vue
22
+ DashboardGroup.vue
23
+ WidgetShell.vue
24
+ WidgetRenderer.vue
25
+
26
+ widgets/
27
+ registry.ts
28
+
29
+ table/
30
+ TableWidget.vue
31
+ TableWidgetEditor.vue
32
+ table.adapter.ts
33
+
34
+ chart/
35
+ ChartWidget.vue
36
+ ChartWidgetEditor.vue
37
+ chart.adapter.ts
38
+ charts/
39
+ PieChart.vue
40
+ LineChart.vue
41
+ BarChart.vue
42
+ StackedBarChart.vue
43
+ FunnelChart.vue
44
+ HistogramChart.vue
45
+
46
+ kpi-card/
47
+ KpiCardWidget.vue
48
+ KpiCardWidgetEditor.vue
49
+ kpi.adapter.ts
50
+
51
+ pivot-table/
52
+ PivotTableWidget.vue
53
+ PivotTableWidgetEditor.vue
54
+ pivot.adapter.ts
55
+
56
+ gauge-card/
57
+ GaugeCardWidget.vue
58
+ GaugeCardWidgetEditor.vue
59
+ gauge.adapter.ts
@@ -0,0 +1,213 @@
1
+ import type {
2
+ DashboardConfig,
3
+ DashboardGroupConfig,
4
+ DashboardGroupMoveDirection,
5
+ DashboardWidgetConfig,
6
+ DashboardWidgetMoveDirection,
7
+ } from '../model/dashboard.types.js'
8
+
9
+ export type DashboardWidgetConfigValidationError = {
10
+ field: string
11
+ message: string
12
+ }
13
+
14
+ export type DashboardResponse = {
15
+ id: string
16
+ slug: string
17
+ label: string
18
+ revision: number
19
+ config: DashboardConfig
20
+ }
21
+
22
+ export type DashboardWidgetDataResponse = {
23
+ widget: DashboardWidgetConfig
24
+ data: unknown
25
+ }
26
+
27
+ export class DashboardApiError extends Error {
28
+ validationErrors: DashboardWidgetConfigValidationError[]
29
+
30
+ constructor(message: string, validationErrors: DashboardWidgetConfigValidationError[] = []) {
31
+ super(message)
32
+ this.name = 'DashboardApiError'
33
+ this.validationErrors = validationErrors
34
+ }
35
+ }
36
+
37
+ function normalizeValidationErrors(response: any): DashboardWidgetConfigValidationError[] {
38
+ if (Array.isArray(response?.validationErrors)) {
39
+ return response.validationErrors
40
+ }
41
+
42
+ if (Array.isArray(response?.details)) {
43
+ return response.details.map((detail: any) => ({
44
+ field: Array.isArray(detail.instancePath)
45
+ ? detail.instancePath.join('.')
46
+ : String(detail.instancePath || detail.path || 'config').replace(/^\//, '').replaceAll('/', '.'),
47
+ message: String(detail.message || 'Invalid value'),
48
+ }))
49
+ }
50
+
51
+ return []
52
+ }
53
+
54
+ async function parseDashboardResponse(rawResponse: Response) {
55
+ const responseText = await rawResponse.text()
56
+
57
+ if (!responseText) {
58
+ return null
59
+ }
60
+
61
+ try {
62
+ return JSON.parse(responseText)
63
+ } catch {
64
+ return {
65
+ error: responseText,
66
+ }
67
+ }
68
+ }
69
+
70
+ async function callDashboardApi(path: string, body: Record<string, unknown>): Promise<DashboardResponse> {
71
+ const rawResponse = await fetch(path, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ 'accept-language': localStorage.getItem('af_lang') || 'en',
76
+ },
77
+ body: JSON.stringify(body),
78
+ })
79
+
80
+ const response = await parseDashboardResponse(rawResponse)
81
+
82
+ if (!rawResponse.ok) {
83
+ throw new DashboardApiError(
84
+ response?.error || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`,
85
+ normalizeValidationErrors(response),
86
+ )
87
+ }
88
+
89
+ if (!response || response.error) {
90
+ throw new DashboardApiError(response?.error || 'Dashboard request failed', normalizeValidationErrors(response))
91
+ }
92
+
93
+ return {
94
+ id: response.id,
95
+ slug: response.slug,
96
+ label: response.label,
97
+ revision: response.revision,
98
+ config: response.config,
99
+ }
100
+ }
101
+
102
+ async function callDashboardWidgetDataApi(
103
+ path: string,
104
+ body: Record<string, unknown>,
105
+ ): Promise<DashboardWidgetDataResponse> {
106
+ const rawResponse = await fetch(path, {
107
+ method: 'POST',
108
+ headers: {
109
+ 'Content-Type': 'application/json',
110
+ 'accept-language': localStorage.getItem('af_lang') || 'en',
111
+ },
112
+ body: JSON.stringify(body),
113
+ })
114
+
115
+ const response = await parseDashboardResponse(rawResponse)
116
+
117
+ if (!rawResponse.ok) {
118
+ throw new DashboardApiError(
119
+ response?.error || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`,
120
+ normalizeValidationErrors(response),
121
+ )
122
+ }
123
+
124
+ if (!response || response.error) {
125
+ throw new DashboardApiError(response?.error || 'Dashboard request failed', normalizeValidationErrors(response))
126
+ }
127
+
128
+ return {
129
+ widget: response.widget,
130
+ data: response.data,
131
+ }
132
+ }
133
+
134
+ export const dashboardApi = {
135
+ async getDashboardConfig(slug: string): Promise<DashboardResponse> {
136
+ return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
137
+ },
138
+
139
+ async addDashboardGroup(slug: string): Promise<DashboardResponse> {
140
+ return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
141
+ },
142
+
143
+ async moveDashboardGroup(
144
+ slug: string,
145
+ groupId: string,
146
+ direction: DashboardGroupMoveDirection,
147
+ ): Promise<DashboardResponse> {
148
+ return callDashboardApi('/adminapi/v1/dashboard/move_dashboard_group', {
149
+ slug,
150
+ groupId,
151
+ direction,
152
+ })
153
+ },
154
+
155
+ async removeDashboardGroup(slug: string, groupId: string): Promise<DashboardResponse> {
156
+ return callDashboardApi('/adminapi/v1/dashboard/remove_dashboard_group', {
157
+ slug,
158
+ groupId,
159
+ })
160
+ },
161
+
162
+ async setDashboardGroupConfig(slug: string, groupId: string, config: DashboardGroupConfig): Promise<DashboardResponse> {
163
+ return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
164
+ slug,
165
+ groupId,
166
+ config,
167
+ })
168
+ },
169
+
170
+ async addDashboardWidget(slug: string, groupId: string): Promise<DashboardResponse> {
171
+ return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_widget', {
172
+ slug,
173
+ groupId,
174
+ })
175
+ },
176
+
177
+ async moveDashboardWidget(
178
+ slug: string,
179
+ widgetId: string,
180
+ direction: DashboardWidgetMoveDirection,
181
+ ): Promise<DashboardResponse> {
182
+ return callDashboardApi('/adminapi/v1/dashboard/move_dashboard_widget', {
183
+ slug,
184
+ widgetId,
185
+ direction,
186
+ })
187
+ },
188
+
189
+ async removeDashboardWidget(slug: string, widgetId: string): Promise<DashboardResponse> {
190
+ return callDashboardApi('/adminapi/v1/dashboard/remove_dashboard_widget', {
191
+ slug,
192
+ widgetId,
193
+ })
194
+ },
195
+
196
+ async setWidgetConfig(slug: string, widgetId: string, config: DashboardWidgetConfig): Promise<DashboardResponse> {
197
+ return callDashboardApi('/adminapi/v1/dashboard/set_widget_config', {
198
+ slug,
199
+ widgetId,
200
+ config,
201
+ })
202
+ },
203
+
204
+ async getDashboardWidgetData(
205
+ slug: string,
206
+ widgetId: string,
207
+ ): Promise<DashboardWidgetDataResponse> {
208
+ return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', {
209
+ slug,
210
+ widgetId,
211
+ })
212
+ },
213
+ }
@@ -0,0 +1,41 @@
1
+ import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
2
+ import type { Ref, ShallowRef } from 'vue'
3
+
4
+ type ElementSizeState<T extends HTMLElement> = {
5
+ el: ShallowRef<T | null>
6
+ width: Ref<number>
7
+ height: Ref<number>
8
+ }
9
+
10
+ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
11
+ const el = shallowRef<T | null>(null)
12
+ const width = ref(0)
13
+ const height = ref(0)
14
+
15
+ let observer: ResizeObserver | undefined
16
+
17
+ onMounted(() => {
18
+ observer = new ResizeObserver(([entry]) => {
19
+ if (!entry) {
20
+ return
21
+ }
22
+
23
+ width.value = Math.floor(entry.contentRect.width)
24
+ height.value = Math.floor(entry.contentRect.height)
25
+ })
26
+
27
+ if (el.value) {
28
+ observer.observe(el.value)
29
+ }
30
+ })
31
+
32
+ onBeforeUnmount(() => {
33
+ observer?.disconnect()
34
+ })
35
+
36
+ return {
37
+ el: el as ShallowRef<T | null>,
38
+ width,
39
+ height,
40
+ }
41
+ }
@@ -0,0 +1,73 @@
1
+ import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js'
2
+
3
+ export type DashboardConfig = {
4
+ version: number
5
+ groups: DashboardGroupConfig[]
6
+ widgets: DashboardWidgetConfig[]
7
+ }
8
+
9
+ export type DashboardGroupConfig = {
10
+ id: string
11
+ label: string
12
+ order: number
13
+ }
14
+
15
+ export type DashboardGroupMoveDirection = 'up' | 'down'
16
+
17
+ export type DashboardWidgetMoveDirection = 'up' | 'down'
18
+
19
+ export type DashboardWidgetTarget =
20
+ | 'empty'
21
+ | 'table'
22
+ | 'chart'
23
+ | 'kpi_card'
24
+ | 'pivot_table'
25
+ | 'gauge_card'
26
+
27
+ export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full'
28
+
29
+ export type WidgetLayout = {
30
+ size?: DashboardWidgetSize
31
+ width?: number
32
+ minWidth?: number
33
+ maxWidth?: number | null
34
+ height?: number
35
+ }
36
+
37
+ export type DashboardWidgetConfig = {
38
+ id: string
39
+ group_id: string
40
+ label?: string
41
+ size?: DashboardWidgetSize
42
+ width?: number
43
+ height?: number
44
+ minWidth?: number
45
+ maxWidth?: number | null
46
+ order: number
47
+ target: DashboardWidgetTarget
48
+ chart?: ChartWidgetConfig
49
+ table?: unknown
50
+ kpi_card?: unknown
51
+ pivot_table?: unknown
52
+ gauge_card?: unknown
53
+ query?: unknown
54
+ }
55
+
56
+ export type DashboardWidgetTableData = {
57
+ columns: string[]
58
+ rows: Record<string, unknown>[]
59
+ }
60
+
61
+ export function normalizeDashboardConfig(config: unknown): DashboardConfig {
62
+ const value = isRecord(config) ? config : {}
63
+
64
+ return {
65
+ version: typeof value.version === 'number' ? value.version : 1,
66
+ groups: Array.isArray(value.groups) ? (value.groups as DashboardGroupConfig[]) : [],
67
+ widgets: Array.isArray(value.widgets) ? (value.widgets as DashboardWidgetConfig[]) : [],
68
+ }
69
+ }
70
+
71
+ function isRecord(value: unknown): value is Record<string, unknown> {
72
+ return typeof value === 'object' && value !== null
73
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "custom",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "license": "MIT",
6
+ "dependencies": {
7
+ "yaml": "^2.9.0"
8
+ }
9
+ }
@@ -0,0 +1,24 @@
1
+ lockfileVersion: '9.0'
2
+
3
+ settings:
4
+ autoInstallPeers: true
5
+ excludeLinksFromLockfile: false
6
+
7
+ importers:
8
+
9
+ .:
10
+ dependencies:
11
+ yaml:
12
+ specifier: ^2.9.0
13
+ version: 2.9.0
14
+
15
+ packages:
16
+
17
+ yaml@2.9.0:
18
+ resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==}
19
+ engines: {node: '>= 14.6'}
20
+ hasBin: true
21
+
22
+ snapshots:
23
+
24
+ yaml@2.9.0: {}
@@ -0,0 +1,51 @@
1
+ import { ref, watch, type Ref } from 'vue'
2
+ import { dashboardApi } from '../api/dashboardApi.js'
3
+
4
+ export function useDashboardConfig(slug: Ref<string>) {
5
+ const data = ref<Awaited<ReturnType<typeof dashboardApi.getDashboardConfig>> | null>(null)
6
+ const isLoading = ref(false)
7
+ const isFetching = ref(false)
8
+ const error = ref<unknown>(null)
9
+
10
+ async function refetch() {
11
+ if (!slug.value) {
12
+ data.value = null
13
+ error.value = null
14
+ return null
15
+ }
16
+
17
+ isFetching.value = true
18
+ if (data.value === null) {
19
+ isLoading.value = true
20
+ }
21
+
22
+ try {
23
+ const response = await dashboardApi.getDashboardConfig(slug.value)
24
+ data.value = response
25
+ error.value = null
26
+ return response
27
+ } catch (e) {
28
+ error.value = e
29
+ throw e
30
+ } finally {
31
+ isFetching.value = false
32
+ isLoading.value = false
33
+ }
34
+ }
35
+
36
+ watch(
37
+ slug,
38
+ () => {
39
+ void refetch()
40
+ },
41
+ { immediate: true },
42
+ )
43
+
44
+ return {
45
+ data,
46
+ isLoading,
47
+ isFetching,
48
+ error,
49
+ refetch,
50
+ }
51
+ }
@@ -0,0 +1,51 @@
1
+ import { ref, watch, type Ref } from 'vue'
2
+ import { dashboardApi } from '../api/dashboardApi.js'
3
+
4
+ export function useWidgetData(slug: Ref<string>, widgetId: Ref<string>) {
5
+ const data = ref<Awaited<ReturnType<typeof dashboardApi.getDashboardWidgetData>> | null>(null)
6
+ const isLoading = ref(false)
7
+ const isFetching = ref(false)
8
+ const error = ref<unknown>(null)
9
+
10
+ async function refetch() {
11
+ if (!slug.value || !widgetId.value) {
12
+ data.value = null
13
+ error.value = null
14
+ return null
15
+ }
16
+
17
+ isFetching.value = true
18
+ if (data.value === null) {
19
+ isLoading.value = true
20
+ }
21
+
22
+ try {
23
+ const response = await dashboardApi.getDashboardWidgetData(slug.value, widgetId.value)
24
+ data.value = response
25
+ error.value = null
26
+ return response
27
+ } catch (e) {
28
+ error.value = e
29
+ throw e
30
+ } finally {
31
+ isFetching.value = false
32
+ isLoading.value = false
33
+ }
34
+ }
35
+
36
+ watch(
37
+ [slug, widgetId],
38
+ () => {
39
+ void refetch()
40
+ },
41
+ { immediate: true },
42
+ )
43
+
44
+ return {
45
+ data,
46
+ isLoading,
47
+ isFetching,
48
+ error,
49
+ refetch,
50
+ }
51
+ }