@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,185 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
class="group/dashboard relative rounded-lg p-4"
|
|
4
|
+
:class="isAdmin ? 'border border-dashed border-lightListBorder dark:border-darkListBorder' : ''"
|
|
5
|
+
>
|
|
6
|
+
<header class="mb-4 flex items-start justify-between gap-4">
|
|
7
|
+
<div>
|
|
8
|
+
<h2 class="m-0 text-lg font-bold text-lightNavbarText dark:text-darkNavbarText">
|
|
9
|
+
{{ group.label }}
|
|
10
|
+
</h2>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
v-if="isAdmin"
|
|
15
|
+
class="absolute right-3 top-3 flex gap-1 opacity-0 transition-opacity group-hover/dashboard:opacity-100"
|
|
16
|
+
>
|
|
17
|
+
<button
|
|
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"
|
|
20
|
+
title="Edit JSON"
|
|
21
|
+
@click="emit('edit-group', group)"
|
|
22
|
+
>
|
|
23
|
+
<svg
|
|
24
|
+
class="h-4 w-4"
|
|
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>
|
|
36
|
+
|
|
37
|
+
<button
|
|
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"
|
|
40
|
+
title="Move up"
|
|
41
|
+
:disabled="!canMoveUp"
|
|
42
|
+
@click="emit('move-up')"
|
|
43
|
+
>
|
|
44
|
+
<svg
|
|
45
|
+
class="h-4 w-4"
|
|
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>
|
|
57
|
+
|
|
58
|
+
<button
|
|
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"
|
|
61
|
+
title="Move down"
|
|
62
|
+
:disabled="!canMoveDown"
|
|
63
|
+
@click="emit('move-down')"
|
|
64
|
+
>
|
|
65
|
+
<svg
|
|
66
|
+
class="h-4 w-4"
|
|
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>
|
|
78
|
+
|
|
79
|
+
<button
|
|
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"
|
|
82
|
+
title="Remove"
|
|
83
|
+
@click="emit('remove-group')"
|
|
84
|
+
>
|
|
85
|
+
<svg
|
|
86
|
+
class="h-4 w-4"
|
|
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>
|
|
102
|
+
</div>
|
|
103
|
+
</header>
|
|
104
|
+
|
|
105
|
+
<div class="flex flex-wrap gap-4">
|
|
106
|
+
<WidgetShell
|
|
107
|
+
v-for="(widget, index) in widgets"
|
|
108
|
+
:key="widget.id"
|
|
109
|
+
:is-admin="isAdmin"
|
|
110
|
+
:can-move-up="index > 0"
|
|
111
|
+
:can-move-down="index < widgets.length - 1"
|
|
112
|
+
:layout="{
|
|
113
|
+
size: widget.size,
|
|
114
|
+
width: widget.width,
|
|
115
|
+
minWidth: widget.minWidth,
|
|
116
|
+
maxWidth: widget.maxWidth,
|
|
117
|
+
height: widget.height,
|
|
118
|
+
}"
|
|
119
|
+
@edit="emit('edit-widget', widget)"
|
|
120
|
+
@move-up="emit('move-widget-up', widget.id)"
|
|
121
|
+
@move-down="emit('move-widget-down', widget.id)"
|
|
122
|
+
@remove="emit('remove-widget', widget.id)"
|
|
123
|
+
>
|
|
124
|
+
<WidgetRenderer
|
|
125
|
+
:widget="widget"
|
|
126
|
+
:dashboard-slug="dashboardSlug"
|
|
127
|
+
:is-admin="isAdmin"
|
|
128
|
+
/>
|
|
129
|
+
</WidgetShell>
|
|
130
|
+
|
|
131
|
+
<div
|
|
132
|
+
v-if="!widgets.length"
|
|
133
|
+
class="flex min-h-24 w-full items-center justify-center rounded-lg text-sm text-lightListTableText dark:text-darkListTableText"
|
|
134
|
+
:class="isAdmin ? 'border border-dashed border-lightListBorder dark:border-darkListBorder' : ''"
|
|
135
|
+
>
|
|
136
|
+
No widgets yet
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div
|
|
142
|
+
v-if="isAdmin"
|
|
143
|
+
class="mt-3 flex"
|
|
144
|
+
>
|
|
145
|
+
<Button
|
|
146
|
+
type="button"
|
|
147
|
+
mode="secondary"
|
|
148
|
+
class="h-10 w-28 border text-xs font-semibold"
|
|
149
|
+
@click="emit('add-widget')"
|
|
150
|
+
>
|
|
151
|
+
Add widget
|
|
152
|
+
</Button>
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
<script setup lang="ts">
|
|
160
|
+
import { Button } from '@/afcl'
|
|
161
|
+
import WidgetRenderer from './WidgetRenderer.vue'
|
|
162
|
+
import WidgetShell from './WidgetShell.vue'
|
|
163
|
+
import type { DashboardGroupConfig, DashboardWidgetConfig } from '../model/dashboard.types.js'
|
|
164
|
+
|
|
165
|
+
defineProps<{
|
|
166
|
+
group: DashboardGroupConfig
|
|
167
|
+
widgets: DashboardWidgetConfig[]
|
|
168
|
+
dashboardSlug: string
|
|
169
|
+
isAdmin: boolean
|
|
170
|
+
canMoveUp: boolean
|
|
171
|
+
canMoveDown: boolean
|
|
172
|
+
}>()
|
|
173
|
+
|
|
174
|
+
const emit = defineEmits<{
|
|
175
|
+
(e: 'add-widget'): void
|
|
176
|
+
(e: 'move-up'): void
|
|
177
|
+
(e: 'move-down'): void
|
|
178
|
+
(e: 'remove-group'): void
|
|
179
|
+
(e: 'edit-group', group: DashboardGroupConfig): void
|
|
180
|
+
(e: 'edit-widget', widget: DashboardWidgetConfig): void
|
|
181
|
+
(e: 'move-widget-up', widgetId: string): void
|
|
182
|
+
(e: 'move-widget-down', widgetId: string): void
|
|
183
|
+
(e: 'remove-widget', widgetId: string): void
|
|
184
|
+
}>()
|
|
185
|
+
</script>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-full w-full">
|
|
3
|
+
<div
|
|
4
|
+
v-if="isLoading"
|
|
5
|
+
class="p-6 text-sm text-lightListTableText dark:text-darkListTableText"
|
|
6
|
+
>
|
|
7
|
+
Loading dashboard...
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div
|
|
11
|
+
v-else-if="error"
|
|
12
|
+
class="p-6 text-sm text-lightInputErrorColor"
|
|
13
|
+
>
|
|
14
|
+
<div>Failed to load dashboard</div>
|
|
15
|
+
|
|
16
|
+
<Button
|
|
17
|
+
type="button"
|
|
18
|
+
class="mt-3"
|
|
19
|
+
@click="refetch"
|
|
20
|
+
>
|
|
21
|
+
Retry
|
|
22
|
+
</Button>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<DashboardRuntime
|
|
26
|
+
v-else-if="dashboard"
|
|
27
|
+
:dashboard-slug="dashboardSlug"
|
|
28
|
+
:dashboard-id="dashboard.id"
|
|
29
|
+
:label="dashboard.label"
|
|
30
|
+
:config="dashboard.config"
|
|
31
|
+
:revision="dashboard.revision"
|
|
32
|
+
:is-admin="isAdmin"
|
|
33
|
+
:is-refreshing="isFetching"
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<div
|
|
37
|
+
v-else
|
|
38
|
+
class="dashboard-page__state"
|
|
39
|
+
>
|
|
40
|
+
Dashboard not found
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
<script setup lang="ts">
|
|
48
|
+
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
49
|
+
import { useRoute } from 'vue-router'
|
|
50
|
+
import { Button } from '@/afcl'
|
|
51
|
+
import { useCoreStore } from '@/stores/core'
|
|
52
|
+
import websocket from '@/websocket'
|
|
53
|
+
import DashboardRuntime from './DashboardRuntime.vue'
|
|
54
|
+
import { useDashboardConfig } from '../queries/useDashboardConfig.js'
|
|
55
|
+
|
|
56
|
+
const route = useRoute()
|
|
57
|
+
const coreStore = useCoreStore()
|
|
58
|
+
|
|
59
|
+
const dashboardSlug = computed(() => {
|
|
60
|
+
const slug = route.params.slug
|
|
61
|
+
|
|
62
|
+
if (Array.isArray(slug)) {
|
|
63
|
+
return slug[0] || 'default'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (slug as string) || 'default'
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const {
|
|
70
|
+
data: dashboard,
|
|
71
|
+
isLoading,
|
|
72
|
+
isFetching,
|
|
73
|
+
error,
|
|
74
|
+
refetch,
|
|
75
|
+
} = useDashboardConfig(dashboardSlug)
|
|
76
|
+
|
|
77
|
+
const isAdmin = computed(() => {
|
|
78
|
+
return coreStore.adminUser?.dbUser.role === 'superadmin'
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX = '/opentopic/dashboard-config-updated'
|
|
82
|
+
const subscribedTopic = ref<string | null>(null)
|
|
83
|
+
|
|
84
|
+
const dashboardConfigUpdatedTopic = computed(() => {
|
|
85
|
+
return `${DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX}/${dashboardSlug.value}`
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
function handleDashboardConfigUpdated(data: { slug?: string; revision?: number }) {
|
|
89
|
+
if (data.slug && data.slug !== dashboardSlug.value) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof data.revision === 'number' && dashboard.value && data.revision <= dashboard.value.revision) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
void refetch()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function subscribeToDashboardUpdates() {
|
|
101
|
+
if (subscribedTopic.value) {
|
|
102
|
+
websocket.unsubscribe(subscribedTopic.value)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
subscribedTopic.value = dashboardConfigUpdatedTopic.value
|
|
106
|
+
websocket.subscribe(subscribedTopic.value, handleDashboardConfigUpdated)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
watch(dashboardConfigUpdatedTopic, subscribeToDashboardUpdates)
|
|
110
|
+
|
|
111
|
+
onMounted(() => {
|
|
112
|
+
subscribeToDashboardUpdates()
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
onUnmounted(() => {
|
|
116
|
+
if (!subscribedTopic.value) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
websocket.unsubscribe(subscribedTopic.value)
|
|
121
|
+
})
|
|
122
|
+
</script>
|