diff --git a/src/admin/package-lock.json b/src/admin/package-lock.json index 1b6d4fe..696cf9e 100755 --- a/src/admin/package-lock.json +++ b/src/admin/package-lock.json @@ -12,10 +12,12 @@ "@iconify-json/ri": "^1.2.0", "@iconify/vue": "^4.1.0", "@vueuse/core": "^11.0.0", + "apexcharts": "^5.15.0", "axios": "^1.15.0", "pinia": "^3.0.4", "vue": "^3.4.0", "vue-router": "^4.3.0", + "vue3-apexcharts": "^1.11.1", "vue3-perfect-scrollbar": "^2.0.0", "vuetify": "^3.7.5" }, @@ -4281,6 +4283,11 @@ "node": ">= 8" } }, + "node_modules/apexcharts": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.15.0.tgz", + "integrity": "sha512-ATswoiZi8wynKcwqf0eyE0Is5mBQoDpxZN3PlSVy41OrD+9GMBp2kZQRjyGK+5/j0GFjHkuAd7GKOrt5VMoPrw==" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -11130,6 +11137,20 @@ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", "license": "MIT" }, + "node_modules/vue3-apexcharts": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/vue3-apexcharts/-/vue3-apexcharts-1.11.1.tgz", + "integrity": "sha512-MbN3vg8bMG19wc0Lm1HkeQvODgLm56DgpIxtNUO0xpf/JCzYWVGE4jzXp2JISzy2s3Kul1yOxNQUYsLvKQ5L9g==", + "peerDependencies": { + "apexcharts": ">=5.10.0", + "vue": ">=3.0.0" + }, + "peerDependenciesMeta": { + "apexcharts": { + "optional": false + } + } + }, "node_modules/vue3-perfect-scrollbar": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/vue3-perfect-scrollbar/-/vue3-perfect-scrollbar-2.0.0.tgz", diff --git a/src/admin/package.json b/src/admin/package.json index 004615d..72e0458 100755 --- a/src/admin/package.json +++ b/src/admin/package.json @@ -14,10 +14,12 @@ "@iconify-json/ri": "^1.2.0", "@iconify/vue": "^4.1.0", "@vueuse/core": "^11.0.0", + "apexcharts": "^5.15.0", "axios": "^1.15.0", "pinia": "^3.0.4", "vue": "^3.4.0", "vue-router": "^4.3.0", + "vue3-apexcharts": "^1.11.1", "vue3-perfect-scrollbar": "^2.0.0", "vuetify": "^3.7.5" }, diff --git a/src/admin/src/api/usage.ts b/src/admin/src/api/usage.ts new file mode 100644 index 0000000..0d1fd71 --- /dev/null +++ b/src/admin/src/api/usage.ts @@ -0,0 +1,7 @@ +import apiClient from './client'; +import type { DashboardUsageResponse } from '@/types/api'; + +export async function getUsageDashboard(environmentId: number, days = 14): Promise { + const { data } = await apiClient.get(`/v1/environments/${environmentId}/usage`, { params: { days } }); + return data; +} diff --git a/src/admin/src/components/dashboard/DailyUsageChart.vue b/src/admin/src/components/dashboard/DailyUsageChart.vue new file mode 100644 index 0000000..77aa5a9 --- /dev/null +++ b/src/admin/src/components/dashboard/DailyUsageChart.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/src/admin/src/components/dashboard/TopFeaturesChart.vue b/src/admin/src/components/dashboard/TopFeaturesChart.vue new file mode 100644 index 0000000..7793a42 --- /dev/null +++ b/src/admin/src/components/dashboard/TopFeaturesChart.vue @@ -0,0 +1,49 @@ + + + diff --git a/src/admin/src/main.ts b/src/admin/src/main.ts index f3f94a8..903bf09 100755 --- a/src/admin/src/main.ts +++ b/src/admin/src/main.ts @@ -1,5 +1,6 @@ import { createApp } from 'vue'; import { createPinia } from 'pinia'; +import VueApexCharts from 'vue3-apexcharts'; import App from './App.vue'; // Styles @@ -15,6 +16,7 @@ const pinia = createPinia(); app.use(pinia); app.use(router); +app.use(VueApexCharts); installVuetify(app); // Restore persisted auth and context state before the first route navigation diff --git a/src/admin/src/types/api.ts b/src/admin/src/types/api.ts index ae820b3..2afeef2 100755 --- a/src/admin/src/types/api.ts +++ b/src/admin/src/types/api.ts @@ -476,6 +476,25 @@ export interface FlagResponse { featureStateValue: string | null; } +// ─── Feature Usage ───────────────────────────────────────────────────────────── + +export interface TopFeatureUsage { + featureId: number; + featureName: string; + count: number; +} + +export interface DailyUsage { + date: string; + totalCount: number; + features: TopFeatureUsage[]; +} + +export interface DashboardUsageResponse { + topFeaturesLastDay: TopFeatureUsage[]; + dailyUsage: DailyUsage[]; +} + // ─── Errors ─────────────────────────────────────────────────────────────────── export interface ApiValidationError { diff --git a/src/admin/src/views/DashboardView.vue b/src/admin/src/views/DashboardView.vue index ec95474..bf3e10b 100755 --- a/src/admin/src/views/DashboardView.vue +++ b/src/admin/src/views/DashboardView.vue @@ -71,6 +71,61 @@ + + +