feat: Added sitewide statistics (#7322)

This commit is contained in:
Brian Choromanski
2026-07-17 14:34:08 -04:00
committed by GitHub
parent 16a0ba2e21
commit 31187b1a74
2 changed files with 85 additions and 2 deletions

View File

@@ -940,7 +940,10 @@
"openai-ready": "OpenAI Ready", "openai-ready": "OpenAI Ready",
"openai-not-ready": "OpenAI Not Ready", "openai-not-ready": "OpenAI Not Ready",
"openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.", "openai-ready-error-text": "Not all OpenAI Values are configured. This can be ignored if you are not using OpenAI features.",
"openai-ready-success-text": "Required OpenAI variables are all set." "openai-ready-success-text": "Required OpenAI variables are all set.",
"site-statistics": "Site Statistics",
"uncategorized-recipes": "Uncategorized Recipes",
"untagged-recipes": "Untagged Recipes"
}, },
"shopping-list": { "shopping-list": {
"all-lists": "All Lists", "all-lists": "All Lists",

View File

@@ -157,6 +157,34 @@
</v-alert> </v-alert>
</section> </section>
<!-- Site Statistics -->
<section>
<BaseCardSectionTitle
class="pt-2"
:icon="$globals.icons.chart"
:title="$t('settings.site-statistics')"
/>
<div
class="d-flex flex-wrap justify-center align-center"
style="gap: 0.8rem"
>
<StatsCards
v-for="(value, key) in adminStats"
:key="`${key}-${value}`"
:min-width="$vuetify.display.xs ? '100%' : '158'"
:icon="getAdminStatsIcon(key)"
:to="getAdminStatsTo(key)"
>
<template #title>
{{ getAdminStatsTitle(key) }}
</template>
<template #value>
{{ value }}
</template>
</StatsCards>
</div>
</section>
<!-- General App Info --> <!-- General App Info -->
<section class="mt-4"> <section class="mt-4">
<BaseCardSectionTitle <BaseCardSectionTitle
@@ -234,7 +262,8 @@ import type { TranslateResult } from "vue-i18n";
import { useAdminApi, useUserApi } from "~/composables/api"; import { useAdminApi, useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators"; import { validators } from "~/composables/use-validators";
import { useAsyncKey } from "~/composables/use-utils"; import { useAsyncKey } from "~/composables/use-utils";
import type { CheckAppConfig } from "~/lib/api/types/admin"; import StatsCards from "~/components/global/StatsCards.vue";
import type { AppStatistics, CheckAppConfig } from "~/lib/api/types/admin";
import AppLoader from "~/components/global/AppLoader.vue"; import AppLoader from "~/components/global/AppLoader.vue";
interface SimpleCheck { interface SimpleCheck {
@@ -285,17 +314,68 @@ const appConfig = ref<CheckApp>({
ldapReady: false, ldapReady: false,
oidcReady: false, oidcReady: false,
}); });
const adminStats = ref<AppStatistics>({
totalRecipes: 0,
totalUsers: 0,
totalHouseholds: 0,
totalGroups: 0,
uncategorizedRecipes: 0,
untaggedRecipes: 0,
});
function isLocalHostOrHttps() { function isLocalHostOrHttps() {
return window.location.hostname === "localhost" || window.location.protocol === "https:"; return window.location.hostname === "localhost" || window.location.protocol === "https:";
} }
const api = useUserApi(); const api = useUserApi();
const adminApi = useAdminApi(); const adminApi = useAdminApi();
const adminStatsText: { [key: string]: string } = {
totalRecipes: i18n.t("general.recipes"),
totalUsers: i18n.t("user.users"),
totalHouseholds: i18n.t("household.households"),
totalGroups: i18n.t("group.groups"),
uncategorizedRecipes: i18n.t("settings.uncategorized-recipes"),
untaggedRecipes: i18n.t("settings.untagged-recipes"),
};
function getAdminStatsTitle(key: string) {
return adminStatsText[key] ?? key;
}
const adminStatsIcon: { [key: string]: string } = {
totalRecipes: $globals.icons.primary,
totalUsers: $globals.icons.user,
totalHouseholds: $globals.icons.household,
totalGroups: $globals.icons.group,
uncategorizedRecipes: $globals.icons.categories,
untaggedRecipes: $globals.icons.tags,
};
function getAdminStatsIcon(key: string) {
return adminStatsIcon[key] ?? $globals.icons.primary;
}
const adminStatsTo = computed<{ [key: string]: string }>(() => {
return {
totalUsers: "/admin/manage/users",
totalHouseholds: "/admin/manage/households",
totalGroups: "/admin/manage/groups",
};
});
function getAdminStatsTo(key: string) {
return adminStatsTo.value[key] ?? undefined;
}
onMounted(async () => { onMounted(async () => {
const { data } = await adminApi.about.checkApp(); const { data } = await adminApi.about.checkApp();
if (data) { if (data) {
appConfig.value = { ...data, isSiteSecure: false }; appConfig.value = { ...data, isSiteSecure: false };
} }
appConfig.value.isSiteSecure = isLocalHostOrHttps(); appConfig.value.isSiteSecure = isLocalHostOrHttps();
const { data: adminData } = await adminApi.about.statistics();
if (adminData) {
adminStats.value = adminData;
}
}); });
const simpleChecks = computed<SimpleCheck[]>(() => { const simpleChecks = computed<SimpleCheck[]>(() => {
const goodIcon = $globals.icons.checkboxMarkedCircle; const goodIcon = $globals.icons.checkboxMarkedCircle;