@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,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>