@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.
- package/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/README.md +59 -0
- package/custom/api/dashboardApi.ts +213 -0
- package/custom/composables/useElementSize.ts +41 -0
- package/custom/model/dashboard.types.ts +73 -0
- package/custom/package.json +9 -0
- package/custom/pnpm-lock.yaml +24 -0
- package/custom/queries/useDashboardConfig.ts +51 -0
- package/custom/queries/useWidgetData.ts +51 -0
- package/custom/runtime/DashboardGroup.vue +185 -0
- package/custom/runtime/DashboardPage.vue +122 -0
- package/custom/runtime/DashboardRuntime.vue +435 -0
- package/custom/runtime/WidgetRenderer.vue +60 -0
- package/custom/runtime/WidgetShell.vue +152 -0
- package/custom/skills/adminforth-dashboard/SKILL.md +125 -0
- package/custom/widgets/chart/ChartWidget.vue +188 -0
- package/custom/widgets/chart/bar/BarChart.vue +167 -0
- package/custom/widgets/chart/chart.types.ts +34 -0
- package/custom/widgets/chart/chart.utils.ts +54 -0
- package/custom/widgets/chart/funnel/FunnelChart.vue +197 -0
- package/custom/widgets/chart/histogram/HistogramChart.vue +21 -0
- package/custom/widgets/chart/line/LineChart.vue +175 -0
- package/custom/widgets/chart/pie/PieChart.vue +161 -0
- package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +256 -0
- package/custom/widgets/gauge-card/GaugeCardWidget.vue +107 -0
- package/custom/widgets/kpi-card/KpiCardWidget.vue +73 -0
- package/custom/widgets/pivot-table/PivotTableWidget.vue +122 -0
- package/custom/widgets/registry.ts +51 -0
- package/custom/widgets/table/TableWidget.vue +110 -0
- package/dist/custom/api/dashboardApi.d.ts +32 -0
- package/dist/custom/api/dashboardApi.js +179 -0
- package/dist/custom/api/dashboardApi.ts +213 -0
- package/dist/custom/composables/useElementSize.d.ts +8 -0
- package/dist/custom/composables/useElementSize.js +30 -0
- package/dist/custom/composables/useElementSize.ts +41 -0
- package/dist/custom/model/dashboard.types.d.ts +45 -0
- package/dist/custom/model/dashboard.types.js +14 -0
- package/dist/custom/model/dashboard.types.ts +73 -0
- package/dist/custom/package.json +9 -0
- package/dist/custom/pnpm-lock.yaml +24 -0
- package/dist/custom/queries/useDashboardConfig.d.ts +112 -0
- package/dist/custom/queries/useDashboardConfig.js +57 -0
- package/dist/custom/queries/useDashboardConfig.ts +51 -0
- package/dist/custom/queries/useWidgetData.d.ts +90 -0
- package/dist/custom/queries/useWidgetData.js +57 -0
- package/dist/custom/queries/useWidgetData.ts +51 -0
- package/dist/custom/runtime/DashboardGroup.vue +185 -0
- package/dist/custom/runtime/DashboardPage.vue +122 -0
- package/dist/custom/runtime/DashboardRuntime.vue +435 -0
- package/dist/custom/runtime/WidgetRenderer.vue +60 -0
- package/dist/custom/runtime/WidgetShell.vue +152 -0
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +125 -0
- package/dist/custom/widgets/chart/ChartWidget.vue +188 -0
- package/dist/custom/widgets/chart/bar/BarChart.vue +167 -0
- package/dist/custom/widgets/chart/chart.types.d.ts +25 -0
- package/dist/custom/widgets/chart/chart.types.js +2 -0
- package/dist/custom/widgets/chart/chart.types.ts +34 -0
- package/dist/custom/widgets/chart/chart.utils.d.ts +5 -0
- package/dist/custom/widgets/chart/chart.utils.js +52 -0
- package/dist/custom/widgets/chart/chart.utils.ts +54 -0
- package/dist/custom/widgets/chart/funnel/FunnelChart.vue +197 -0
- package/dist/custom/widgets/chart/histogram/HistogramChart.vue +21 -0
- package/dist/custom/widgets/chart/line/LineChart.vue +175 -0
- package/dist/custom/widgets/chart/pie/PieChart.vue +161 -0
- package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +256 -0
- package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +107 -0
- package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +73 -0
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +122 -0
- package/dist/custom/widgets/registry.d.ts +11 -0
- package/dist/custom/widgets/registry.js +47 -0
- package/dist/custom/widgets/registry.ts +51 -0
- package/dist/custom/widgets/table/TableWidget.vue +110 -0
- package/dist/endpoint/dashboard.d.ts +7 -0
- package/dist/endpoint/dashboard.js +29 -0
- package/dist/endpoint/groups.d.ts +30 -0
- package/dist/endpoint/groups.js +131 -0
- package/dist/endpoint/widgets.d.ts +15 -0
- package/dist/endpoint/widgets.js +182 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +124 -0
- package/dist/schema/api.d.ts +1205 -0
- package/dist/schema/api.js +84 -0
- package/dist/schema/widget.d.ts +514 -0
- package/dist/schema/widget.js +133 -0
- package/dist/services/dashboardConfigService.d.ts +35 -0
- package/dist/services/dashboardConfigService.js +79 -0
- package/dist/services/widgetConfigValidator.d.ts +8 -0
- package/dist/services/widgetConfigValidator.js +65 -0
- package/dist/services/widgetDataService.d.ts +20 -0
- package/dist/services/widgetDataService.js +32 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +1 -0
- package/endpoint/dashboard.ts +32 -0
- package/endpoint/groups.ts +213 -0
- package/endpoint/widgets.ts +255 -0
- package/index.ts +141 -0
- package/package.json +64 -0
- package/schema/api.ts +99 -0
- package/schema/widget.ts +159 -0
- package/services/dashboardConfigService.ts +136 -0
- package/services/widgetConfigValidator.ts +93 -0
- package/services/widgetDataService.ts +57 -0
- package/shims-vue.d.ts +5 -0
- package/tsconfig.json +18 -0
- 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,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
|
+
}
|