﻿<template>
  <div class="overview-page">
      <h1>Обзор</h1>

      <p v-if="error" class="error">{{ error }}</p>

      <p v-else-if="loading">Загрузка…</p>

      <template v-else-if="data">

    <div class="dashboard-cards">

      <div class="dash-card">

        <div class="dash-value">{{ data.events_last_24h }}</div>

        <div class="dash-label">Событий за 24 ч</div>

        <RouterLink to="/events">Все события →</RouterLink>

      </div>

      <div class="dash-card">

        <div class="dash-value">{{ data.problems_open }}</div>

        <div class="dash-label">Открытых проблем</div>

        <RouterLink :to="{ path: '/problems', query: { status: 'open' } }">Проблемы →</RouterLink>

      </div>

      <div class="dash-card">

        <div class="dash-value">{{ data.problems_opened_24h }}</div>

        <div class="dash-label">Новых проблем за 24 ч</div>

        <RouterLink :to="{ path: '/problems', query: { created_within_hours: '24' } }">Новые за 24 ч →</RouterLink>

      </div>

      <div class="dash-card">

        <div class="dash-value">{{ data.problems_resolved_24h }}</div>

        <div class="dash-label">Закрыто за 24 ч</div>

        <RouterLink :to="{ path: '/problems', query: { resolved_within_hours: '24' } }">Закрытые за 24 ч →</RouterLink>

      </div>

      <div class="dash-card">

        <div class="dash-value">{{ data.hosts_total }}</div>

        <div class="dash-label">Хостов</div>

        <RouterLink to="/hosts">Хосты →</RouterLink>

      </div>

      <div class="dash-card" :class="{ 'dash-warn': data.hosts_stale > 0 }">

        <div class="dash-value">{{ data.hosts_stale }}</div>

        <div class="dash-label">Устаревший heartbeat</div>

        <RouterLink :to="{ path: '/hosts', query: { agent_status: 'stale' } }">Stale-хосты →</RouterLink>

      </div>

      <div class="dash-card">

        <div class="dash-value">{{ data.heartbeats_24h }}</div>

        <div class="dash-label">Heartbeat за 24 ч</div>

        <RouterLink :to="{ path: '/events', query: { type: 'agent.heartbeat' } }">События →</RouterLink>

      </div>

      <div class="dash-card">

        <div class="dash-value">{{ data.daily_reports_24h }}</div>

        <div class="dash-label">Отчёты за 24 ч</div>

        <RouterLink to="/reports">Отчёты →</RouterLink>

      </div>

    </div>



    <div class="dashboard-panels">

      <section class="dash-panel">

        <h2>Топ хостов (24 ч)</h2>

        <div v-if="data.top_hosts.length" class="dash-table-scroll">
          <table class="dash-table">

          <thead>

            <tr>

              <th>Хост</th>

              <th>Событий</th>

            </tr>

          </thead>

          <tbody>

            <tr v-for="h in data.top_hosts" :key="h.host_id">

              <td>

                <RouterLink :to="{ path: '/events', query: { hostname: h.hostname } }">

                  {{ h.hostname }}

                </RouterLink>

              </td>

              <td>{{ h.count }}</td>

            </tr>

          </tbody>

          </table>
        </div>

        <p v-else class="muted">Нет событий за 24 ч</p>

      </section>



      <section class="dash-panel">

        <h2>Топ типов (24 ч)</h2>

        <div v-if="data.top_event_types.length" class="dash-table-scroll">
          <table class="dash-table">

          <thead>

            <tr>

              <th>Type</th>

              <th>Событий</th>

            </tr>

          </thead>

          <tbody>

            <tr v-for="t in data.top_event_types" :key="t.type">

              <td>

                <RouterLink :to="{ path: '/events', query: { type: t.type } }">

                  <code>{{ t.type }}</code>

                </RouterLink>

              </td>

              <td>{{ t.count }}</td>

            </tr>

          </tbody>

          </table>
        </div>

        <p v-else class="muted">Нет событий за 24 ч</p>

      </section>

    </div>



    <h2>Severity за 24 ч</h2>

    <ul class="severity-list">

      <li v-for="(count, sev) in data.severity_24h" :key="sev">

        <RouterLink :to="{ path: '/events', query: { severity: sev } }">

          <span :class="'sev-' + sev">{{ sev }}</span>: {{ count }}

        </RouterLink>

      </li>

      <li v-if="!Object.keys(data.severity_24h).length">Нет событий</li>

    </ul>



    <h2>Последние события</h2>

    <p class="muted dash-recent-hint">

      Обновляется при поступлении событий (live, ~5 с). Время — момент приёма SAC.

    </p>

    <table>

      <thead>

        <tr>

          <th>ID</th>

          <th>Получено</th>

          <th>Хост</th>

          <th>Имя сервера</th>

          <th>Пользователь</th>

          <th title="Длительность сессии (из session_duration_sec)">Длительность</th>

          <th>Версия агента</th>

          <th>Severity</th>

          <th>Title</th>

          <th>Действия</th>

        </tr>

      </thead>

      <tbody>

        <tr v-for="e in data.recent_events" :key="e.id">

          <td>

            <RouterLink :to="`/events/${e.id}`">{{ e.id }}</RouterLink>

          </td>

          <td>{{ formatDt(e.received_at) }}</td>

          <td>

            <RouterLink :to="{ path: '/events', query: { hostname: e.hostname } }">

              {{ e.hostname }}

            </RouterLink>

          </td>

          <td>{{ formatServerName(e.display_name) }}</td>

          <td>{{ e.actor_user || "—" }}</td>

          <td>{{ formatSessionDuration(e.session_duration_sec) }}</td>

          <td>{{ e.product_version || "—" }}</td>

          <td :class="'sev-' + e.severity">{{ e.severity }}</td>

          <td>
            <RdgAccessBadge v-if="e.rdg_access_path" :event="e" />
            <RdgFlapBadge v-if="hasRdgFlapUi(e)" :event="e" />
            {{ e.title }}
          </td>

          <td class="dash-actions">

            <button
              v-if="rdgQwinstaEventId(e)"
              type="button"
              class="secondary dash-qwinsta-btn"
              :disabled="qwinstaLoadingId === e.id"
              title="qwinsta и завершение сеанса на рабочем ПК"
              @click="runQwinsta(e)"
            >
              Обрыв RDS
            </button>
            <button
              v-if="showEventSessionTerminateButton(e)"
              type="button"
              class="secondary dash-qwinsta-btn"
              :disabled="sessionTerminateLoadingId === e.id"
              @click="runSessionTerminate(e)"
            >
              {{ sessionTerminateLoadingId === e.id ? "…" : "Завершить" }}
            </button>

          </td>

        </tr>

      </tbody>

    </table>

    <RdgQwinstaModal
      :modal="qwinstaModal"
      @close="closeQwinstaModal"
      @logoff="runLogoff"
    />

    <div class="filters" style="margin-top: 1rem">

      <button type="button" class="secondary" @click="load">Обновить всё</button>

      <span v-if="live" class="live-badge">live</span>

    </div>

      </template>
  </div>
</template>



<script setup lang="ts">

import { onMounted, onUnmounted, ref } from "vue";

import {
  apiFetch,
  ApiError,
  getToken,
  postEventTerminateSession,
  type DashboardSummary,
  type EventSummary,
} from "../api";
import RdgAccessBadge from "../components/RdgAccessBadge.vue";
import RdgFlapBadge from "../components/RdgFlapBadge.vue";
import RdgQwinstaModal from "../components/RdgQwinstaModal.vue";
import { useRdgQwinsta } from "../composables/useRdgQwinsta";
import { formatServerName } from "../utils/hostDisplay";
import { hasRdgFlapUi, rdgQwinstaEventId } from "../utils/rdgFlap";
import { formatSessionDuration } from "../utils/sessionDuration";
import { markEventSessionTerminatedInList, showEventSessionTerminateButton } from "../utils/sessionActions";

const { qwinstaLoadingId, qwinstaModal, closeQwinstaModal, runQwinsta, runLogoff } = useRdgQwinsta();
const sessionTerminateLoadingId = ref<number | null>(null);

const data = ref<DashboardSummary | null>(null);

const loading = ref(false);

const error = ref("");

const live = ref(false);

const lastKnownEventDbId = ref<number | null>(null);

let eventSource: EventSource | null = null;

let refreshingRecent = false;

function formatDt(iso: string) {

  return new Date(iso).toLocaleString("ru-RU");

}

async function runSessionTerminate(event: EventSummary) {
  const label = event.actor_user || event.title;
  if (!window.confirm(`Завершить сессию пользователя ${label}?`)) return;
  sessionTerminateLoadingId.value = event.id;
  try {
    const res = await postEventTerminateSession(event.id);
    if (!res.ok) {
      window.alert(res.message);
    } else {
      markEventSessionTerminatedInList(data.value?.recent_events, event.id);
    }
  } catch (e) {
    if (e instanceof ApiError && e.status === 409) {
      markEventSessionTerminatedInList(data.value?.recent_events, event.id);
    }
    window.alert(e instanceof Error ? e.message : "Ошибка завершения сессии");
  } finally {
    sessionTerminateLoadingId.value = null;
  }
}



function syncLastKnownFromRecent() {

  const top = data.value?.recent_events?.[0];

  lastKnownEventDbId.value = top?.id ?? null;

}



async function refreshRecentEvents() {

  if (!data.value || refreshingRecent) return;

  refreshingRecent = true;

  try {

    const events = await apiFetch<EventSummary[]>(

      "/api/v1/dashboards/recent-events?limit=8",

    );

    data.value.recent_events = events;

    syncLastKnownFromRecent();

  } catch {

    /* keep table on transient errors */

  } finally {

    refreshingRecent = false;

  }

}



async function load() {

  loading.value = true;

  error.value = "";

  try {

    data.value = await apiFetch<DashboardSummary>("/api/v1/dashboards/summary");

    syncLastKnownFromRecent();

  } catch (e) {

    error.value = e instanceof Error ? e.message : String(e);

  } finally {

    loading.value = false;

  }

}



function applyDashboardCounters(msg: {

  events_last_24h?: number;

  problems_open?: number;

  hosts_stale?: number;

  heartbeats_24h?: number;

  daily_reports_24h?: number;

  last_event_id?: number | null;

}) {

  if (!data.value) return;

  if (typeof msg.events_last_24h === "number") {

    data.value.events_last_24h = msg.events_last_24h;

  }

  if (typeof msg.problems_open === "number") {

    data.value.problems_open = msg.problems_open;

  }

  if (typeof msg.hosts_stale === "number") {

    data.value.hosts_stale = msg.hosts_stale;

  }

  if (typeof msg.heartbeats_24h === "number") {

    data.value.heartbeats_24h = msg.heartbeats_24h;

  }

  if (typeof msg.daily_reports_24h === "number") {

    data.value.daily_reports_24h = msg.daily_reports_24h;

  }



  const incoming = msg.last_event_id;

  if (incoming === undefined) return;

  const prev = lastKnownEventDbId.value;

  if (incoming === null) {

    if (prev !== null) void refreshRecentEvents();

    lastKnownEventDbId.value = null;

    return;

  }

  if (prev === null || incoming !== prev) {

    void refreshRecentEvents();

  }

  lastKnownEventDbId.value = incoming;

}



function connectLive() {

  const token = getToken();

  if (!token) return;

  eventSource?.close();

  const url = `/api/v1/stream/events`;

  eventSource = new EventSource(url);

  eventSource.onopen = () => {

    live.value = true;

  };

  eventSource.onmessage = (ev) => {

    try {

      const msg = JSON.parse(ev.data) as {

        type?: string;

        events_last_24h?: number;

        problems_open?: number;

        hosts_stale?: number;

        heartbeats_24h?: number;

        daily_reports_24h?: number;

        last_event_id?: number | null;

      };

      if (msg.type !== "dashboard" || !data.value) return;

      applyDashboardCounters(msg);

    } catch {

      /* ignore malformed SSE */

    }

  };

  eventSource.onerror = () => {

    live.value = false;

  };

}



onMounted(() => {

  load().then(() => connectLive());

});



onUnmounted(() => {
  eventSource?.close();
  eventSource = null;
});

</script>



<style scoped>

.dash-table-scroll {

  max-height: 21rem;

  overflow-y: auto;

}

.dash-recent-hint {

  margin: -0.25rem 0 0.75rem;

  font-size: 0.9rem;

}

.dash-actions {
  white-space: nowrap;
}

.dash-qwinsta-btn {
  font-size: 0.85rem;
  padding: 0.2rem 0.55rem;
}

</style>


