diff --git a/app/api/routes.py b/app/api/routes.py index 124f4a9..bf156a8 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -44,9 +44,23 @@ sub: str email: str | None system_role: str + # Профиль gnexus-auth (scope profile) — для карточки пользователя в SPA. + username: str | None = None + display_name: str | None = None + avatar_url: str | None = None + # Страница профиля на сервере gnexus-auth (ссылка из навигации). + account_url: str = "" @router.get("/v1/admin/me") def admin_me(user: AuthenticatedUser = Depends(require_admin)) -> AdminMe: """Проверка гейта админки: 401 без токена, 403 без роли admin.""" - return AdminMe(sub=str(user.user_id), email=user.email, system_role=user.system_role) \ No newline at end of file + return AdminMe( + sub=str(user.user_id), + email=user.email, + system_role=user.system_role or "unknown", + username=user.profile.get("username"), + display_name=user.profile.get("display_name"), + avatar_url=user.profile.get("avatar_url"), + account_url=f"{get_settings().gauth_base_url.rstrip('/')}/account/profile", + ) \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 31976e1..94918f1 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -13,7 +13,7 @@ // подсветку активного пункта навигации. import { computed, onUnmounted, ref, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; -import { GnNavigationShell, GnUserCard } from "gnexus-ui-kit/vue"; +import { GnIdentity, GnIconButton, GnNavigationShell } from "gnexus-ui-kit/vue"; import { adminMe, clearToken, logout } from "./api.js"; const route = useRoute(); @@ -32,8 +32,13 @@ const me = ref(null); let heartbeatTimer = null; +// Имя для карточки: display_name -> username -> email (userinfo profile scope). +const displayName = computed( + () => me.value?.display_name || me.value?.username || me.value?.email || "", +); + const initials = computed(() => { - const src = me.value?.email || me.value?.sub || ""; + const src = displayName.value || me.value?.email || ""; return src .split(/[.\s@_]+/) .filter(Boolean) @@ -42,6 +47,16 @@ .join(""); }); +// Аватар identity: картинка из gnexus-auth или initials (GnAvatar рендерит +// их строкой сам). size sm + статус online — как в демо каркаса кита. +const identityAvatar = computed(() => ({ + size: "sm", + status: "online", + ...(me.value?.avatar_url + ? { src: me.value.avatar_url, alt: displayName.value } + : { initials: initials.value }), +})); + function stopHeartbeat() { if (heartbeatTimer) { clearInterval(heartbeatTimer); @@ -112,13 +127,27 @@ :current="current" >