<!--
  组件名称：MonitorPanel.vue
  组件描述：性能测试实时监控面板 - TPS/响应时间/状态码实时图表

  @author czj
  @date 2026-08-12
-->

<template>
  <div class="monitor-panel">
    <!-- 无任务时提示 → 提供任务选择器 + 执行历史 -->
    <div v-if="!taskId && !executionId" class="empty-state">
      <el-icon :size="48"><Monitor /></el-icon>
      <p style="margin-bottom: 16px;">请选择要查看实时监控的任务</p>
      <el-select
        v-model="selectedTaskId"
        placeholder="选择任务 ID"
        filterable
        clearable
        style="width: 360px"
        @change="onTaskSelected"
      >
        <el-option
          v-for="t in availableTasks"
          :key="t.id"
          :label="`${t.name} (${t.id.slice(0, 8)}...)`"
          :value="t.id"
        />
      </el-select>
      <el-button
        v-if="selectedTaskId"
        type="primary"
        style="margin-left: 12px;"
        @click="onTaskSelected(selectedTaskId)"
      >查看监控</el-button>
      <p v-if="tasksLoading" style="margin-top: 12px; color: #909399; font-size: 13px;">
        加载中...
      </p>

      <!-- 最近执行记录 -->
      <div style="width: 100%; max-width: 960px; margin-top: 32px;">
        <h4 style="margin-bottom: 12px; color: #303133;">最近执行记录</h4>
        <el-table
          :data="recentExecutions"
          v-loading="loadingExecutions"
          size="small"
          stripe
          border
          empty-text="暂无执行记录"
          style="width: 100%"
        >
          <el-table-column prop="taskName" label="任务名" min-width="140" show-overflow-tooltip />
          <el-table-column prop="status" label="状态" width="100">
            <template #default="{ row }">
              <el-tag :type="statusTagType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="开始时间" width="170">
            <template #default="{ row }">{{ formatTime(row.startTime) }}</template>
          </el-table-column>
          <el-table-column label="时长" width="80">
            <template #default="{ row }">{{ row.durationActual ? `${row.durationActual.toFixed(0)}s` : '-' }}</template>
          </el-table-column>
          <el-table-column label="TPS" width="80">
            <template #default="{ row }">{{ row.actualTps != null ? row.actualTps.toFixed(1) : '-' }}</template>
          </el-table-column>
          <el-table-column label="错误率" width="80">
            <template #default="{ row }">
              <span :style="{ color: (row.errorRate || 0) > 5 ? '#f56c6c' : '#67c23a' }">
                {{ row.errorRate != null ? `${(row.errorRate * 100).toFixed(1)}%` : '-' }}
              </span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="210" fixed="right">
            <template #default="{ row }">
              <el-button
                v-if="row.status === 'running'"
                link type="primary" size="small"
                @click="router.push({ path: '/performance/monitor', query: { taskId: row.taskId, executionId: row.id } })"
              >实时监控</el-button>
              <el-button
                v-if="row.status === 'completed' || row.status === 'failed'"
                link type="primary" size="small"
                @click="router.push({ path: '/performance/monitor', query: { taskId: row.taskId, executionId: row.id } })"
              >监控回放</el-button>
              <el-button
                v-if="row.status === 'completed' || row.status === 'failed'"
                link type="primary" size="small"
                @click="router.push({ path: '/performance/report', query: { taskId: row.taskId, executionId: row.id } })"
              >查看报告</el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-pagination
          v-if="executionTotal > 20"
          small
          layout="prev, pager, next"
          :total="executionTotal"
          :page-size="20"
          :current-page="executionPage"
          @current-change="loadExecutions"
          style="margin-top: 12px; justify-content: center;"
        />
      </div>
    </div>

    <template v-else>
      <!-- 监控头部 -->
      <div class="monitor-header">
        <div class="task-info">
          <span class="label">任务ID：</span><span class="value">{{ taskId }}</span>
          <span v-if="executionId" class="label" style="margin-left: 12px;">执行ID：</span>
          <span v-if="executionId" class="value">{{ executionId.slice(0, 18) }}...</span>
          <el-tag v-if="isReplayMode" type="info" size="small" style="margin-left: 8px;">回放模式</el-tag>
          <el-tag v-else-if="isLiveMode" type="success" size="small" style="margin-left: 8px;">实时监控</el-tag>
        </div>
        <div class="status-bar">
          <span class="status-dot" :class="connectionStatus" />
          <span>{{ connectionStatusText }}</span>
          <el-tag v-if="taskStatus" :type="statusTagType" size="small" style="margin-left: 8px">
            {{ statusLabel(taskStatus) }}
          </el-tag>
        </div>
      </div>

      <!-- 完成横幅：执行完成后显示倒计时自动跳转报告 -->
      <el-alert
        v-if="showCompleteBanner"
        :type="completeStatus === 'failed' ? 'error' : 'success'"
        :closable="false"
        show-icon
        style="margin-bottom: 12px;"
      >
        <template #title>
          <span>性能测试已完成{{ completeStatus === 'failed' ? '（执行失败）' : '' }}</span>
          <span style="margin-left: 16px;">
            <el-button link type="primary" size="small" @click="navigateToReport">
              前往查看报告（{{ countdown }}s 后自动跳转）
            </el-button>
            <el-button link type="info" size="small" @click="cancelAutoNavigate">取消</el-button>
          </span>
        </template>
      </el-alert>

      <!-- 实时指标卡片 -->
      <el-row :gutter="12" class="metrics-row">
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentTps.toFixed(1) }}</div>
            <div class="metric-label">TPS（当前吞吐量）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentAvgRt.toFixed(0) }}ms</div>
            <div class="metric-label">Avg Response Time（平均响应时间）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ totalRequests }}</div>
            <div class="metric-label">Total Requests（总请求数）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value" :style="{ color: errorRate > 5 ? '#f56c6c' : '#67c23a' }">
              {{ errorRate.toFixed(1) }}%
            </div>
            <div class="metric-label">Error Rate（错误率）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentPeakTps.toFixed(1) }}</div>
            <div class="metric-label">Peak TPS（峰值吞吐量）</div>
          </el-card>
        </el-col>
        <el-col :span="4">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentInflight }}</div>
            <div class="metric-label">Concurrent Users（实时并发数）</div>
          </el-card>
        </el-col>
      </el-row>

      <!-- TPS 趋势图 -->
      <el-card shadow="never" class="chart-card">
        <template #header><span>TPS 趋势（吞吐量趋势）</span></template>
        <div ref="tpsChartRef" class="chart" />
      </el-card>

      <!-- 响应时间图 + 状态码分布 -->
      <el-row :gutter="12">
        <el-col :span="16">
          <el-card shadow="never" class="chart-card">
            <template #header><span>响应时间趋势 Response Time（含 P95）</span></template>
            <div ref="rtChartRef" class="chart" />
          </el-card>
        </el-col>
        <el-col :span="8">
          <el-card shadow="never" class="chart-card">
            <template #header><span>状态码分布 Status Code</span></template>
            <div ref="statusChartRef" class="chart" />
          </el-card>
        </el-col>
      </el-row>

      <!-- 延迟/带宽 + 并发 -->
      <el-row :gutter="12">
        <el-col :span="16">
          <el-card shadow="never" class="chart-card">
            <template #header><span>延迟与连接时间趋势 Latency &amp; Connect Time</span></template>
            <div ref="latencyChartRef" class="chart" />
          </el-card>
        </el-col>
        <el-col :span="8">
          <el-card shadow="never" class="chart-card">
            <template #header><span>并发数趋势 Concurrency</span></template>
            <div ref="inflightChartRef" class="chart" />
          </el-card>
        </el-col>
      </el-row>

      <!-- 执行机资源区块（默认折叠） -->
      <el-collapse v-model="resourceExpanded" style="margin-bottom: 12px;">
        <el-collapse-item title="执行机资源 Resource Monitor" name="resource">
          <!-- 资源指标卡片 -->
          <el-row :gutter="12" class="metrics-row">
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value" :style="{ color: currentCpu > 85 ? '#f56c6c' : '#409eff' }">
                  {{ currentCpu.toFixed(1) }}%
                </div>
                <div class="metric-label">CPU Usage（CPU 使用率）</div>
              </el-card>
            </el-col>
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value" :style="{ color: currentMem > 85 ? '#f56c6c' : '#409eff' }">
                  {{ currentMem.toFixed(1) }}%
                </div>
                <div class="metric-label">Memory Usage（内存使用率）</div>
              </el-card>
            </el-col>
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value">{{ currentNetSent.toFixed(1) }}</div>
                <div class="metric-label">Network Sent（发送 KB/s）</div>
              </el-card>
            </el-col>
            <el-col :span="6">
              <el-card shadow="never" class="metric-card">
                <div class="metric-value">{{ currentNetRecv.toFixed(1) }}</div>
                <div class="metric-label">Network Received（接收 KB/s）</div>
              </el-card>
            </el-col>
          </el-row>
          <!-- 资源趋势图（全宽堆叠） -->
          <el-card shadow="never" class="chart-card">
            <template #header><span>CPU &amp; 内存趋势 CPU / Memory</span></template>
            <div ref="resourceChartRef" class="chart" />
          </el-card>
          <el-card shadow="never" class="chart-card">
            <template #header><span>网络趋势 Network（发送 / 接收 KB/s）</span></template>
            <div ref="networkChartRef" class="chart" />
          </el-card>
        </el-collapse-item>
      </el-collapse>

      <!-- 目标机资源区块（默认折叠） -->
      <el-collapse v-model="targetResourceExpanded" style="margin-bottom: 12px;">
        <el-collapse-item title="目标机资源 Target Resource Monitor" name="targetResource">
          <div v-show="targetAvailable" class="target-resource-wrap">
            <!-- 目标机信息 + 资源指标卡片 -->
            <el-row :gutter="12" class="metrics-row">
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value" :style="{ color: targetCpu > 85 ? '#f56c6c' : '#409eff' }">
                    {{ targetCpu.toFixed(1) }}%
                  </div>
                  <div class="metric-label">CPU Usage（目标机 CPU 使用率）</div>
                </el-card>
              </el-col>
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value" :style="{ color: targetMem > 85 ? '#f56c6c' : '#409eff' }">
                    {{ targetMem.toFixed(1) }}%
                  </div>
                  <div class="metric-label">Memory Usage（目标机内存使用率）{{ targetMemBar }}</div>
                </el-card>
              </el-col>
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value">{{ targetLoadAvg1.toFixed(2) }}</div>
                  <div class="metric-label">Load Average 1m（1 分钟平均负载）</div>
                </el-card>
              </el-col>
              <el-col :span="6">
                <el-card shadow="never" class="metric-card">
                  <div class="metric-value">{{ targetMemUsedMb.toFixed(0) }}MB</div>
                  <div class="metric-label">Memory Used（已用内存 {{ targetMemTotalMb.toFixed(0) }}MB）</div>
                </el-card>
              </el-col>
            </el-row>
            <div class="target-host-info">
              <el-tag size="small" type="info" effect="plain">SSH {{ targetHost }}</el-tag>
              <span class="target-host-hint">目标机资源监控（SSH 连接 {{ targetHost }}）</span>
            </div>
            <!-- 目标机资源趋势图 -->
            <el-card shadow="never" class="chart-card">
              <template #header><span>目标机 CPU &amp; 内存趋势 CPU / Memory / Load</span></template>
              <div ref="targetResourceChartRef" class="chart" />
            </el-card>

            <!-- 目标机 Java 进程占用（仅匹配到 Java 服务时展示） -->
            <template v-if="javaProcessAvailable">
              <el-card shadow="never" class="chart-card">
                <template #header>
                  <span>Java 服务进程占用 Java Process Usage（仅匹配会议相关服务）</span>
                </template>
                <el-table :data="targetJavaProcesses" size="small" stripe border>
                  <el-table-column prop="processName" label="服务 Process" min-width="200" show-overflow-tooltip />
                  <el-table-column prop="pid" label="PID" width="100" />
                  <el-table-column label="CPU %（单核 100%）" width="160">
                    <template #default="{ row }">
                      <span :style="{ color: row.cpuPercent > 80 ? '#f56c6c' : '#409eff', fontWeight: 600 }">
                        {{ row.cpuPercent.toFixed(1) }}%
                      </span>
                    </template>
                  </el-table-column>
                  <el-table-column label="Memory %" width="140">
                    <template #default="{ row }">
                      <span :style="{ color: row.memPercent > 50 ? '#f56c6c' : '#409eff', fontWeight: 600 }">
                        {{ row.memPercent.toFixed(1) }}%
                      </span>
                    </template>
                  </el-table-column>
                  <el-table-column label="RSS（常驻内存 MB）" width="180">
                    <template #default="{ row }">{{ row.rssMb.toFixed(0) }} MB</template>
                  </el-table-column>
                </el-table>
              </el-card>
            </template>
          </div>
          <el-empty
            v-show="!targetAvailable"
            description="目标机资源监控数据不可用（SSH 连接失败或未开启）"
            :image-size="80"
          />
        </el-collapse-item>
      </el-collapse>
    </template>
  </div>
</template>

<script setup lang="ts">
/**
 * 性能测试实时监控面板
 *
 * 功能：WebSocket 实时接收快照数据，ECharts 动态展示 TPS/响应时间/状态码
 */

import { ref, watch, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Monitor } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { createPerfMonitorStream, listTasks, listExecutions, getExecutionSnapshots } from '@/api/performance'
import type { EChartsOption } from 'echarts'
import type { PerformanceTaskListItem, PerformanceExecutionListItem } from '@/types/performance'

const route = useRoute()
const router = useRouter()
/** 从路由 query 读取任务 ID（由任务列表监控按钮跳转携带） */
const taskId = computed(() => (route.query.taskId as string) || '')

/** 从路由 query 读取执行记录 ID（由任务列表执行跳转或报告页切换携带） */
const executionId = computed(() => (route.query.executionId as string) || '')

/** 是否正在运行中（由 WebSocket 消息控制） */
const isRunning = ref(false)

/** 计算当前模式 */
const isReplayMode = computed(() => !!executionId.value && !isRunning.value)
const isLiveMode = computed(() => !!executionId.value && isRunning.value)

/** 完成横幅状态 */
const showCompleteBanner = ref(false)
const completeStatus = ref('')
const countdown = ref(3)
let countdownTimer: ReturnType<typeof setInterval> | null = null
let hasReceivedWsData = false

/** 格式化时间 */
function formatTime(t: string | null | undefined): string {
  if (!t) return '-'
  try {
    const d = new Date(t)
    return d.toLocaleString('zh-CN', { hour12: false })
  } catch { return t }
}

/** 执行历史列表状态 */
const recentExecutions = ref<PerformanceExecutionListItem[]>([])
const loadingExecutions = ref(false)
const executionTotal = ref(0)
const executionPage = ref(1)

/** 加载执行历史列表 */
async function loadExecutions(page = 1) {
  loadingExecutions.value = true
  try {
    const params: any = { page, pageSize: 20 }
    if (taskId.value) params.taskId = taskId.value
    const res = await listExecutions(params)
    recentExecutions.value = res.items || []
    executionTotal.value = res.total || 0
    executionPage.value = page
  } catch { /* ignore */ } finally {
    loadingExecutions.value = false
  }
}

/** 加载回放数据（从 API 获取历史快照） */
async function loadReplayData() {
  if (!executionId.value) return
  connectionStatus.value = 'connecting'
  try {
    const res = await getExecutionSnapshots(executionId.value, { pageSize: 5000 })
    const snapshots = res.items || []
    if (snapshots.length === 0) {
      connectionStatus.value = 'disconnected'
      ElMessage.info('该执行记录无快照数据')
      return
    }
    // 清空数据缓冲区
    timeLabels.value = []
    tpsData.value = []
    avgRtData.value = []
    p50Data.value = []
    p90Data.value = []
    p95Data.value = []
    p99Data.value = []
    latencyAvgData.value = []
    connectTimeAvgData.value = []
    inflightData.value = []
    status2xx.value = 0
    status4xx.value = 0
    status5xx.value = 0
    currentTps.value = 0
    currentAvgRt.value = 0
    currentPeakTps.value = 0
    currentInflight.value = 0
    totalRequests.value = 0
    errorRate.value = 0
    resourceTimeLabels.value = []
    cpuData.value = []
    memData.value = []
    netSentData.value = []
    netRecvData.value = []
    currentCpu.value = 0
    currentMem.value = 0
    currentNetSent.value = 0
    currentNetRecv.value = 0
    targetResourceTimeLabels.value = []
    targetCpuData.value = []
    targetMemData.value = []
    targetLoadData.value = []
    targetCpu.value = 0
    targetMem.value = 0
    targetMemUsedMb.value = 0
    targetMemTotalMb.value = 0
    targetLoadAvg1.value = 0
    targetAvailable.value = false
    targetHost.value = ''
    targetJavaProcesses.value = []
    javaProcessAvailable.value = false

    for (const s of snapshots) {
      const elapsed = s.elapsed != null ? `${s.elapsed.toFixed(0)}s` : `${timeLabels.value.length + 1}s`
      timeLabels.value.push(elapsed)
      tpsData.value.push(s.tps || 0)
      avgRtData.value.push(s.avgResponseTime || 0)
      p50Data.value.push(s.p50ResponseTime || 0)
      p90Data.value.push(s.p90ResponseTime || 0)
      p95Data.value.push(s.p95ResponseTime || 0)
      p99Data.value.push(s.p99ResponseTime || 0)
      latencyAvgData.value.push(s.latencyAvg || 0)
      connectTimeAvgData.value.push(s.connectTimeAvg || 0)
      inflightData.value.push(s.concurrentUsers || 0)
      status2xx.value += s.status2xx || 0
      status4xx.value += s.status4xx || 0
      status5xx.value += s.status5xx || 0
      currentTps.value = s.tps || 0
      currentAvgRt.value = s.avgResponseTime || 0
      currentPeakTps.value = Math.max(currentPeakTps.value, s.peakTps || 0)
      currentInflight.value = s.concurrentUsers || 0
      totalRequests.value += (s.successCount || 0) + (s.failCount || 0)
      errorRate.value = totalRequests.value > 0
        ? ((s.failCount || 0) / ((s.successCount || 0) + (s.failCount || 0))) * 100
        : 0

      // 资源数据
      const r = s.resource
      if (r) {
        resourceTimeLabels.value.push(elapsed)
        cpuData.value.push(r.cpuPercent || 0)
        memData.value.push(r.memPercent || 0)
        netSentData.value.push(r.netSentKbS || 0)
        netRecvData.value.push(r.netRecvKbS || 0)
        currentCpu.value = r.cpuPercent || 0
        currentMem.value = r.memPercent || 0
        currentNetSent.value = r.netSentKbS || 0
        currentNetRecv.value = r.netRecvKbS || 0
      }

      // 目标机资源数据
      const tr = s.targetResource
      if (tr) {
        targetResourceTimeLabels.value.push(elapsed)
        targetCpuData.value.push(tr.cpuPercent || 0)
        targetMemData.value.push(tr.memPercent || 0)
        targetLoadData.value.push(tr.loadAvg1 || 0)
        targetCpu.value = tr.cpuPercent || 0
        targetMem.value = tr.memPercent || 0
        targetMemUsedMb.value = tr.memUsedMb || 0
        targetMemTotalMb.value = tr.memTotalMb || 0
        targetLoadAvg1.value = tr.loadAvg1 || 0
        targetAvailable.value = tr.available !== false
        targetHost.value = tr.host || ''
        targetJavaProcesses.value = (tr.javaProcesses || []).map((p: any) => ({
          processName: p.processName, pid: p.pid,
          cpuPercent: p.cpuPercent != null ? p.cpuPercent : 0,
          memPercent: p.memPercent != null ? p.memPercent : 0,
          rssMb: p.rssMb != null ? p.rssMb : 0,
        }))
        javaProcessAvailable.value = targetJavaProcesses.value.length > 0
      }
    }

    taskStatus.value = 'completed'
    connectionStatus.value = 'disconnected'
    ElMessage.success(`已加载 ${snapshots.length} 条快照数据（回放模式）`)

    // 等 DOM 更新后渲染图表
    nextTick(() => {
      initCharts()
      updateTpsChart()
      updateRtChart()
      updateStatusChart()
      updateLatencyChart()
      updateInflightChart()
      if (resourceTimeLabels.value.length > 0) {
        if (!resourceExpanded.value.includes('resource')) {
          resourceExpanded.value.push('resource')
        }
        updateResourceChart()
        updateNetworkChart()
      }
      if (targetResourceTimeLabels.value.length > 0) {
        if (!targetResourceExpanded.value.includes('targetResource')) {
          targetResourceExpanded.value.push('targetResource')
        }
        nextTick(() => updateTargetResourceChart())
      }
    })
  } catch (e: any) {
    connectionStatus.value = 'disconnected'
    ElMessage.error('加载回放数据失败: ' + (e.message || ''))
  }
}

/** 导航到报告页 */
function navigateToReport() {
  if (countdownTimer) {
    clearInterval(countdownTimer)
    countdownTimer = null
  }
  const query: Record<string, string> = {}
  if (taskId.value) query.taskId = taskId.value
  if (executionId.value) query.executionId = executionId.value
  router.push({ path: '/performance/report', query })
}

/** 取消自动导航 */
function cancelAutoNavigate() {
  if (countdownTimer) {
    clearInterval(countdownTimer)
    countdownTimer = null
  }
  showCompleteBanner.value = false
}

/** 任务选择器状态 */
const selectedTaskId = ref('')
const availableTasks = ref<PerformanceTaskListItem[]>([])
const tasksLoading = ref(false)

/** 加载可选任务列表（已完成/运行中的任务） */
async function loadAvailableTasks() {
  tasksLoading.value = true
  try {
    const res = await listTasks({ page: 1, pageSize: 100 })
    availableTasks.value = (res.items || []).filter(
      t => t.status === 'running' || t.status === 'completed' || t.status === 'failed'
    )
  } catch { /* ignore */ } finally {
    tasksLoading.value = false
  }
}

/** 选中任务后跳转 */
function onTaskSelected(id: string) {
  if (id) {
    router.push({ path: '/performance/monitor', query: { taskId: id } })
  }
}

// 连接状态
const connectionStatus = ref<'disconnected' | 'connecting' | 'connected'>('disconnected')
const connectionStatusText = computed(() => {
  const map = { disconnected: '未连接', connecting: '连接中...', connected: '已连接' }
  return map[connectionStatus.value]
})
const taskStatus = ref<string>('')

// 实时指标
const currentTps = ref(0)
const currentAvgRt = ref(0)
const currentPeakTps = ref(0)
const currentInflight = ref(0)
const totalRequests = ref(0)
const errorRate = ref(0)

// 资源指标
const currentCpu = ref(0)
const currentMem = ref(0)
const currentNetSent = ref(0)
const currentNetRecv = ref(0)

// 目标机资源指标
const targetCpu = ref(0)
const targetMem = ref(0)
const targetMemUsedMb = ref(0)
const targetMemTotalMb = ref(0)
const targetLoadAvg1 = ref(0)
const targetAvailable = ref(false)
const targetHost = ref('')

// 目标机 Java 进程指标
const targetJavaProcesses = ref<Array<{
  processName: string
  pid: number
  cpuPercent: number
  memPercent: number
  rssMb: number
}>>([])
const javaProcessAvailable = ref(false)

// 图表数据缓冲区
const timeLabels = ref<string[]>([])
const tpsData = ref<number[]>([])
const avgRtData = ref<number[]>([])
const p50Data = ref<number[]>([])
const p90Data = ref<number[]>([])
const p95Data = ref<number[]>([])
const p99Data = ref<number[]>([])
const latencyAvgData = ref<number[]>([])
const connectTimeAvgData = ref<number[]>([])
const inflightData = ref<number[]>([])
const status2xx = ref(0)
const status4xx = ref(0)
const status5xx = ref(0)

// 资源图表数据缓冲区
const resourceTimeLabels = ref<string[]>([])
const cpuData = ref<number[]>([])
const memData = ref<number[]>([])
const netSentData = ref<number[]>([])
const netRecvData = ref<number[]>([])

// 目标机资源图表数据缓冲区
const targetResourceTimeLabels = ref<string[]>([])
const targetCpuData = ref<number[]>([])
const targetMemData = ref<number[]>([])
const targetLoadData = ref<number[]>([])

// 图表引用
const tpsChartRef = ref<HTMLElement>()
const rtChartRef = ref<HTMLElement>()
const statusChartRef = ref<HTMLElement>()
const latencyChartRef = ref<HTMLElement>()
const inflightChartRef = ref<HTMLElement>()

// 资源图表引用
const resourceChartRef = ref<HTMLElement>()
const networkChartRef = ref<HTMLElement>()

// 目标机资源图表引用
const targetResourceChartRef = ref<HTMLElement>()

// 资源折叠面板状态
const resourceExpanded = ref<string[]>([])
const targetResourceExpanded = ref<string[]>([])
/** 折叠面板是否已自动展开过（每次连接重置，避免用户手动收起后被再次强制展开） */
const resourceAutoExpanded = ref(false)
const targetAutoExpanded = ref(false)

let tpsChart: echarts.ECharts | null = null
let rtChart: echarts.ECharts | null = null
let statusChart: echarts.ECharts | null = null
let latencyChart: echarts.ECharts | null = null
let inflightChart: echarts.ECharts | null = null
let resourceChart: echarts.ECharts | null = null
let networkChart: echarts.ECharts | null = null
let targetResourceChart: echarts.ECharts | null = null
let ws: WebSocket | null = null

/** 状态标签类型 */
function statusTagType(status: string) {
  const map: Record<string, string> = { completed: 'success', running: 'primary', failed: 'danger', cancelled: 'warning' }
  return map[status] || 'info'
}

/** 状态标签文字 */
function statusLabel(status: string) {
  const map: Record<string, string> = { completed: '已完成', running: '运行中', pending: '等待中', failed: '失败', cancelled: '已取消' }
  return map[status] || status
}

/** 初始化图表 */
function initCharts() {
  if (tpsChartRef.value) {
    tpsChart = echarts.init(tpsChartRef.value)
  }
  if (rtChartRef.value) {
    rtChart = echarts.init(rtChartRef.value)
  }
  if (statusChartRef.value) {
    statusChart = echarts.init(statusChartRef.value)
  }
  if (latencyChartRef.value) {
    latencyChart = echarts.init(latencyChartRef.value)
  }
  if (inflightChartRef.value) {
    inflightChart = echarts.init(inflightChartRef.value)
  }
  if (resourceChartRef.value) {
    resourceChart = echarts.init(resourceChartRef.value)
  }
  if (networkChartRef.value) {
    networkChart = echarts.init(networkChartRef.value)
  }
  // 目标机资源图表不在此初始化 —— 懒初始化由 updateTargetResourceChart 在数据到达时处理
  // 避免折叠状态 + v-show=false 导致零尺寸初始化后图表空白
}

/** 更新 TPS 图表 */
function updateTpsChart() {
  if (!tpsChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    grid: { left: 50, right: 20, bottom: 30, top: 10 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'TPS' },
    series: [{
      type: 'line',
      data: tpsData.value,
      smooth: true,
      showSymbol: false,
      lineStyle: { width: 2 },
      areaStyle: {
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: 'rgba(64,158,255,0.3)' },
          { offset: 1, color: 'rgba(64,158,255,0.05)' },
        ]),
      },
    }],
  }
  tpsChart.setOption(option, true)
}

/** 更新响应时间图表 */
function updateRtChart() {
  if (!rtChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['平均 Avg', 'P50', 'P90', 'P95', 'P99'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'ms' },
    series: [
      { name: '平均 Avg', type: 'line', data: avgRtData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5 } },
      { name: 'P50', type: 'line', data: p50Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
      { name: 'P90', type: 'line', data: p90Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
      { name: 'P95', type: 'line', data: p95Data.value, smooth: true, showSymbol: false, lineStyle: { width: 2, type: 'dashed', color: '#e6a23c' } },
      { name: 'P99', type: 'line', data: p99Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dotted' } },
    ],
  }
  rtChart.setOption(option, true)
}

/** 更新延迟与连接时间图表 */
function updateLatencyChart() {
  if (!latencyChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['Latency（首字节延迟）', 'Connect（连接时间）'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'ms' },
    series: [
      { name: 'Latency（首字节延迟）', type: 'line', data: latencyAvgData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5 }, areaStyle: { opacity: 0.08 } },
      { name: 'Connect（连接时间）', type: 'line', data: connectTimeAvgData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed', color: '#e6a23c' } },
    ],
  }
  latencyChart.setOption(option, true)
}

/** 更新并发数图表 */
function updateInflightChart() {
  if (!inflightChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    grid: { left: 50, right: 20, bottom: 30, top: 10 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'Concurrency（并发数）', minInterval: 1 },
    series: [{
      type: 'line',
      data: inflightData.value,
      smooth: true,
      showSymbol: false,
      step: 'end',
      lineStyle: { width: 2, color: '#9b59b6' },
      areaStyle: { opacity: 0.12 },
    }],
  }
  inflightChart.setOption(option, true)
}

/** 更新状态码饼图 */
function updateStatusChart() {
  if (!statusChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
    series: [{
      type: 'pie',
      radius: ['40%', '70%'],
      center: ['50%', '55%'],
      data: [
        { name: '2xx', value: status2xx.value, itemStyle: { color: '#67c23a' } },
        { name: '4xx', value: status4xx.value, itemStyle: { color: '#e6a23c' } },
        { name: '5xx', value: status5xx.value, itemStyle: { color: '#f56c6c' } },
      ],
      label: { show: true, formatter: '{b}\n{d}%' },
    }],
  }
  statusChart.setOption(option, true)
}

/** 更新 CPU & 内存趋势图 */
function updateResourceChart() {
  if (!resourceChart && resourceChartRef.value) {
    resourceChart = echarts.init(resourceChartRef.value)
  }
  if (!resourceChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['CPU %', 'Memory %'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: resourceTimeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: '%', max: 100 },
    series: [
      { name: 'CPU %', type: 'line', data: cpuData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#f56c6c' }, areaStyle: { opacity: 0.08 } },
      { name: 'Memory %', type: 'line', data: memData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#409eff' }, areaStyle: { opacity: 0.08 } },
    ],
  }
  resourceChart.setOption(option, true)
}

/** 更新网络趋势图 */
function updateNetworkChart() {
  if (!networkChart && networkChartRef.value) {
    networkChart = echarts.init(networkChartRef.value)
  }
  if (!networkChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['发送 Sent', '接收 Recv'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: resourceTimeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'KB/s' },
    series: [
      { name: '发送 Sent', type: 'line', data: netSentData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#67c23a' }, areaStyle: { opacity: 0.08 } },
      { name: '接收 Recv', type: 'line', data: netRecvData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#e6a23c' }, areaStyle: { opacity: 0.08 } },
    ],
  }
  networkChart.setOption(option, true)
}

// 目标机资源图表懒初始化：折叠状态下 div 可能 display:none，
  // 因此等数据到达时 nextTick 确保 v-show 已生效再 init
  function updateTargetResourceChart() {
    if (!targetResourceChartRef.value) return
    if (!targetResourceChart) {
      targetResourceChart = echarts.init(targetResourceChartRef.value)
    }
    // 若之前因隐藏导致零尺寸，resize 重新读取容器尺寸
    if (targetResourceChart.getWidth() === 0 || targetResourceChart.getHeight() === 0) {
      targetResourceChart.resize()
    }
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    legend: { data: ['CPU %', 'Memory %', 'Load 1m'], top: 0, itemWidth: 12, itemHeight: 8 },
    grid: { left: 50, right: 20, bottom: 30, top: 30 },
    xAxis: { type: 'category', data: targetResourceTimeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: [
      { type: 'value', name: '%', max: 100 },
      { type: 'value', name: 'Load', splitLine: { show: false } },
    ],
    series: [
      { name: 'CPU %', type: 'line', data: targetCpuData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#f56c6c' }, areaStyle: { opacity: 0.08 } },
      { name: 'Memory %', type: 'line', data: targetMemData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, color: '#409eff' }, areaStyle: { opacity: 0.08 } },
      { name: 'Load 1m', type: 'line', data: targetLoadData.value, smooth: true, showSymbol: false, yAxisIndex: 1, lineStyle: { width: 1.5, type: 'dashed', color: '#e6a23c' } },
    ],
  }
  targetResourceChart.setOption(option, true)
}

/** 目标机内存用量文案 */
function targetMemBar() {
  return targetMemTotalMb.value > 0 ? `（${targetMemUsedMb.value.toFixed(0)} / ${targetMemTotalMb.value.toFixed(0)} MB）` : ''
}

/** 处理快照数据 */
function handleSnapshot(data: any) {
  const d = data.data || data
  hasReceivedWsData = true
  isRunning.value = true
  currentTps.value = d.tps || 0
  currentAvgRt.value = d.avgResponseTime || d.avg_response_time || 0
  currentPeakTps.value = d.peakTps || d.peak_tps || 0
  currentInflight.value = d.concurrentUsers || d.concurrent_users || 0
  totalRequests.value = d.totalRequests || d.total_requests || 0
  const fail = d.failCount || d.fail_count || 0
  const total = totalRequests.value
  errorRate.value = total > 0 ? (fail / total) * 100 : 0

  // 更新图表数据
  const elapsed = d.elapsed != null ? `${d.elapsed.toFixed(0)}s` : `${timeLabels.value.length + 1}s`
  timeLabels.value.push(elapsed)
  tpsData.value.push(d.tps || 0)
  avgRtData.value.push(d.avgResponseTime || d.avg_response_time || 0)
  p50Data.value.push(d.p50 || 0)
  p90Data.value.push(d.p90 || 0)
  p95Data.value.push(d.p95 || 0)
  p99Data.value.push(d.p99 || 0)
  latencyAvgData.value.push(d.latencyAvg || d.latency_avg || 0)
  connectTimeAvgData.value.push(d.connectTimeAvg || d.connect_time_avg || 0)
  inflightData.value.push(d.concurrentUsers || d.concurrent_users || 0)

  status2xx.value += d.status2xx || d.status_2xx || 0
  status4xx.value += d.status4xx || d.status_4xx || 0
  status5xx.value += d.status5xx || d.status_5xx || 0

  // 执行机资源监控数据（后端已合并进快照）
  const r = d.resource
  if (r) {
    currentCpu.value = r.cpuPercent != null ? r.cpuPercent : (r.cpu_percent || 0)
    currentMem.value = r.memPercent != null ? r.memPercent : (r.mem_percent || 0)
    currentNetSent.value = r.netSentKbS != null ? r.netSentKbS : (r.net_sent_kb_s || 0)
    currentNetRecv.value = r.netRecvKbS != null ? r.netRecvKbS : (r.net_recv_kb_s || 0)

    const relapsed = d.elapsed != null ? `${d.elapsed.toFixed(0)}s` : `${resourceTimeLabels.value.length + 1}s`
    resourceTimeLabels.value.push(relapsed)
    cpuData.value.push(currentCpu.value)
    memData.value.push(currentMem.value)
    netSentData.value.push(currentNetSent.value)
    netRecvData.value.push(currentNetRecv.value)

    if (resourceTimeLabels.value.length > 300) {
      resourceTimeLabels.value.shift()
      cpuData.value.shift()
      memData.value.shift()
      netSentData.value.shift()
      netRecvData.value.shift()
    }

    // 若执行机资源面板处于折叠状态，自动展开一次（让用户直接看到数据；用户手动收起后不强制）
    if (!resourceAutoExpanded.value && !resourceExpanded.value.includes('resource')) {
      resourceExpanded.value.push('resource')
    }
    resourceAutoExpanded.value = true

    updateResourceChart()
    updateNetworkChart()
  }

  // 目标机资源监控数据
  const tr = d.targetResource
  if (tr) {
    targetCpu.value = tr.cpuPercent != null ? tr.cpuPercent : 0
    targetMem.value = tr.memPercent != null ? tr.memPercent : 0
    targetMemUsedMb.value = tr.memUsedMb != null ? tr.memUsedMb : 0
    targetMemTotalMb.value = tr.memTotalMb != null ? tr.memTotalMb : 0
    targetLoadAvg1.value = tr.loadAvg1 != null ? tr.loadAvg1 : 0
    targetAvailable.value = tr.available !== false
    targetHost.value = tr.host || ''
    targetJavaProcesses.value = (tr.javaProcesses || []).map((p: any) => ({
      processName: p.processName,
      pid: p.pid,
      cpuPercent: p.cpuPercent != null ? p.cpuPercent : 0,
      memPercent: p.memPercent != null ? p.memPercent : 0,
      rssMb: p.rssMb != null ? p.rssMb : 0,
    }))
    // 仅限 Java 进程存在时展示（避免无进程时空表头）
    javaProcessAvailable.value = targetJavaProcesses.value.length > 0

    const relapsed = d.elapsed != null ? `${d.elapsed.toFixed(0)}s` : `${targetResourceTimeLabels.value.length + 1}s`
    targetResourceTimeLabels.value.push(relapsed)
    targetCpuData.value.push(targetCpu.value)
    targetMemData.value.push(targetMem.value)
    targetLoadData.value.push(targetLoadAvg1.value)

    if (targetResourceTimeLabels.value.length > 300) {
      targetResourceTimeLabels.value.shift()
      targetCpuData.value.shift()
      targetMemData.value.shift()
      targetLoadData.value.shift()
    }

    // 若目标机资源面板处于折叠状态，自动展开一次（同上）
    if (!targetAutoExpanded.value && !targetResourceExpanded.value.includes('targetResource')) {
      targetResourceExpanded.value.push('targetResource')
    }
    targetAutoExpanded.value = true

    // 用 nextTick 确保 v-show 已生效、div 可见后再更新图表，避免零尺寸空白
    nextTick(() => updateTargetResourceChart())
  }

  // 限制显示点数
  if (timeLabels.value.length > 300) {
    timeLabels.value.shift()
    tpsData.value.shift()
    avgRtData.value.shift()
    p50Data.value.shift()
    p90Data.value.shift()
    p95Data.value.shift()
    p99Data.value.shift()
    latencyAvgData.value.shift()
    connectTimeAvgData.value.shift()
    inflightData.value.shift()
  }

  // 更新图表
  updateTpsChart()
  updateRtChart()
  updateStatusChart()
  updateLatencyChart()
  updateInflightChart()
  taskStatus.value = 'running'
}

/** 处理完成事件 */
function handleComplete(data: any) {
  taskStatus.value = data.status || 'completed'
  connectionStatus.value = 'disconnected'
  isRunning.value = false
  ElMessage.success('性能测试已完成')

  // 如果 WS 没有收到任何数据（任务已离线完成），回退到 API 加载快照
  if (!hasReceivedWsData && executionId.value) {
    loadReplayData()
    return
  }

  // 显示完成横幅 + 倒计时自动跳转报告
  completeStatus.value = taskStatus.value
  showCompleteBanner.value = true
  countdown.value = 3
  if (countdownTimer) clearInterval(countdownTimer)
  countdownTimer = setInterval(() => {
    countdown.value--
    if (countdown.value <= 0) {
      navigateToReport()
    }
  }, 1000)
}

/** 连接 WebSocket */
function connectWs() {
  if (!taskId.value) return

  // 关闭旧连接
  disconnectWs()

  // 重置数据
  hasReceivedWsData = false
  isRunning.value = false
  showCompleteBanner.value = false
  completeStatus.value = ''
  countdown.value = 3
  if (countdownTimer) {
    clearInterval(countdownTimer)
    countdownTimer = null
  }
  timeLabels.value = []
  tpsData.value = []
  avgRtData.value = []
  p50Data.value = []
  p90Data.value = []
  p95Data.value = []
  p99Data.value = []
  latencyAvgData.value = []
  connectTimeAvgData.value = []
  inflightData.value = []
  status2xx.value = 0
  status4xx.value = 0
  status5xx.value = 0
  currentTps.value = 0
  currentAvgRt.value = 0
  currentPeakTps.value = 0
  currentInflight.value = 0
  totalRequests.value = 0
  errorRate.value = 0
  taskStatus.value = ''
  resourceTimeLabels.value = []
  cpuData.value = []
  memData.value = []
  netSentData.value = []
  netRecvData.value = []
  currentCpu.value = 0
  currentMem.value = 0
  currentNetSent.value = 0
  currentNetRecv.value = 0
  targetResourceTimeLabels.value = []
  targetCpuData.value = []
  targetMemData.value = []
  targetLoadData.value = []
  targetCpu.value = 0
  targetMem.value = 0
  targetMemUsedMb.value = 0
  targetMemTotalMb.value = 0
  targetLoadAvg1.value = 0
  targetAvailable.value = false
  targetHost.value = ''
  targetJavaProcesses.value = []
  javaProcessAvailable.value = false
  resourceAutoExpanded.value = false
  targetAutoExpanded.value = false

  connectionStatus.value = 'connecting'

  ws = createPerfMonitorStream(
    taskId.value,
    handleSnapshot,
    handleComplete,
    () => {
      connectionStatus.value = 'disconnected'
      ElMessage.error('WebSocket 连接异常')
    },
    () => {
      if (connectionStatus.value === 'connected') {
        connectionStatus.value = 'disconnected'
      }
    },
  )

  ws.onopen = () => {
    connectionStatus.value = 'connected'
  }
}

/** 断开 WebSocket */
function disconnectWs() {
  if (ws) {
    ws.close()
    ws = null
  }
}

/** 刷新（外部调用） */
function refresh() {
  connectWs()
}

defineExpose({ refresh })

watch(() => route.query.taskId, (newId) => {
  if (newId) {
    // 重置完成横幅状态
    showCompleteBanner.value = false
    if (countdownTimer) {
      clearInterval(countdownTimer)
      countdownTimer = null
    }
    nextTick(() => {
      initCharts()
      // 有 executionId 且非实时 → 回放模式，直接加载 API 数据
      if (executionId.value && !isRunning.value) {
        loadReplayData()
      } else {
        connectWs()
      }
    })
  }
})

watch(() => route.query.executionId, (newExecId) => {
  if (newExecId && taskId.value) {
    // 重置完成横幅状态
    showCompleteBanner.value = false
    if (countdownTimer) {
      clearInterval(countdownTimer)
      countdownTimer = null
    }
    nextTick(() => {
      initCharts()
      if (isRunning.value) {
        connectWs()
      } else {
        loadReplayData()
      }
    })
  }
})

onMounted(() => {
  if (taskId.value) {
    if (executionId.value && !isRunning.value) {
      // 回放模式直接加载 API 数据
      nextTick(() => {
        initCharts()
        loadReplayData()
      })
    } else {
      nextTick(() => {
        initCharts()
        connectWs()
      })
    }
  } else {
    // 无任务时加载可选项 + 最近执行记录，方便从侧边栏直接进入
    loadAvailableTasks()
    loadExecutions()
  }
})

onUnmounted(() => {
  disconnectWs()
  if (countdownTimer) {
    clearInterval(countdownTimer)
    countdownTimer = null
  }
  tpsChart?.dispose()
  rtChart?.dispose()
  statusChart?.dispose()
  latencyChart?.dispose()
  inflightChart?.dispose()
  resourceChart?.dispose()
  networkChart?.dispose()
  targetResourceChart?.dispose()
})
</script>

<style scoped>
.monitor-panel {
  padding: 0;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 80px 0;
  color: #909399;
}

.empty-state p {
  margin-top: 12px;
  font-size: 14px;
}

.monitor-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  font-size: 13px;
}

.task-info .label {
  color: #909399;
}

.task-info .value {
  color: #303133;
  font-family: monospace;
}

.status-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: #909399;
}

.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}

.status-dot.connected {
  background: #67c23a;
}

.status-dot.connecting {
  background: #e6a23c;
  animation: pulse 1s infinite;
}

.status-dot.disconnected {
  background: #c0c4cc;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

.metrics-row {
  margin-bottom: 12px;
}

.metric-card {
  text-align: center;
}

.metric-value {
  font-size: 28px;
  font-weight: 700;
  color: #409eff;
  line-height: 1.2;
}

.metric-label {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}

.chart-card {
  margin-bottom: 12px;
}

.chart {
  width: 100%;
  height: 280px;
}

.chart-sm {
  width: 100%;
  height: 220px;
}

.target-host-info {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  font-size: 12px;
  color: #909399;
}

.target-host-hint {
  color: #909399;
}

.target-resource-wrap {
  margin-top: 4px;
}
</style>