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"
>
-
+
+
+
+
+