Newer
Older
gn-synapse / frontend / src / views / DashboardView.vue
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { useRouter } from "vue-router";
import { GnButton, GnMetricCard, GnStatusCard, GnLoader } from "gnexus-ui-kit/vue";
import { adminMe, clearToken, healthz, logout } from "../api.js";

const router = useRouter();

async function signOut() {
  await logout();
  router.push("/login");
}

const me = ref(null);
const health = ref(null);
const error = ref(true); // пока не загружено — показать лоадер, не ошибку
let heartbeatTimer = null;

onMounted(async () => {
  try {
    [me.value, health.value] = await Promise.all([adminMe(), healthz()]);
    error.value = false;
  } catch (ex) {
    // Роль ниже admin — сервер не пустит: показываем экран отсечения.
    if (ex.status === 403) {
      router.push("/denied");
      return;
    }
    error.value = `Сервис недоступен: ${ex.message}`;
  }
  // Single sign-out: пользователь вышел из gnexus-auth -> наш вебхук отзывает
  // его токены -> здесь 401, и SPA уходит на /login сама.
  heartbeatTimer = setInterval(async () => {
    try {
      await adminMe();
    } catch (ex) {
      clearInterval(heartbeatTimer);
      if (ex.status === 403) router.push("/denied");
      else if (ex.status === 401) {
        clearToken();
        router.push("/login");
      }
    }
  }, 45000);
});

onUnmounted(() => {
  if (heartbeatTimer) clearInterval(heartbeatTimer);
});
</script>

<template>
  <div class="dash">
    <h2 class="page-title">Обзор</h2>

    <GnLoader v-if="error === true" />
    <GnStatusCard v-else-if="typeof error === 'string'" variant="error" icon="ph-warning-circle" :text="error" />

    <template v-else>
      <div class="metrics">
        <GnMetricCard label="Сервис" :value="health.status" icon="ph-broadcast" />
        <GnMetricCard label="Вы вошли как" :value="me.email || me.sub" icon="ph-user" :meta="me.system_role" />
      </div>
      <GnStatusCard
        variant="info"
        icon="ph-info"
        title="Каркас"
        text="Дальше: схема БД (#34), API событий (#32), Ingestion (#31), Routing Engine (#30), доставки (#29, #28)."
      />
      <GnButton variant="secondary" icon="ph-sign-out" @click="signOut">Выйти</GnButton>
    </template>
  </div>
</template>

<style scoped>
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--gn-space-4);
}
.page-title {
  font-size: var(--gn-font-size-lg);
  font-weight: var(--gn-font-weight-bold);
  text-transform: uppercase;
  margin-bottom: var(--gn-space-4);
}
</style>