<template>
  <div class="vm-detail-page">
    <!-- 顶部面包屑 + 返回 -->
    <div class="page-header">
      <div class="header-left">
        <el-button @click="goBack" link class="back-btn"><el-icon><ArrowLeft /></el-icon> 返回虚拟机列表</el-button>
      </div>
      <div class="header-right">
        <el-button plain :icon="Refresh" @click="loadDetail" :loading="loading">刷新</el-button>
      </div>
    </div>

    <div class="main-content" v-loading="loading">
      <!-- 实例概览栏 -->
      <div class="instance-overview" v-if="detail">
        <div class="overview-left">
          <h2 class="instance-name">{{ detail.name || '-' }} <span class="instance-id">{{ detail.id }}</span></h2>
        </div>
        <div class="overview-actions">
          <el-button type="primary" @click="handleGetVnc">远程连接</el-button>
          <el-button type="danger" @click="handlePower('stop')" :disabled="detail.status === 'stopped' || detail.status === 'stop'">关机</el-button>
          <el-dropdown trigger="click" @command="handleMoreCommand">
            <el-button>更多 <el-icon class="el-icon--right"><ArrowDown /></el-icon></el-button>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item command="start" :disabled="detail.status === 'running'">启动</el-dropdown-item>
                <el-dropdown-item command="reboot">重启</el-dropdown-item>
                <el-dropdown-item command="suspend">暂停</el-dropdown-item>
                <el-dropdown-item command="resume" v-if="detail.status === 'paused'">恢复</el-dropdown-item>
                <el-dropdown-item divided command="editVm">编辑虚拟机</el-dropdown-item>
                <el-dropdown-item command="refactorVm">重构虚拟机</el-dropdown-item>
                <el-dropdown-item command="updateTraffic">修改带宽</el-dropdown-item>
                <el-dropdown-item divided command="bindSg">绑定安全组</el-dropdown-item>
                <el-dropdown-item command="unbindSg">解绑安全组</el-dropdown-item>
                <el-dropdown-item divided command="rebuild">重建虚拟机</el-dropdown-item>
                <el-dropdown-item command="rescue">救援模式</el-dropdown-item>
                <el-dropdown-item command="exitRescue">退出救援</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </div>

      <!-- 状态概览条 -->
      <div class="status-bar" v-if="detail">
        <div class="status-item">
          <span class="status-label">实例状态</span>
          <span class="status-value">
            <span class="status-dot" :class="detail.status === 'running' ? 'dot-running' : 'dot-other'"></span>
            {{ vmStatusLabel(detail.status) }}
          </span>
        </div>
        <div class="status-item">
          <span class="status-label">IP地址</span>
          <span class="status-value">{{ detail.ips || '-' }}</span>
        </div>
        <div class="status-item">
          <span class="status-label">创建时间</span>
          <span class="status-value">{{ formatTimestamp(detail.created_at) }}</span>
        </div>
      </div>

      <!-- 标签页 -->
      <el-tabs v-model="activeTab" class="detail-tabs" v-if="detail">
        <el-tab-pane label="实例详情" name="info">
          <!-- 配置信息 -->
          <div class="section-block">
            <h3 class="section-title">配置信息</h3>
            <div class="config-grid">
              <div class="config-row">
                <div class="config-cell">
                  <span class="config-label">实例规格</span>
                  <span class="config-value spec-value">{{ detail.vcpu || '-' }}核vCPU | {{ formatMemory(detail.memory) }} | {{ detail.rx_bandwidth || 0 }} Mbps</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">操作系统</span>
                  <span class="config-value">{{ vmImage?.name || '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">镜像类型</span>
                  <span class="config-value">
                    <el-tag v-if="vmImage" :type="vmImage.os_type === 'linux' ? 'success' : 'primary'" size="small">{{ vmImage.os_type || '-' }}</el-tag>
                    <span v-else>-</span>
                  </span>
                </div>
              </div>
              <div class="config-row">
                <div class="config-cell">
                  <span class="config-label">公网IP</span>
                  <span class="config-value ip-value">{{ detail.ips || '暂无' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">下行/上行带宽</span>
                  <span class="config-value">{{ detail.rx_bandwidth || 0 }} / {{ detail.tx_bandwidth || 0 }} Mbps</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">安全组</span>
                  <span class="config-value">
                    <template v-if="vmPortGroup">
                      <el-tag size="small" type="success">{{ vmPortGroup.name }}</el-tag>
                    </template>
                    <span v-else style="color: #909399">未绑定</span>
                  </span>
                </div>
              </div>
              <div class="config-row">
                <div class="config-cell">
                  <span class="config-label">用户名</span>
                  <span class="config-value">root</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">远程端口</span>
                  <span class="config-value">{{ detail.ssh_port || 22 }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">密码</span>
                  <span class="config-value password-cell">
                    <code>{{ showPassword ? (detail.root_password || '-') : '••••••••' }}</code>
                    <el-button link type="primary" size="small" @click="showPassword = !showPassword">{{ showPassword ? '隐藏' : '显示' }}</el-button>
                    <el-button link type="primary" size="small" @click="copyText(detail.root_password)">复制</el-button>
                  </span>
                </div>
              </div>
              <div class="config-row">
                <div class="config-cell">
                  <span class="config-label">流量上限(GB)</span>
                  <span class="config-value">{{ detail.traffic_max ?? '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">快照配额</span>
                  <span class="config-value">{{ detail.snapshot_num ?? '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">备份配额</span>
                  <span class="config-value">{{ detail.backup_num ?? '-' }}</span>
                </div>
              </div>
              <div class="config-row">
                <div class="config-cell" style="flex: 2">
                  <span class="config-label">UUID</span>
                  <span class="config-value mono-text">{{ detail.uuid || '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">更新时间</span>
                  <span class="config-value">{{ formatTimestamp(detail.updated_at) }}</span>
                </div>
              </div>
            </div>
          </div>

        </el-tab-pane>

        <el-tab-pane label="网络信息" name="network">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">网络信息</h3>
              <div style="display: flex; gap: 8px">
                <el-button size="small" type="primary" @click="showCreateNetworkDialog">创建网络</el-button>
                <el-button size="small" @click="handleAddNetwork">添加已有网络</el-button>
              </div>
            </div>
            <el-table v-if="vmNetworks.length" :data="pagedNetworks" size="small" stripe>
              <el-table-column prop="id" label="ID" width="60" />
              <el-table-column prop="name" label="名称" min-width="100" />
              <el-table-column prop="address" label="IP地址" min-width="140" show-overflow-tooltip />
              <el-table-column prop="gateway" label="网关" min-width="120" />
              <el-table-column prop="mac_address" label="MAC地址" min-width="150" show-overflow-tooltip />
              <el-table-column prop="type" label="类型" width="80">
                <template #default="{ row }"><el-tag size="small">{{ row.type || '-' }}</el-tag></template>
              </el-table-column>
              <el-table-column prop="bridge_name" label="网桥" width="80" />
              <el-table-column label="操作" width="120" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleEditNetwork(row)">编辑</el-button>
                  <el-button link type="danger" size="small" @click="handleDeleteNetwork(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-else description="暂无网络" :image-size="60" />
            <div class="pagination-wrapper" v-if="vmNetworks.length > 0">
              <el-pagination v-model:current-page="networkPage" v-model:page-size="networkPageSize"
                :page-sizes="[10, 20, 50]" :total="vmNetworks.length" layout="total, sizes, prev, pager, next" small
                @size-change="s => { networkPageSize = s; networkPage = 1 }"
                @current-change="p => { networkPage = p }" />
            </div>
          </div>
        </el-tab-pane>

        <el-tab-pane label="磁盘卷信息" name="volume">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">磁盘卷信息</h3>
              <div style="display: flex; gap: 8px">
                <el-button size="small" type="primary" @click="showCreateVolumeDialog">创建数据卷</el-button>
                <el-button size="small" @click="handleAddVolume">挂载已有数据卷</el-button>
              </div>
            </div>
            <el-table v-if="vmVolumes.length" :data="pagedVolumes" size="small" stripe>
              <el-table-column prop="id" label="ID" width="60" />
              <el-table-column prop="name" label="名称" min-width="100" />
              <el-table-column label="大小" width="80">
                <template #default="{ row }">{{ row.size ? row.size + ' GB' : '-' }}</template>
              </el-table-column>
              <el-table-column label="类型" width="80">
                <template #default="{ row }">
                  <el-tag :type="row.is_system ? 'danger' : ''" size="small">{{ row.is_system ? '系统盘' : '数据盘' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="状态" width="80">
                <template #default="{ row }">
                  <el-tag :type="row.status === 'ready' ? 'success' : 'info'" size="small">{{ row.status === 'ready' ? '就绪' : (row.status || '-') }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="path" label="路径" min-width="160" show-overflow-tooltip>
                <template #default="{ row }"><span class="mono-text">{{ row.path || '-' }}</span></template>
              </el-table-column>
              <el-table-column label="操作" width="260" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleResizeVolume(row)">调整大小</el-button>
                  <el-button link type="success" size="small" @click="handleMountVolume(row)" v-if="!row.is_mount">挂载</el-button>
                  <el-button link type="warning" size="small" @click="handleUnmountVolume(row)" v-if="row.is_mount">卸载</el-button>
                  <el-button link type="info" size="small" @click="handleTransferVolume(row)">迁移</el-button>
                  <el-button link type="danger" size="small" @click="handleDeleteVolume(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-else description="暂无磁盘卷" :image-size="60" />
            <div class="pagination-wrapper" v-if="vmVolumes.length > 0">
              <el-pagination v-model:current-page="volumePage" v-model:page-size="volumePageSize"
                :page-sizes="[10, 20, 50]" :total="vmVolumes.length" layout="total, sizes, prev, pager, next" small
                @size-change="s => { volumePageSize = s; volumePage = 1 }"
                @current-change="p => { volumePage = p }" />
            </div>
          </div>
        </el-tab-pane>

        <el-tab-pane label="安全组" name="security">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">安全组管理</h3>
              <div style="display: flex; gap: 8px; align-items: center">
                <el-select v-model="sgDirectionFilter" placeholder="方向" clearable style="width: 100px" size="small">
                  <el-option label="入站" value="in" />
                  <el-option label="出站" value="out" />
                </el-select>
                <el-button size="small" type="primary" @click="handleBindSgFromTab">绑定安全组</el-button>
              </div>
            </div>
            <el-table v-if="filteredSecurityGroups.length" :data="pagedSecurityGroups" size="small" stripe>
              <el-table-column prop="id" label="ID" width="80" />
              <el-table-column prop="name" label="名称" min-width="120" />
              <el-table-column label="方向" width="80">
                <template #default="{ row }">
                  <el-tag :type="row.direction === 'in' ? 'primary' : 'warning'" size="small">{{ row.direction === 'in' ? '入站' : '出站' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column prop="note" label="备注" min-width="140" show-overflow-tooltip />
              <el-table-column label="锁定" width="80">
                <template #default="{ row }">
                  <el-tag :type="row.lock ? 'danger' : 'info'" size="small">{{ row.lock ? '已锁定' : '未锁定' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="白名单" width="80">
                <template #default="{ row }">
                  <el-tag :type="row.drop_all ? 'warning' : 'info'" size="small">{{ row.drop_all ? '开启' : '关闭' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="共享" width="80">
                <template #default="{ row }">
                  <el-tag :type="row.shared ? 'success' : 'info'" size="small">{{ row.shared ? '共享' : '私有' }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="操作" width="100" fixed="right">
                <template #default="{ row }">
                  <el-button link type="danger" size="small" @click="handleUnbindSgFromTab(row)">解绑</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-else description="暂无绑定的安全组" :image-size="60" />
            <div class="pagination-wrapper" v-if="filteredSecurityGroups.length > 0">
              <el-pagination v-model:current-page="securityPage" v-model:page-size="securityPageSize"
                :page-sizes="[10, 20, 50]" :total="filteredSecurityGroups.length" layout="total, sizes, prev, pager, next" small
                @size-change="s => { securityPageSize = s; securityPage = 1 }"
                @current-change="p => { securityPage = p }" />
            </div>
          </div>
        </el-tab-pane>

        <el-tab-pane label="快照" name="snapshot">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">快照管理</h3>
              <div style="display: flex; align-items: center; gap: 8px">
                <el-tag v-if="snapshotQuota" size="small" effect="plain">{{ snapshotQuota.count }} / {{ snapshotQuota.limit }}</el-tag>
                <el-button size="small" @click="handleSetSnapshotLimit">设置上限</el-button>
                <el-button size="small" type="primary" @click="handleCreateSnapshot">创建快照</el-button>
                <el-button size="small" @click="loadSnapshots">刷新</el-button>
              </div>
            </div>
            <el-table :data="snapshotList" v-loading="snapshotLoading" stripe size="small" style="width: 100%">
              <el-table-column prop="id" label="ID" width="60" />
              <el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip />
              <el-table-column label="状态" width="90">
                <template #default="{ row }">
                  <el-tag :type="taskStatusType(row.status)" size="small">{{ snapshotStatusLabel(row.status) }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="任务ID" min-width="160">
                <template #default="{ row }">
                  <span style="font-family: Consolas, monospace; font-size: 12px">{{ row.task_id || '-' }}</span>
                </template>
              </el-table-column>
              <el-table-column label="创建时间" width="170">
                <template #default="{ row }">{{ formatTimestamp(row.created_at) }}</template>
              </el-table-column>
              <el-table-column label="更新时间" width="170">
                <template #default="{ row }">{{ formatTimestamp(row.updated_at) }}</template>
              </el-table-column>
              <el-table-column label="操作" width="180" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleRestoreSnapshot(row)">恢复</el-button>
                  <el-button link type="info" size="small" @click="handleSnapshotProgress(row)" v-if="row.status === 'running' || row.status === 'pending'">进度</el-button>
                  <el-button link type="danger" size="small" @click="handleDeleteSnapshot(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!snapshotList.length && !snapshotLoading" description="暂无快照" :image-size="60" />
            <div class="pagination-wrapper" v-if="snapshotTotal > 0">
              <el-pagination v-model:current-page="snapshotPage" v-model:page-size="snapshotPageSize"
                :page-sizes="[10, 20, 50]" :total="snapshotTotal" layout="total, sizes, prev, pager, next" small
                @size-change="s => { snapshotPageSize = s; snapshotPage = 1; loadSnapshots() }"
                @current-change="p => { snapshotPage = p; loadSnapshots() }" />
            </div>
          </div>
        </el-tab-pane>

        <el-tab-pane label="备份" name="backup">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">备份管理</h3>
              <div style="display: flex; align-items: center; gap: 8px">
                <el-tag v-if="backupQuota" size="small" effect="plain">{{ backupQuota.count }} / {{ backupQuota.limit }}</el-tag>
                <el-button size="small" @click="handleSetBackupLimit">设置上限</el-button>
                <el-button size="small" type="primary" @click="handleCreateBackup">创建备份</el-button>
                <el-button size="small" @click="loadBackups">刷新</el-button>
              </div>
            </div>
            <el-table :data="backupList" v-loading="backupLoading" stripe size="small" style="width: 100%">
              <el-table-column prop="id" label="ID" width="60" />
              <el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip />
              <el-table-column label="状态" width="90">
                <template #default="{ row }">
                  <el-tag :type="taskStatusType(row.status)" size="small">{{ snapshotStatusLabel(row.status) }}</el-tag>
                </template>
              </el-table-column>
              <el-table-column label="任务ID" min-width="160">
                <template #default="{ row }">
                  <span style="font-family: Consolas, monospace; font-size: 12px">{{ row.task_id || '-' }}</span>
                </template>
              </el-table-column>
              <el-table-column label="创建时间" width="170">
                <template #default="{ row }">{{ formatTimestamp(row.created_at) }}</template>
              </el-table-column>
              <el-table-column label="更新时间" width="170">
                <template #default="{ row }">{{ formatTimestamp(row.updated_at) }}</template>
              </el-table-column>
              <el-table-column label="操作" width="180" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleRestoreBackup(row)">恢复</el-button>
                  <el-button link type="info" size="small" @click="handleBackupProgress(row)" v-if="row.status === 'running' || row.status === 'pending'">进度</el-button>
                  <el-button link type="danger" size="small" @click="handleDeleteBackup(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!backupList.length && !backupLoading" description="暂无备份" :image-size="60" />
            <div class="pagination-wrapper" v-if="backupTotal > 0">
              <el-pagination v-model:current-page="backupPage" v-model:page-size="backupPageSize"
                :page-sizes="[10, 20, 50]" :total="backupTotal" layout="total, sizes, prev, pager, next" small
                @size-change="s => { backupPageSize = s; backupPage = 1; loadBackups() }"
                @current-change="p => { backupPage = p; loadBackups() }" />
            </div>
          </div>
        </el-tab-pane>

        <el-tab-pane label="监控" name="monitor">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">实时指标</h3>
              <div style="display: flex; align-items: center; gap: 8px">
                <el-tag v-if="pollingActive" type="success" size="small" effect="light">每3秒刷新</el-tag>
                <el-button size="small" :icon="Refresh" @click="pollMetrics" :loading="metricsLoading">刷新指标</el-button>
              </div>
            </div>
            <div v-if="metricsData" class="metrics-summary">
              <div class="metric-card">
                <div class="metric-title">CPU 使用率</div>
                <div class="metric-num">{{ (metricsData.cpu_usage_percent ?? 0).toFixed(1) }}%</div>
              </div>
              <div class="metric-card" v-if="metricsData.internet_speed && Object.keys(metricsData.internet_speed).length">
                <div class="metric-title">网络速率</div>
                <div class="net-speed-items">
                  <div v-for="(val, key) in metricsData.internet_speed" :key="key" class="net-speed-item">
                    <span class="net-speed-label">{{ key === 'rx_bytes' ? '↓ 接收' : key === 'tx_bytes' ? '↑ 发送' : key }}</span>
                    <span class="net-speed-value">{{ formatNetSpeed(val) }}</span>
                  </div>
                </div>
              </div>
            </div>
            <div class="charts-area">
              <div class="chart-wrapper">
                <h4 class="chart-label">CPU 使用率</h4>
                <div ref="cpuChartRef" class="chart-container"></div>
              </div>
              <div class="chart-wrapper" v-if="metricsHistory.netKeys.length">
                <h4 class="chart-label">网络速率</h4>
                <div ref="netChartRef" class="chart-container"></div>
              </div>
            </div>
            <el-empty v-if="!metricsData && !metricsLoading" description="暂无指标数据，切换到此标签页后自动开始采集" />
          </div>
        </el-tab-pane>
      </el-tabs>
    </div>

    <!-- 重建弹窗 -->
    <el-dialog v-model="rebuildDialogVisible" title="重建虚拟机" width="480px" destroy-on-close>
      <el-alert title="重建会清除当前虚拟机数据并使用新镜像重新创建，请谨慎操作！" type="warning" :closable="false" style="margin-bottom: 16px" />
      <el-form label-width="100px">
        <el-form-item label="虚拟机">{{ detail?.name || '-' }}</el-form-item>
        <el-form-item label="新镜像" required>
          <div style="display: flex; gap: 8px; width: 100%">
            <el-input :model-value="rebuildImageId ? `${rebuildImageName || ''} (ID: ${rebuildImageId})` : ''" readonly placeholder="请选择镜像" style="flex: 1" />
            <el-button type="primary" @click="showImageSelector = true">选择</el-button>
          </div>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="rebuildDialogVisible = false">取消</el-button>
        <el-button type="danger" :loading="actionLoading" @click="submitRebuild">确定重建</el-button>
      </template>
    </el-dialog>

    <ImageSelectorPopup v-model="showImageSelector" :service-id="serviceId" :current-id="rebuildImageId" @confirm="img => { rebuildImageId = img.id; rebuildImageName = img.name }" />

    <!-- 创建快照弹窗 -->
    <el-dialog v-model="snapshotCreateVisible" title="创建快照" width="480px" destroy-on-close>
      <el-form :model="snapshotForm" label-width="100px">
        <el-form-item label="虚拟机">{{ detail?.name || '-' }} (ID: {{ vmId }})</el-form-item>
        <el-form-item label="快照名称" required>
          <el-input v-model="snapshotForm.name" placeholder="请输入快照名称" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="snapshotCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateSnapshot">创建</el-button>
      </template>
    </el-dialog>

    <!-- 创建备份弹窗 -->
    <el-dialog v-model="backupCreateVisible" title="创建备份" width="480px" destroy-on-close>
      <el-form :model="backupForm" label-width="100px">
        <el-form-item label="虚拟机">{{ detail?.name || '-' }} (ID: {{ vmId }})</el-form-item>
        <el-form-item label="备份名称" required>
          <el-input v-model="backupForm.name" placeholder="请输入备份名称" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="backupCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateBackup">创建</el-button>
      </template>
    </el-dialog>

    <!-- 快照/备份进度弹窗 -->
    <el-dialog v-model="taskProgressVisible" :title="taskProgressTitle" width="520px" destroy-on-close>
      <div v-loading="taskProgressLoading">
        <el-descriptions :column="1" border size="small" v-if="taskProgressData">
          <el-descriptions-item label="任务ID">
            <span class="mono-text">{{ taskProgressData.task_id || '-' }}</span>
          </el-descriptions-item>
          <el-descriptions-item label="状态">
            <el-tag :type="taskStatusType(taskProgressData.status)" size="small">{{ snapshotStatusLabel(taskProgressData.status) }}</el-tag>
          </el-descriptions-item>
          <template v-if="taskProgressMeta">
            <el-descriptions-item v-for="(val, key) in taskProgressMeta" :key="key" :label="taskMetaLabel(key)">
              <span :style="key.includes('path') ? 'font-family: Consolas, monospace; font-size: 13px; word-break: break-all' : ''">{{ val }}</span>
            </el-descriptions-item>
          </template>
        </el-descriptions>
        <el-empty v-else description="暂无进度信息" />
      </div>
      <template #footer>
        <el-button @click="taskProgressVisible = false">关闭</el-button>
      </template>
    </el-dialog>

    <!-- 编辑虚拟机弹窗 -->
    <el-dialog v-model="editDialogVisible" title="编辑虚拟机" width="640px" destroy-on-close>
      <el-form ref="editFormRef" :model="editForm" label-width="120px">
        <el-form-item label="名称">
          <el-input v-model="editForm.name" placeholder="虚拟机名称" />
        </el-form-item>
        <el-form-item label="内存">
          <div style="display: flex; align-items: center; gap: 8px; width: 100%">
            <el-input-number v-model="editMemoryDisplay" :min="1" :precision="editMemoryUnit === 'GB' ? 2 : 0" controls-position="right" style="width: 200px" />
            <el-select v-model="editMemoryUnit" style="width: 80px">
              <el-option label="MB" value="MB" />
              <el-option label="GB" value="GB" />
            </el-select>
            <span style="color: #909399; font-size: 12px">（{{ editForm.memory }} KB）</span>
          </div>
        </el-form-item>
        <el-form-item label="CPU(核)">
          <el-input-number v-model="editForm.vcpu" :min="1" controls-position="right" style="width: 200px" />
        </el-form-item>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="下行带宽(Mbps)">
              <el-input-number v-model="editForm.rx_bandwidth" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="上行带宽(Mbps)">
              <el-input-number v-model="editForm.tx_bandwidth" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="Root密码">
          <el-input v-model="editForm.root_password" placeholder="留空则不修改" show-password />
        </el-form-item>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="SSH端口">
              <el-input-number v-model="editForm.ssh_port" :min="1" :max="65535" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="流量上限(GB)">
              <el-input-number v-model="editForm.traffic_max" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="快照配额">
              <el-input-number v-model="editForm.snapshot_num" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="备份配额">
              <el-input-number v-model="editForm.backup_num" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="安全组">
          <el-select v-model="editForm.port_group_id" placeholder="选择安全组（可选）" filterable clearable style="width: 100%">
            <el-option v-for="g in sgOptions" :key="g.id" :label="`${g.name} (ID: ${g.id})`" :value="g.id" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="editDialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitEditVm">确定</el-button>
      </template>
    </el-dialog>

    <!-- 重构虚拟机弹窗 -->
    <el-dialog v-model="refactorDialogVisible" title="重构虚拟机" width="650px" destroy-on-close>
      <el-alert title="重构会修改虚拟机的底层配置参数，请谨慎操作！" type="warning" :closable="false" style="margin-bottom: 16px" />
      <el-form ref="refactorFormRef" :model="refactorForm" label-width="130px">
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="内存">
              <div style="display: flex; align-items: center; gap: 6px; width: 100%">
                <el-input-number v-model="refactorMemDisplay" :min="0" :step="refactorMemUnit === 1048576 ? 1 : (refactorMemUnit === 1024 ? 512 : 1024)" :precision="refactorMemUnit === 1048576 ? 2 : 0" controls-position="right" style="flex: 1" />
                <el-select v-model="refactorMemUnit" style="width: 80px" @change="onRefactorMemUnitChange">
                  <el-option label="KB" :value="1" />
                  <el-option label="MB" :value="1024" />
                  <el-option label="GB" :value="1048576" />
                </el-select>
              </div>
              <span style="color: #909399; font-size: 12px">（{{ refactorForm.memory }} KB）</span>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="CPU(核)">
              <el-input-number v-model="refactorForm.vcpu" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="下行带宽(Mbps)">
              <el-input-number v-model="refactorForm.rx_bandwidth" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="上行带宽(Mbps)">
              <el-input-number v-model="refactorForm.tx_bandwidth" :min="0" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="Root密码">
          <el-input v-model="refactorForm.root_password" placeholder="不修改留空" show-password />
        </el-form-item>
        <el-form-item label="UUID">
          <el-input v-model="refactorForm.uuid" placeholder="虚拟机UUID" />
        </el-form-item>
        <el-form-item label="Mate Data ID">
          <el-input v-model="refactorForm.mate_data_id" placeholder="元数据ID" />
        </el-form-item>
        <el-form-item label="物理机名称">
          <el-input v-model="refactorForm.physical_name" placeholder="不修改留空" />
        </el-form-item>
        <el-form-item label="配置路径">
          <el-input v-model="refactorForm.config_path" placeholder="不修改留空" />
        </el-form-item>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="SSH端口">
              <el-input-number v-model="refactorForm.ssh_port" :min="0" :max="65535" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="VNC端口">
              <el-input-number v-model="refactorForm.vnc_port" :min="0" :max="65535" controls-position="right" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-form-item label="VNC密码">
          <el-input v-model="refactorForm.vnc_password" placeholder="不填随机" show-password />
        </el-form-item>
        <el-form-item label="网络">
          <div style="width: 100%">
            <div style="display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px" v-if="refactorSelectedNetworks.length">
              <el-tag v-for="n in refactorSelectedNetworks" :key="n.id" closable size="small" @close="removeRefactorNetwork(n.id)">
                {{ n.name }} (ID:{{ n.id }})
              </el-tag>
            </div>
            <el-button size="small" @click="showRefactorNetworkSelector = true">选择网络</el-button>
          </div>
        </el-form-item>
        <el-form-item label="外网网络">
          <el-select v-model="refactorForm.internet_network_id" placeholder="选择外网网络（可选）" clearable filterable style="width: 100%">
            <el-option v-for="n in refactorSelectedNetworks" :key="n.id" :label="`${n.name} (ID: ${n.id})`" :value="n.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="安全组">
          <el-select v-model="refactorForm.port_group_id" placeholder="选择安全组（可选）" filterable clearable style="width: 100%">
            <el-option v-for="g in sgOptions" :key="g.id" :label="`${g.name} (ID: ${g.id})`" :value="g.id" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="refactorDialogVisible = false">取消</el-button>
        <el-button type="warning" :loading="actionLoading" @click="submitRefactorVm">确定重构</el-button>
      </template>
    </el-dialog>

    <!-- 重构用网络选择器 -->
    <NetworkSelectorPopup v-model="showRefactorNetworkSelector" :service-id="serviceId" :host-id="vmHostId" @confirm="handleRefactorNetworkConfirm" />

    <!-- VNC 连接弹窗 -->
    <el-dialog v-model="vncDialogVisible" title="获取 VNC 连接" width="560px" destroy-on-close>
      <el-form label-width="100px">
        <el-form-item label="虚拟机">{{ detail?.name || '-' }} (ID: {{ vmId }})</el-form-item>
        <el-form-item label="VNC节点">
          <el-select v-model="vncNodeId" placeholder="选择VNC节点" filterable style="width: 100%">
            <el-option v-for="n in vncNodeOptions" :key="n.id" :label="`${n.name} (${n.ip}:${n.port})`" :value="n.id" />
          </el-select>
        </el-form-item>
      </el-form>
      <div v-if="vncResult" class="vnc-result">
        <el-descriptions :column="1" border size="small">
          <el-descriptions-item label="VNC地址">
            <el-link type="primary" :href="vncResult.url" target="_blank">{{ vncResult.url }}</el-link>
          </el-descriptions-item>
          <el-descriptions-item label="过期时间">{{ formatTimestamp(vncResult.expire_at) }}</el-descriptions-item>
        </el-descriptions>
      </div>
      <template #footer>
        <el-button @click="vncDialogVisible = false">关闭</el-button>
        <el-button type="primary" :loading="vncLoading" @click="submitGetVnc">获取</el-button>
      </template>
    </el-dialog>

    <!-- 绑定/解绑安全组弹窗 -->
    <!-- 绑定安全组（弹窗选择组件） -->
    <SecurityGroupSelectorPopup v-model="sgBindSelectorVisible" :service-id="serviceId" @confirm="handleSgBindConfirm" />

    <!-- 编辑网络弹窗 -->
    <el-dialog v-model="netEditVisible" title="编辑网络" width="560px" destroy-on-close>
      <el-form :model="netEditForm" label-width="120px">
        <el-form-item label="名称">
          <el-input v-model="netEditForm.name" />
        </el-form-item>
        <el-form-item label="IP地址(CIDR)">
          <el-input v-model="netEditForm.address" placeholder="如 192.168.1.100/24" />
        </el-form-item>
        <el-form-item label="网关">
          <el-input v-model="netEditForm.gateway" placeholder="如 192.168.1.1" />
        </el-form-item>
        <el-form-item label="DNS">
          <el-input v-model="netEditForm.nameservers" placeholder="默认 114.114.114.114,8.8.8.8" />
        </el-form-item>
        <el-form-item label="网络类型">
          <el-select v-model="netEditForm.type" style="width: 100%">
            <el-option label="网桥 (bridge)" value="bridge" />
            <el-option label="内网 (nat)" value="nat" />
          </el-select>
        </el-form-item>
        <el-form-item label="MAC地址">
          <el-input v-model="netEditForm.mac_address" placeholder="不填随机" />
        </el-form-item>
        <el-form-item label="网桥名称">
          <el-input v-model="netEditForm.bridge_name" placeholder="不填默认" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="netEditVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitEditNetwork">确定</el-button>
      </template>
    </el-dialog>

    <!-- 调整卷大小弹窗 -->
    <el-dialog v-model="volResizeVisible" title="调整数据卷大小" width="440px" destroy-on-close>
      <el-form :model="volResizeForm" label-width="100px">
        <el-form-item label="卷名称">{{ volResizeForm._name || '-' }}</el-form-item>
        <el-form-item label="当前大小">{{ volResizeForm._currentSize }} GB</el-form-item>
        <el-form-item label="新大小(GB)">
          <el-input-number v-model="volResizeForm.size" :min="volResizeForm._currentSize || 1" controls-position="right" style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="volResizeVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitResizeVolume">确定</el-button>
      </template>
    </el-dialog>

    <!-- 创建网络弹窗 -->
    <el-dialog v-model="netCreateVisible" title="创建网络" width="600px" destroy-on-close>
      <el-form ref="netCreateFormRef" :model="netCreateForm" :rules="netCreateRules" label-width="120px">
        <el-form-item label="名称" prop="name">
          <el-input v-model="netCreateForm.name" placeholder="网络名称" />
        </el-form-item>
        <el-form-item label="网络类型" prop="type">
          <el-select v-model="netCreateForm.type" style="width: 100%">
            <el-option label="网桥(Bridge/外网)" value="bridge" />
            <el-option label="内网(NAT)" value="nat" />
          </el-select>
        </el-form-item>
        <el-form-item label="IP 地址(CIDR)" prop="address">
          <el-input v-model="netCreateForm.address" placeholder="例如 192.168.1.0/24" />
        </el-form-item>
        <el-form-item label="网关地址" prop="gateway">
          <el-input v-model="netCreateForm.gateway" placeholder="例如 192.168.1.1" />
        </el-form-item>
        <el-form-item label="DNS 服务器">
          <el-input v-model="netCreateForm.nameservers" placeholder="默认 114.114.114.114,8.8.8.8" />
        </el-form-item>
        <el-divider content-position="left">高级配置（可选）</el-divider>
        <el-form-item label="MAC 地址">
          <el-input v-model="netCreateForm.mac_address" placeholder="不填则随机" />
        </el-form-item>
        <el-form-item label="虚拟网桥名">
          <el-input v-model="netCreateForm.bridge_name" placeholder="不填使用默认" />
        </el-form-item>
        <el-form-item label="逻辑网桥名">
          <el-input v-model="netCreateForm.ls_bridge_name" placeholder="不填使用默认" />
        </el-form-item>
        <el-form-item label="逻辑端口名">
          <el-input v-model="netCreateForm.ls_name" placeholder="不填使用默认" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="netCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateNetwork">创建</el-button>
      </template>
    </el-dialog>

    <!-- 添加已有网络弹窗 -->
    <NetworkSelectorPopup v-model="netAddVisible" :service-id="serviceId" :host-id="vmHostId" @confirm="handleNetworkSelectorConfirm" />

    <!-- 创建数据卷弹窗 -->
    <el-dialog v-model="volCreateVisible" title="创建数据卷" width="550px" destroy-on-close>
      <el-form ref="volCreateFormRef" :model="volCreateForm" :rules="volCreateRules" label-width="120px">
        <el-form-item label="名称" prop="name">
          <el-input v-model="volCreateForm.name" placeholder="数据卷名称" />
        </el-form-item>
        <el-form-item label="大小(GB)" prop="size">
          <el-input-number v-model="volCreateForm.size" :min="1" :step="10" style="width: 100%" />
        </el-form-item>
        <el-form-item label="是否系统镜像">
          <el-switch v-model="volCreateForm.is_system" />
        </el-form-item>
        <el-form-item label="挂载设备名">
          <el-input v-model="volCreateForm.target_device" placeholder="不填自动生成" />
        </el-form-item>
        <el-form-item label="所属镜像ID">
          <el-input-number v-model="volCreateForm.image_id" :min="0" style="width: 100%" placeholder="镜像ID" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="volCreateVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitCreateVolume">创建</el-button>
      </template>
    </el-dialog>

    <!-- 挂载已有数据卷弹窗 -->
    <VolumeSelectorPopup v-model="volAddVisible" :service-id="serviceId" :host-id="vmHostId" @confirm="handleVolumeSelectorConfirm" />

    <!-- 迁移卷弹窗 -->
    <el-dialog v-model="volTransferVisible" title="迁移数据卷" width="480px" destroy-on-close>
      <el-form :model="volTransferForm" label-width="120px">
        <el-form-item label="卷名称">{{ volTransferForm._name || '-' }}</el-form-item>
        <el-form-item label="目标虚拟机" required>
          <el-select v-model="volTransferForm.vm_id" placeholder="选择目标虚拟机" filterable style="width: 100%">
            <el-option v-for="v in vmListOptions" :key="v.id" :label="`${v.name} (ID: ${v.id})`" :value="v.id" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="volTransferVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitTransferVolume">确定</el-button>
      </template>
    </el-dialog>

    <!-- 修改带宽弹窗 -->
    <el-dialog v-model="trafficDialogVisible" title="修改虚拟机带宽" width="520px" destroy-on-close>
      <el-form :model="trafficForm" label-width="140px">
        <el-form-item label="下行带宽(Mbps)">
          <el-input-number v-model="trafficForm.rx_bandwidth" :min="0" controls-position="right" style="width: 100%" />
        </el-form-item>
        <el-form-item label="上行带宽(Mbps)">
          <el-input-number v-model="trafficForm.tx_bandwidth" :min="0" controls-position="right" style="width: 100%" />
        </el-form-item>
        <el-form-item label="每月最大流量(KB)">
          <el-input-number v-model="trafficForm.traffic_max" :min="0" controls-position="right" style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="trafficDialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="actionLoading" @click="submitUpdateTraffic">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, onActivated, onDeactivated, onBeforeUnmount, nextTick, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowLeft, Refresh, ArrowDown } from '@element-plus/icons-vue'
import {
  getVmDetail, getVmStatus, getVmMetrics,
  startVm, stopVm, rebootVm, suspendVm, resumeVm,
  rebuildVm, refactorVm, updateVm, updateVmTraffic, rescueVm, exitRescueVm, deleteVm,
  getRemoteHostList, getVmVnc, getVncNodeList,
  bindSecurityGroup, unbindSecurityGroup, getSecurityGroupList,
  createNetwork, updateNetwork, deleteNetwork, getNetworkList,
  createVolume, resizeVolume, mountVolume, unmountVolume, transferVolume, deleteVolume, getVolumeList,
  getVmList,
  getSnapshotList, createSnapshot, restoreSnapshot, deleteSnapshot, getSnapshotProgress, getSnapshotCount, setSnapshotLimit,
  getBackupList, createBackup, restoreBackup, deleteBackup, getBackupProgress, getBackupCount, setBackupLimit
} from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
import * as echarts from 'echarts'
import ImageSelectorPopup from '@/components/admin/ImageSelectorPopup.vue'
import VolumeSelectorPopup from '@/components/admin/VolumeSelectorPopup.vue'
import NetworkSelectorPopup from '@/components/admin/NetworkSelectorPopup.vue'
import SecurityGroupSelectorPopup from '@/components/admin/SecurityGroupSelectorPopup.vue'
import { useTagsViewStore } from '@/store/tagsViewStore'

const route = useRoute()
const router = useRouter()
const tagsViewStore = useTagsViewStore()

const serviceId = computed(() => parseInt(route.query.service_id) || 0)
const serviceName = computed(() => route.query.service_name || '')
const vmId = computed(() => parseInt(route.query.vm_id) || parseInt(route.query.id) || 0)

const loading = ref(false)
const actionLoading = ref(false)
const statusLoading = ref(false)
const metricsLoading = ref(false)
const detail = ref(null)
const vmHostId = ref(0)
const vmNetworks = ref([])
const vmVolumes = ref([])
const vmImage = ref(null)

const networkPage = ref(1)
const networkPageSize = ref(10)
const pagedNetworks = computed(() => {
  const start = (networkPage.value - 1) * networkPageSize.value
  return vmNetworks.value.slice(start, start + networkPageSize.value)
})

const volumePage = ref(1)
const volumePageSize = ref(10)
const pagedVolumes = computed(() => {
  const start = (volumePage.value - 1) * volumePageSize.value
  return vmVolumes.value.slice(start, start + volumePageSize.value)
})
const vmPortGroup = ref(null)
const metricsData = ref(null)
const hostOptions = ref([])
const rebuildDialogVisible = ref(false)
const rebuildImageId = ref(0)
const rebuildImageName = ref('')
const showImageSelector = ref(false)
const activeTab = ref('info')
const showPassword = ref(false)

const copyText = (text) => {
  if (!text) { ElMessage.warning('无内容可复制'); return }
  if (navigator.clipboard && window.isSecureContext) {
    navigator.clipboard.writeText(text).then(() => ElMessage.success('已复制到剪贴板')).catch(() => fallbackCopy(text))
  } else {
    fallbackCopy(text)
  }
}
const fallbackCopy = (text) => {
  const ta = document.createElement('textarea')
  ta.value = text
  ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0'
  document.body.appendChild(ta)
  ta.select()
  try {
    document.execCommand('copy')
    ElMessage.success('已复制到剪贴板')
  } catch { ElMessage.error('复制失败，请手动复制') }
  document.body.removeChild(ta)
}

const handleMoreCommand = (cmd) => {
  const powerActions = ['start', 'stop', 'reboot', 'suspend', 'resume']
  if (powerActions.includes(cmd)) { handlePower(cmd); return }
  const actionMap = {
    editVm: handleEditVm, refactorVm: handleRefactorVm, updateTraffic: handleUpdateTraffic,
    bindSg: handleBindSg, unbindSg: handleUnbindSg,
    rebuild: handleRebuild, rescue: handleRescue, exitRescue: handleExitRescue
  }
  if (actionMap[cmd]) actionMap[cmd]()
}

const vmStatusType = (s) => ({ running: 'success', ready: 'success', creating: 'warning', pending: 'info', stopped: 'danger', stop: 'danger', error: 'danger', paused: 'warning', reboot: 'warning', poweroff: 'info', unknown: 'info' }[s] || 'info')
const vmStatusLabel = (s) => ({ running: '运行中', ready: '就绪', creating: '创建中', pending: '等待中', stopped: '已停止', stop: '已停止', error: '错误', paused: '已暂停', reboot: '重启中', poweroff: '已关机', unknown: '未知' }[s] || s || '-')
const imgStatusType = (s) => ({ ready: 'success', downloading: 'warning', pending: 'info', error: 'danger' }[s] || 'info')
const imgStatusLabel = (s) => ({ ready: '就绪', downloading: '下载中', pending: '等待中', error: '错误' }[s] || s || '-')

const formatMemory = (kb) => { if (!kb) return '-'; kb = Number(kb); if (kb >= 1073741824) return (kb / 1073741824).toFixed(1) + ' TB'; if (kb >= 1048576) return (kb / 1048576).toFixed(1) + ' GB'; if (kb >= 1024) return (kb / 1024).toFixed(0) + ' MB'; return kb + ' KB' }
const formatNetSpeed = (bytes) => { if (bytes == null) return '-'; const n = Number(bytes); if (n >= 1073741824) return (n / 1073741824).toFixed(2) + ' GB/s'; if (n >= 1048576) return (n / 1048576).toFixed(2) + ' MB/s'; if (n >= 1024) return (n / 1024).toFixed(2) + ' KB/s'; return n + ' B/s' }
const formatTimestamp = (ts) => {
  if (!ts) return '-'
  if (typeof ts === 'object' && ts.seconds) return new Date(Number(ts.seconds) * 1000).toLocaleString('zh-CN')
  if (typeof ts === 'string' || typeof ts === 'number') { const d = new Date(ts); return isNaN(d.getTime()) ? String(ts) : d.toLocaleString('zh-CN') }
  return '-'
}
const getHostLabel = (hid) => { const h = hostOptions.value.find(x => x.id === hid); return h ? h.name : (hid || '-') }

const loadHostOptions = async () => {
  try {
    const res = await getRemoteHostList({ service_id: serviceId.value, page: 1, page_size: 100 })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      hostOptions.value = Array.isArray(inner) ? inner : (inner.hosts || inner.list || inner.data || [])
    }
  } catch { /* */ }
}

const loadDetail = async () => {
  if (!vmId.value) return
  loading.value = true
  try {
    const res = await getVmDetail({ service_id: serviceId.value, vm_id: vmId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data
      detail.value = d.data ?? d.vm ?? d
      vmNetworks.value = d.networks || []
      vmVolumes.value = d.volumes || []
      vmImage.value = d.image || null
      vmPortGroup.value = d.in_port_group || null
      vmHostId.value = detail.value?.host_id || vmVolumes.value[0]?.host_id || vmNetworks.value[0]?.host_id || 0
    } else ElMessage.error(extractApiError(res?.data, '加载失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '加载失败')) } finally { loading.value = false }
}

const loadVmVolumes = async () => {
  if (!detail.value) return
  const hid = vmHostId.value
  if (!hid) return
  try {
    const res = await getVolumeList({ service_id: serviceId.value, host_id: hid, vm_id: vmId.value, page: 1, count: 200 })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      vmVolumes.value = inner.data || inner.volumes || (Array.isArray(inner) ? inner : [])
    }
  } catch { /* */ }
}

const loadVmNetworks = async () => {
  if (!detail.value) return
  const hid = vmHostId.value
  if (!hid) return
  try {
    const res = await getNetworkList({ service_id: serviceId.value, host_id: hid, page: 1, page_size: 200 })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      vmNetworks.value = inner.data || inner.networks || (Array.isArray(inner) ? inner : [])
    }
  } catch { /* */ }
}

const fetchVmStatus = async () => {
  if (!detail.value) return
  statusLoading.value = true
  try {
    const res = await getVmStatus({ service_id: serviceId.value, vm_id: vmId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const sd = res.data.data
      detail.value = { ...detail.value, status: sd.status ?? sd }
      ElMessage.success('状态已刷新: ' + vmStatusLabel(detail.value.status))
    }
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取状态失败')) } finally { statusLoading.value = false }
}

// ---- ECharts 监控 ----
const cpuChartRef = ref(null)
const netChartRef = ref(null)
let cpuChart = null
let netChart = null
const MAX_HISTORY = 60
const metricsHistory = reactive({ times: [], cpu: [], netKeys: [], netSeries: {} })
const pollingActive = ref(false)
let pollTimer = null
let isPageActive = false

const fetchVmMetrics = async () => {
  if (!detail.value || !isPageActive) return
  metricsLoading.value = true
  try {
    const res = await getVmMetrics({ service_id: serviceId.value, vm_name: detail.value.name })
    if (res?.data?.code === 200) {
      metricsData.value = res.data.data?.data ?? res.data.data
      pushHistory(metricsData.value)
      await nextTick()
      renderCharts()
    }
  } catch { /* silent for polling */ } finally { metricsLoading.value = false }
}

const pushHistory = (d) => {
  const now = new Date().toLocaleTimeString('zh-CN', { hour12: false })
  metricsHistory.times.push(now)
  metricsHistory.cpu.push(d.cpu_usage_percent ?? 0)
  if (d.internet_speed && typeof d.internet_speed === 'object') {
    for (const key of Object.keys(d.internet_speed)) {
      if (!metricsHistory.netSeries[key]) {
        metricsHistory.netKeys.push(key)
        metricsHistory.netSeries[key] = []
        const fillCount = metricsHistory.times.length - 1
        for (let i = 0; i < fillCount; i++) metricsHistory.netSeries[key].push(0)
      }
      metricsHistory.netSeries[key].push(Number(d.internet_speed[key]) || 0)
    }
    for (const key of metricsHistory.netKeys) {
      if (metricsHistory.netSeries[key].length < metricsHistory.times.length) {
        metricsHistory.netSeries[key].push(0)
      }
    }
  }
  if (metricsHistory.times.length > MAX_HISTORY) {
    metricsHistory.times.shift()
    metricsHistory.cpu.shift()
    for (const key of metricsHistory.netKeys) {
      metricsHistory.netSeries[key]?.shift()
    }
  }
}

const makeLineOption = (seriesArr) => ({
  tooltip: { trigger: 'axis' },
  grid: { top: 10, right: 16, bottom: 24, left: 50 },
  xAxis: { type: 'category', data: metricsHistory.times, boundaryGap: false, axisLabel: { fontSize: 10 } },
  yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10 } },
  series: seriesArr.map(s => ({
    name: s.name, type: 'line', smooth: true, symbol: 'none',
    areaStyle: { opacity: 0.15 }, lineStyle: { width: 2, color: s.color },
    itemStyle: { color: s.color }, data: s.data
  }))
})

const COLORS = ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399']

const renderCharts = () => {
  const times = [...metricsHistory.times]
  const cpuData = [...metricsHistory.cpu]

  if (cpuChartRef.value) {
    if (!cpuChart) cpuChart = echarts.init(cpuChartRef.value)
    cpuChart.setOption({
      tooltip: { trigger: 'axis', formatter: (params) => `${params[0].axisValue}<br/>${params[0].marker} CPU: ${Number(params[0].value).toFixed(2)}%` },
      grid: { top: 10, right: 16, bottom: 24, left: 50 },
      xAxis: { type: 'category', data: times, boundaryGap: false, axisLabel: { fontSize: 10 } },
      yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10, formatter: v => v.toFixed(1) + '%' } },
      series: [{ name: 'CPU', type: 'line', smooth: true, symbol: 'none', areaStyle: { opacity: 0.15 }, lineStyle: { width: 2, color: '#409eff' }, itemStyle: { color: '#409eff' }, data: cpuData }]
    }, true)
  }
  if (netChartRef.value) {
    if (!netChart) netChart = echarts.init(netChartRef.value)
    const series = metricsHistory.netKeys.map((key, i) => ({
      name: key, type: 'line', smooth: true, symbol: 'none',
      areaStyle: { opacity: 0.15 }, lineStyle: { width: 2, color: COLORS[i % COLORS.length] },
      itemStyle: { color: COLORS[i % COLORS.length] }, data: [...(metricsHistory.netSeries[key] || [])]
    }))
    if (series.length === 0) series.push({ name: '网络', type: 'line', smooth: true, symbol: 'none', data: [], itemStyle: { color: '#409eff' } })
    netChart.setOption({
      tooltip: { trigger: 'axis', formatter: (params) => {
        let s = params[0]?.axisValue || ''
        params.forEach(p => {
          const label = p.seriesName === 'rx_bytes' ? '↓ 接收' : p.seriesName === 'tx_bytes' ? '↑ 发送' : p.seriesName
          s += `<br/>${p.marker} ${label}: ${formatNetSpeed(p.value)}`
        })
        return s
      }},
      grid: { top: 10, right: 16, bottom: 24, left: 50 },
      xAxis: { type: 'category', data: times, boundaryGap: false, axisLabel: { fontSize: 10 } },
      yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10 } },
      series
    }, true)
  }
}

const pollMetrics = () => { fetchVmMetrics() }

const startPolling = () => {
  if (!isPageActive || !serviceId.value || !vmId.value) return
  stopPolling()
  pollingActive.value = true
  fetchVmMetrics()
  pollTimer = setInterval(() => { fetchVmMetrics() }, 3000)
}

const stopPolling = () => {
  pollingActive.value = false
  if (pollTimer) { clearInterval(pollTimer); pollTimer = null }
}

const disposeCharts = () => {
  cpuChart?.dispose(); cpuChart = null
  netChart?.dispose(); netChart = null
}

const clearHistory = () => {
  metricsHistory.times.length = 0
  metricsHistory.cpu.length = 0
  metricsHistory.netKeys.length = 0
  for (const k in metricsHistory.netSeries) delete metricsHistory.netSeries[k]
}

const handlePower = (action) => {
  const labels = { start: '启动', stop: '停止', reboot: '重启', suspend: '暂停', resume: '恢复' }
  ElMessageBox.confirm(`确定要${labels[action]}虚拟机「${detail.value?.name}」吗？`, `${labels[action]}确认`, {
    confirmButtonText: '确定', cancelButtonText: '取消', type: action === 'stop' ? 'warning' : 'info'
  }).then(async () => {
    try {
      const apis = { start: startVm, stop: stopVm, reboot: rebootVm, suspend: suspendVm, resume: resumeVm }
      let res
      if (action === 'resume') {
        const fd = new FormData(); fd.append('service_id', serviceId.value); fd.append('vm_id', vmId.value)
        res = await resumeVm(fd)
      } else {
        res = await apis[action]({ service_id: serviceId.value, vm_id: vmId.value })
      }
      if (res?.data?.code === 200) { ElMessage.success(`${labels[action]}成功`); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, `${labels[action]}失败`))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, `${labels[action]}失败`)) }
  }).catch(() => {})
}

const handleRebuild = () => {
  rebuildImageId.value = detail.value?.image_id || 0
  rebuildImageName.value = ''
  rebuildDialogVisible.value = true
}

const submitRebuild = async () => {
  if (!rebuildImageId.value) { ElMessage.warning('请选择镜像'); return }
  actionLoading.value = true
  try {
    const res = await rebuildVm({ service_id: serviceId.value, vm_id: vmId.value, image_id: rebuildImageId.value })
    if (res?.data?.code === 200) { ElMessage.success('重建成功'); rebuildDialogVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '重建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '重建失败')) } finally { actionLoading.value = false }
}

const handleRescue = () => {
  ElMessageBox.confirm(`确定让虚拟机「${detail.value?.name}」进入救援模式吗？`, '救援模式', {
    confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
  }).then(async () => {
    const fd = new FormData(); fd.append('service_id', serviceId.value); fd.append('vm_id', vmId.value)
    try {
      const res = await rescueVm(fd)
      if (res?.data?.code === 200) { ElMessage.success('已进入救援模式'); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '操作失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) }
  }).catch(() => {})
}

const handleExitRescue = () => {
  ElMessageBox.confirm(`确定让虚拟机「${detail.value?.name}」退出救援模式吗？`, '退出救援', {
    confirmButtonText: '确定', cancelButtonText: '取消', type: 'info'
  }).then(async () => {
    const fd = new FormData(); fd.append('service_id', serviceId.value); fd.append('vm_id', vmId.value)
    try {
      const res = await exitRescueVm(fd)
      if (res?.data?.code === 200) { ElMessage.success('已退出救援模式'); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '操作失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) }
  }).catch(() => {})
}

// ---- 编辑虚拟机 ----
const editDialogVisible = ref(false)
const editFormRef = ref(null)
const editForm = reactive({
  name: '', memory: 0, vcpu: 1,
  rx_bandwidth: 0, tx_bandwidth: 0,
  root_password: '', ssh_port: 22,
  traffic_max: 0, snapshot_num: 0, backup_num: 0,
  port_group_id: 0
})
const editMemoryUnit = ref('MB')
const editMemUnitFactor = () => editMemoryUnit.value === 'GB' ? 1048576 : 1024
const editMemoryDisplay = computed({
  get: () => {
    const f = editMemUnitFactor()
    const v = editForm.memory / f
    return f === 1048576 ? parseFloat(v.toFixed(2)) : Math.round(v)
  },
  set: (v) => { editForm.memory = Math.round(v * editMemUnitFactor()) }
})

const handleEditVm = async () => {
  if (!detail.value) return
  const d = detail.value
  const mem = d.memory || 0
  editMemoryUnit.value = mem >= 1048576 ? 'GB' : 'MB'
  Object.assign(editForm, {
    name: d.name || '',
    memory: mem, vcpu: d.vcpu || 1,
    rx_bandwidth: d.rx_bandwidth || 0,
    tx_bandwidth: d.tx_bandwidth || 0,
    root_password: d.root_password || '',
    ssh_port: d.ssh_port || 22,
    traffic_max: d.traffic_max || 0,
    snapshot_num: d.snapshot_num || 0,
    backup_num: d.backup_num || 0,
    port_group_id: vmPortGroup.value?.id || null
  })
  if (!sgOptions.value.length) await loadSgOptions()
  editDialogVisible.value = true
}

const submitEditVm = async () => {
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('vm_id', vmId.value)
    if (editForm.name) fd.append('name', editForm.name)
    fd.append('memory', editForm.memory)
    fd.append('vcpu', editForm.vcpu)
    fd.append('rx_bandwidth', editForm.rx_bandwidth)
    fd.append('tx_bandwidth', editForm.tx_bandwidth)
    fd.append('ssh_port', editForm.ssh_port)
    fd.append('traffic_max', editForm.traffic_max)
    fd.append('snapshot_num', editForm.snapshot_num)
    fd.append('backup_num', editForm.backup_num)
    if (editForm.root_password) fd.append('root_password', editForm.root_password)
    if (editForm.port_group_id) fd.append('port_group_id', editForm.port_group_id)
    const res = await updateVm(fd)
    if (res?.data?.code === 200) { ElMessage.success('修改成功'); editDialogVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '修改失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
}

// ---- 重构虚拟机 ----
const refactorDialogVisible = ref(false)
const refactorFormRef = ref(null)
const refactorForm = reactive({
  memory: 0, vcpu: 0, rx_bandwidth: 0, tx_bandwidth: 0,
  root_password: '', uuid: '', mate_data_id: '', physical_name: '', config_path: '',
  ssh_port: 0, vnc_port: 0, vnc_password: '',
  internet_network_id: 0, port_group_id: 0
})
const refactorMemUnit = ref(1048576)
const refactorMemDisplay = ref(0)
const refactorSelectedNetworks = ref([])
const showRefactorNetworkSelector = ref(false)

const onRefactorMemUnitChange = () => {
  refactorMemDisplay.value = refactorForm.memory ? Math.round(refactorForm.memory / refactorMemUnit.value * 100) / 100 : 0
}
watch(refactorMemDisplay, (v) => { refactorForm.memory = Math.round(v * refactorMemUnit.value) })

const handleRefactorNetworkConfirm = (network) => {
  if (!refactorSelectedNetworks.value.find(n => n.id === network.id)) {
    refactorSelectedNetworks.value.push({ id: network.id, name: network.name })
  }
}
const removeRefactorNetwork = (id) => {
  refactorSelectedNetworks.value = refactorSelectedNetworks.value.filter(n => n.id !== id)
  if (refactorForm.internet_network_id === id) refactorForm.internet_network_id = 0
}

const handleRefactorVm = async () => {
  if (!detail.value) return
  const d = detail.value
  Object.assign(refactorForm, {
    memory: d.memory || 0, vcpu: d.vcpu || 0,
    rx_bandwidth: d.rx_bandwidth || 0, tx_bandwidth: d.tx_bandwidth || 0,
    root_password: d.root_password || '',
    uuid: d.uuid || '', mate_data_id: d.mate_data_id || '',
    physical_name: d.physical_name || '', config_path: d.config_path || '',
    ssh_port: d.ssh_port || 0, vnc_port: 0, vnc_password: '',
    internet_network_id: 0,
    port_group_id: vmPortGroup.value?.id || 0
  })
  refactorSelectedNetworks.value = vmNetworks.value.map(n => ({ id: n.id, name: n.name }))
  const mem = d.memory || 0
  if (mem >= 1048576 && mem % 1048576 === 0) { refactorMemUnit.value = 1048576; refactorMemDisplay.value = mem / 1048576 }
  else if (mem >= 1024 && mem % 1024 === 0) { refactorMemUnit.value = 1024; refactorMemDisplay.value = mem / 1024 }
  else { refactorMemUnit.value = 1; refactorMemDisplay.value = mem }
  if (!sgOptions.value.length) await loadSgOptions()
  refactorDialogVisible.value = true
}

const submitRefactorVm = async () => {
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('vm_id', vmId.value)
    if (refactorForm.memory) fd.append('memory', refactorForm.memory)
    if (refactorForm.vcpu) fd.append('vcpu', refactorForm.vcpu)
    fd.append('rx_bandwidth', refactorForm.rx_bandwidth)
    fd.append('tx_bandwidth', refactorForm.tx_bandwidth)
    if (refactorForm.root_password) fd.append('root_password', refactorForm.root_password)
    if (refactorForm.uuid) fd.append('uuid', refactorForm.uuid)
    if (refactorForm.mate_data_id) fd.append('mate_data_id', refactorForm.mate_data_id)
    if (refactorForm.physical_name) fd.append('physical_name', refactorForm.physical_name)
    if (refactorForm.config_path) fd.append('config_path', refactorForm.config_path)
    if (refactorForm.ssh_port) fd.append('ssh_port', refactorForm.ssh_port)
    if (refactorForm.vnc_port) fd.append('vnc_port', refactorForm.vnc_port)
    if (refactorForm.vnc_password) fd.append('vnc_password', refactorForm.vnc_password)
    if (refactorSelectedNetworks.value.length) fd.append('network_ids', refactorSelectedNetworks.value.map(n => n.id).join(','))
    if (refactorForm.internet_network_id) fd.append('internet_network_id', refactorForm.internet_network_id)
    if (refactorForm.port_group_id) fd.append('port_group_id', refactorForm.port_group_id)
    const res = await refactorVm(fd)
    if (res?.data?.code === 200) { ElMessage.success('重构成功'); refactorDialogVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '重构失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '重构失败')) } finally { actionLoading.value = false }
}

// ---- 修改带宽 ----
const trafficDialogVisible = ref(false)
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, traffic_max: 0 })

const handleUpdateTraffic = () => {
  if (!detail.value) return
  Object.assign(trafficForm, {
    rx_bandwidth: detail.value.rx_bandwidth || 0,
    tx_bandwidth: detail.value.tx_bandwidth || 0,
    traffic_max: detail.value.traffic_max || 0
  })
  trafficDialogVisible.value = true
}

const submitUpdateTraffic = async () => {
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('vm_id', vmId.value)
    fd.append('rx_bandwidth', trafficForm.rx_bandwidth)
    fd.append('tx_bandwidth', trafficForm.tx_bandwidth)
    if (trafficForm.traffic_max) fd.append('traffic_max', trafficForm.traffic_max)
    const res = await updateVmTraffic(fd)
    if (res?.data?.code === 200) { ElMessage.success('带宽修改成功'); trafficDialogVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '修改失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
}

// ---- VNC 连接 ----
const vncDialogVisible = ref(false)
const vncNodeId = ref(null)
const vncNodeOptions = ref([])
const vncLoading = ref(false)
const vncResult = ref(null)

const loadVncNodes = async () => {
  try {
    const res = await getVncNodeList({ service_id: serviceId.value, page: 1, page_size: 100 })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      vncNodeOptions.value = inner.items || inner.vnc_nodes || inner.nodes || inner.data || (Array.isArray(inner) ? inner : [])
    }
  } catch { /* */ }
}

const handleGetVnc = async () => {
  vncNodeId.value = null
  vncResult.value = null
  if (!vncNodeOptions.value.length) await loadVncNodes()
  vncDialogVisible.value = true
}

const submitGetVnc = async () => {
  vncLoading.value = true
  vncResult.value = null
  try {
    const res = await getVmVnc({ service_id: serviceId.value, id: vncNodeId.value, vm_id: vmId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      vncResult.value = res.data.data
    } else ElMessage.error(extractApiError(res?.data, '获取VNC连接失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取VNC连接失败')) } finally { vncLoading.value = false }
}

// ---- 安全组管理（标签页列表） ----
const vmSecurityGroups = ref([])
const sgListLoading = ref(false)
const securityPage = ref(1)
const securityPageSize = ref(10)
const sgDirectionFilter = ref('')
const filteredSecurityGroups = computed(() => {
  if (!sgDirectionFilter.value) return vmSecurityGroups.value
  return vmSecurityGroups.value.filter(g => g.direction === sgDirectionFilter.value)
})
const pagedSecurityGroups = computed(() => {
  const start = (securityPage.value - 1) * securityPageSize.value
  return filteredSecurityGroups.value.slice(start, start + securityPageSize.value)
})

const loadVmSecurityGroups = async () => {
  sgListLoading.value = true
  try {
    const res = await getSecurityGroupList({ service_id: serviceId.value, page: 1, page_size: 200, vm_id: vmId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      vmSecurityGroups.value = inner.groups || inner.post_groups || inner.data || (Array.isArray(inner) ? inner : [])
    }
  } catch { /* */ } finally { sgListLoading.value = false }
}

// ---- 绑定/解绑安全组 ----
const sgBindSelectorVisible = ref(false)
const sgOptions = ref([])

const loadSgOptions = async () => {
  try {
    const res = await getSecurityGroupList({ service_id: serviceId.value, page: 1, page_size: 200 })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      sgOptions.value = inner.groups || inner.post_groups || inner.data || (Array.isArray(inner) ? inner : [])
    }
  } catch { /* */ }
}

const handleBindSg = () => { sgBindSelectorVisible.value = true }
const handleUnbindSg = () => { sgBindSelectorVisible.value = true }
const handleBindSgFromTab = () => { sgBindSelectorVisible.value = true }

const handleSgBindConfirm = async (sg) => {
  if (!sg?.id) return
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('id', sg.id)
    fd.append('vm_id', vmId.value)
    const res = await bindSecurityGroup(fd)
    if (res?.data?.code === 200) { ElMessage.success('绑定成功'); loadVmSecurityGroups() }
    else ElMessage.error(extractApiError(res?.data, '绑定失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '绑定失败')) } finally { actionLoading.value = false }
}

const handleUnbindSgFromTab = async (row) => {
  try {
    await ElMessageBox.confirm(`确认解绑安全组「${row.name}」？`, '提示', { type: 'warning' })
  } catch { return }
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('id', row.id)
    fd.append('vm_id', vmId.value)
    const res = await unbindSecurityGroup(fd)
    if (res?.data?.code === 200) { ElMessage.success('解绑成功'); loadVmSecurityGroups() }
    else ElMessage.error(extractApiError(res?.data, '解绑失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '解绑失败')) } finally { actionLoading.value = false }
}

// ---- 创建网络（表单弹窗） ----
const netCreateVisible = ref(false)
const netCreateFormRef = ref(null)
const netCreateForm = reactive({ name: '', address: '', gateway: '', nameservers: '', type: 'bridge', mac_address: '', bridge_name: '', ls_bridge_name: '', ls_name: '' })
const netCreateRules = {
  name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
  address: [{ required: true, message: '请输入IP地址(CIDR)', trigger: 'blur' }],
  gateway: [{ required: true, message: '请输入网关地址', trigger: 'blur' }],
  type: [{ required: true, message: '请选择网络类型', trigger: 'change' }]
}

const showCreateNetworkDialog = () => {
  Object.assign(netCreateForm, { name: '', address: '', gateway: '', nameservers: '', type: 'bridge', mac_address: '', bridge_name: '', ls_bridge_name: '', ls_name: '' })
  netCreateVisible.value = true
}

const submitCreateNetwork = () => {
  netCreateFormRef.value?.validate(async (valid) => {
    if (!valid) return
    actionLoading.value = true
    try {
      const fd = new FormData()
      fd.append('service_id', serviceId.value)
      fd.append('name', netCreateForm.name)
      fd.append('address', netCreateForm.address)
      fd.append('gateway', netCreateForm.gateway)
      fd.append('type', netCreateForm.type)
      fd.append('host_id', vmHostId.value)
      if (netCreateForm.nameservers) fd.append('nameservers', netCreateForm.nameservers)
      if (netCreateForm.mac_address) fd.append('mac_address', netCreateForm.mac_address)
      if (netCreateForm.bridge_name) fd.append('bridge_name', netCreateForm.bridge_name)
      if (netCreateForm.ls_bridge_name) fd.append('ls_bridge_name', netCreateForm.ls_bridge_name)
      if (netCreateForm.ls_name) fd.append('ls_name', netCreateForm.ls_name)
      const res = await createNetwork(fd)
      if (res?.data?.code === 200) { ElMessage.success('网络创建成功'); netCreateVisible.value = false; loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '创建失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
  })
}

// ---- 添加已有网络（弹窗选择组件） ----
const netAddVisible = ref(false)

const handleAddNetwork = () => { netAddVisible.value = true }

const handleNetworkSelectorConfirm = async (network) => {
  if (!network?.id) return
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('vm_id', vmId.value)
    fd.append('network_id', network.id)
    if (network.host_id) fd.append('host_id', network.host_id)
    const res = await createNetwork(fd)
    if (res?.data?.code === 200) { ElMessage.success('网络添加成功'); loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '添加失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '添加失败')) } finally { actionLoading.value = false }
}

// ---- 网络编辑/删除 ----
const netEditVisible = ref(false)
const netEditForm = reactive({ id: 0, name: '', address: '', gateway: '', nameservers: '', type: 'bridge', mac_address: '', bridge_name: '', host_id: 0 })

const handleEditNetwork = (row) => {
  Object.assign(netEditForm, {
    id: row.id, name: row.name || '', address: row.address || '', gateway: row.gateway || '',
    nameservers: row.nameservers || '', type: row.type || 'bridge',
    mac_address: row.mac_address || '', bridge_name: row.bridge_name || '', host_id: row.host_id || 0
  })
  netEditVisible.value = true
}

const submitEditNetwork = async () => {
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('id', netEditForm.id)
    fd.append('host_id', netEditForm.host_id)
    if (netEditForm.name) fd.append('name', netEditForm.name)
    if (netEditForm.address) fd.append('address', netEditForm.address)
    if (netEditForm.gateway) fd.append('gateway', netEditForm.gateway)
    if (netEditForm.nameservers) fd.append('nameservers', netEditForm.nameservers)
    if (netEditForm.type) fd.append('type', netEditForm.type)
    if (netEditForm.mac_address) fd.append('mac_address', netEditForm.mac_address)
    if (netEditForm.bridge_name) fd.append('bridge_name', netEditForm.bridge_name)
    const res = await updateNetwork(fd)
    if (res?.data?.code === 200) { ElMessage.success('网络修改成功'); netEditVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '修改失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
}

const handleDeleteNetwork = (row) => {
  ElMessageBox.confirm(`确定要删除网络「${row.name}」(ID: ${row.id}) 吗？`, '删除网络', {
    confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
  }).then(async () => {
    try {
      const res = await deleteNetwork({ service_id: serviceId.value, network_id: row.id, host_id: row.host_id })
      if (res?.data?.code === 200) { ElMessage.success('网络已删除'); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '删除失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
  }).catch(() => {})
}

// ---- 创建数据卷（表单弹窗） ----
const volCreateVisible = ref(false)
const volCreateFormRef = ref(null)
const volCreateForm = reactive({ name: '', size: 10, is_system: false, target_device: '', image_id: 0 })
const volCreateRules = {
  name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
  size: [{ required: true, message: '请输入大小', trigger: 'blur' }]
}

const showCreateVolumeDialog = () => {
  Object.assign(volCreateForm, { name: '', size: 10, is_system: false, target_device: '', image_id: 0 })
  volCreateVisible.value = true
}

const submitCreateVolume = () => {
  volCreateFormRef.value?.validate(async (valid) => {
    if (!valid) return
    actionLoading.value = true
    try {
      const fd = new FormData()
      fd.append('service_id', serviceId.value)
      fd.append('name', volCreateForm.name)
      fd.append('size', volCreateForm.size)
      fd.append('is_system', volCreateForm.is_system)
      fd.append('vm_id', vmId.value)
      fd.append('host_id', vmHostId.value)
      if (volCreateForm.target_device) fd.append('target_device', volCreateForm.target_device)
      if (volCreateForm.image_id) fd.append('image_id', volCreateForm.image_id)
      const res = await createVolume(fd)
      if (res?.data?.code === 200) { ElMessage.success('数据卷创建成功'); volCreateVisible.value = false; loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '创建失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
  })
}

// ---- 挂载已有数据卷（弹窗选择组件） ----
const volAddVisible = ref(false)

const handleAddVolume = () => { volAddVisible.value = true }

const handleVolumeSelectorConfirm = async (volume) => {
  if (!volume?.id) return
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('vm_id', vmId.value)
    fd.append('volume_id', volume.id)
    const res = await mountVolume(fd)
    if (res?.data?.code === 200) { ElMessage.success('数据卷挂载成功'); loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '挂载失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '挂载失败')) } finally { actionLoading.value = false }
}

// ---- 磁盘卷操作 ----
const volResizeVisible = ref(false)
const volResizeForm = reactive({ volume_id: 0, size: 0, _name: '', _currentSize: 0 })

const handleResizeVolume = (row) => {
  Object.assign(volResizeForm, { volume_id: row.id, size: row.size || 0, _name: row.name, _currentSize: row.size || 0 })
  volResizeVisible.value = true
}

const submitResizeVolume = async () => {
  if (volResizeForm.size <= 0) { ElMessage.warning('请输入有效大小'); return }
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('volume_id', volResizeForm.volume_id)
    fd.append('size', volResizeForm.size)
    const res = await resizeVolume(fd)
    if (res?.data?.code === 200) { ElMessage.success('调整大小成功'); volResizeVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '调整大小失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '调整大小失败')) } finally { actionLoading.value = false }
}

const handleUnmountVolume = (row) => {
  ElMessageBox.confirm(`确定要卸载磁盘卷「${row.name}」(ID: ${row.id}) 吗？`, '卸载磁盘卷', {
    confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
  }).then(async () => {
    try {
      const fd = new FormData()
      fd.append('service_id', serviceId.value)
      fd.append('volume_id', row.id)
      const res = await unmountVolume(fd)
      if (res?.data?.code === 200) { ElMessage.success('卸载成功'); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '卸载失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '卸载失败')) }
  }).catch(() => {})
}

const handleDeleteVolume = (row) => {
  ElMessageBox.confirm(`确定要删除磁盘卷「${row.name}」(ID: ${row.id}) 吗？此操作不可恢复！`, '删除磁盘卷', {
    confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'error'
  }).then(async () => {
    try {
      const res = await deleteVolume({ service_id: serviceId.value, volume_id: row.id })
      if (res?.data?.code === 200) { ElMessage.success('磁盘卷已删除'); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '删除失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
  }).catch(() => {})
}

const handleMountVolume = (row) => {
  ElMessageBox.confirm(`确定要将磁盘卷「${row.name}」(ID: ${row.id}) 挂载到当前虚拟机吗？`, '挂载磁盘卷', {
    confirmButtonText: '确定', cancelButtonText: '取消', type: 'info'
  }).then(async () => {
    try {
      const fd = new FormData()
      fd.append('service_id', serviceId.value)
      fd.append('volume_id', row.id)
      fd.append('vm_id', vmId.value)
      const res = await mountVolume(fd)
      if (res?.data?.code === 200) { ElMessage.success('挂载成功'); loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '挂载失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '挂载失败')) }
  }).catch(() => {})
}

// ---- 迁移卷 ----
const volTransferVisible = ref(false)
const volTransferForm = reactive({ volume_id: 0, vm_id: null, _name: '' })
const vmListOptions = ref([])

const loadVmListOptions = async () => {
  try {
    const res = await getVmList({ service_id: serviceId.value, page: 1, page_size: 200 })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      vmListOptions.value = (inner.vms || inner.data || (Array.isArray(inner) ? inner : [])).filter(v => v.id !== vmId.value)
    }
  } catch { /* */ }
}

const handleTransferVolume = async (row) => {
  Object.assign(volTransferForm, { volume_id: row.id, vm_id: null, _name: row.name })
  if (!vmListOptions.value.length) await loadVmListOptions()
  volTransferVisible.value = true
}

const submitTransferVolume = async () => {
  if (!volTransferForm.vm_id) { ElMessage.warning('请选择目标虚拟机'); return }
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('volume_id', volTransferForm.volume_id)
    fd.append('vm_id', volTransferForm.vm_id)
    const res = await transferVolume(fd)
    if (res?.data?.code === 200) { ElMessage.success('迁移成功'); volTransferVisible.value = false; loadDetail() }
    else ElMessage.error(extractApiError(res?.data, '迁移失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '迁移失败')) } finally { actionLoading.value = false }
}

// ---- 快照/备份管理 ----
const snapshotList = ref([])
const snapshotLoading = ref(false)
const snapshotQuota = ref(null)
const snapshotPage = ref(1)
const snapshotPageSize = ref(10)
const snapshotTotal = ref(0)
const backupList = ref([])
const backupLoading = ref(false)
const backupQuota = ref(null)
const backupPage = ref(1)
const backupPageSize = ref(10)
const backupTotal = ref(0)
const snapshotCreateVisible = ref(false)
const backupCreateVisible = ref(false)
const snapshotForm = reactive({ name: '' })
const backupForm = reactive({ name: '' })
const taskProgressVisible = ref(false)
const taskProgressLoading = ref(false)
const taskProgressData = ref(null)
const taskProgressTitle = ref('')

const taskStatusType = (s) => ({ running: 'primary', completed: 'success', ready: 'success', success: 'success', failed: 'danger', error: 'danger', pending: 'info' }[s] || 'info')
const snapshotStatusLabel = (s) => ({ completed: '完成', ready: '完成', success: '成功', pending: '等待', running: '运行中', failed: '失败', error: '错误' }[s] || s || '-')
const taskMetaLabel = (key) => ({ vm_name: '虚拟机名称', backup_path: '备份路径', snapshot_path: '快照路径', path: '路径', progress: '进度', message: '信息', error: '错误信息' }[key] || key)

const taskProgressMeta = computed(() => {
  if (!taskProgressData.value?.meta) return null
  const raw = taskProgressData.value.meta
  if (typeof raw === 'object') return raw
  if (typeof raw === 'string') {
    const trimmed = raw.trim()
    if (!trimmed || trimmed === '""' || trimmed === '{}') return null
    try { return JSON.parse(trimmed) } catch { return { 信息: raw } }
  }
  return null
})

const loadSnapshots = async () => {
  snapshotLoading.value = true
  try {
    const res = await getSnapshotList({ service_id: serviceId.value, vm_id: vmId.value, page: snapshotPage.value, page_size: snapshotPageSize.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data
      snapshotList.value = d.data || d.list || (Array.isArray(d) ? d : [])
      snapshotTotal.value = d.meta?.count ?? d.total ?? snapshotList.value.length
    } else { snapshotList.value = []; snapshotTotal.value = 0 }
  } catch { snapshotList.value = []; snapshotTotal.value = 0 } finally { snapshotLoading.value = false }
}

const loadSnapshotQuota = async () => {
  try {
    const res = await getSnapshotCount({ service_id: serviceId.value, vm_id: vmId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data
      snapshotQuota.value = { count: d.count ?? 0, limit: d.limit ?? 0 }
    }
  } catch { /* ignore */ }
}

const handleSetSnapshotLimit = () => {
  ElMessageBox.prompt('请输入快照数量上限', '设置快照上限', {
    confirmButtonText: '确定', cancelButtonText: '取消',
    inputPattern: /^[1-9]\d*$/, inputErrorMessage: '请输入正整数',
    inputValue: String(snapshotQuota.value?.limit || 10)
  }).then(async ({ value }) => {
    try {
      const fd = new FormData()
      fd.append('service_id', serviceId.value)
      fd.append('vm_id', vmId.value)
      fd.append('limit', value)
      const res = await setSnapshotLimit(fd)
      if (res?.data?.code === 200) { ElMessage.success('快照上限设置成功'); loadSnapshotQuota() }
      else ElMessage.error(extractApiError(res?.data, '设置失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '设置失败')) }
  }).catch(() => {})
}

const loadBackups = async () => {
  backupLoading.value = true
  try {
    const res = await getBackupList({ service_id: serviceId.value, vm_id: vmId.value, page: backupPage.value, page_size: backupPageSize.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data
      backupList.value = d.data || d.list || (Array.isArray(d) ? d : [])
      backupTotal.value = d.meta?.count ?? d.total ?? backupList.value.length
    } else { backupList.value = []; backupTotal.value = 0 }
  } catch { backupList.value = []; backupTotal.value = 0 } finally { backupLoading.value = false }
}

const loadBackupQuota = async () => {
  try {
    const res = await getBackupCount({ service_id: serviceId.value, vm_id: vmId.value })
    if (res?.data?.code === 200 && res?.data?.data) {
      const d = res.data.data
      backupQuota.value = { count: d.count ?? 0, limit: d.limit ?? 0 }
    }
  } catch { /* ignore */ }
}

const handleSetBackupLimit = () => {
  ElMessageBox.prompt('请输入备份数量上限', '设置备份上限', {
    confirmButtonText: '确定', cancelButtonText: '取消',
    inputPattern: /^[1-9]\d*$/, inputErrorMessage: '请输入正整数',
    inputValue: String(backupQuota.value?.limit || 10)
  }).then(async ({ value }) => {
    try {
      const fd = new FormData()
      fd.append('service_id', serviceId.value)
      fd.append('vm_id', vmId.value)
      fd.append('limit', value)
      const res = await setBackupLimit(fd)
      if (res?.data?.code === 200) { ElMessage.success('备份上限设置成功'); loadBackupQuota() }
      else ElMessage.error(extractApiError(res?.data, '设置失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '设置失败')) }
  }).catch(() => {})
}

const handleCreateSnapshot = () => {
  Object.assign(snapshotForm, { name: '' })
  snapshotCreateVisible.value = true
}
const submitCreateSnapshot = async () => {
  if (!snapshotForm.name) { ElMessage.warning('请输入快照名称'); return }
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('vm_id', vmId.value)
    fd.append('name', snapshotForm.name)
    const res = await createSnapshot(fd)
    if (res?.data?.code === 200) { ElMessage.success('快照创建成功'); snapshotCreateVisible.value = false; loadSnapshots(); loadSnapshotQuota() }
    else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
}

const handleRestoreSnapshot = (row) => {
  ElMessageBox.confirm(`确定要恢复快照「${row.name}」吗？`, '恢复确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
    .then(async () => {
      try {
        const fd = new FormData()
        fd.append('service_id', serviceId.value)
        fd.append('snapshot_id', row.id)
        fd.append('vm_id', vmId.value)
        const res = await restoreSnapshot(fd)
        if (res?.data?.code === 200) ElMessage.success('恢复操作已提交')
        else ElMessage.error(extractApiError(res?.data, '恢复失败'))
      } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '恢复失败')) }
    }).catch(() => {})
}

const handleDeleteSnapshot = (row) => {
  ElMessageBox.confirm(`确定要删除快照「${row.name}」吗？`, '删除确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
    .then(async () => {
      try {
        const fd = new FormData()
        fd.append('service_id', serviceId.value)
        fd.append('snapshot_id', row.id)
        fd.append('vm_id', row.vm_id)
        const res = await deleteSnapshot(fd)
        if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadSnapshots(); loadSnapshotQuota() }
        else ElMessage.error(extractApiError(res?.data, '删除失败'))
      } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
    }).catch(() => {})
}

const handleSnapshotProgress = async (row) => {
  taskProgressTitle.value = '快照任务进度'
  taskProgressData.value = null
  taskProgressVisible.value = true
  taskProgressLoading.value = true
  try {
    const res = await getSnapshotProgress({ service_id: serviceId.value, task_id: String(row.task_id || row.id) })
    if (res?.data?.code === 200) taskProgressData.value = res.data.data?.data ?? res.data.data
    else ElMessage.warning('暂无进度信息')
  } catch { ElMessage.warning('获取进度失败') } finally { taskProgressLoading.value = false }
}

const handleCreateBackup = () => {
  Object.assign(backupForm, { name: '' })
  backupCreateVisible.value = true
}
const submitCreateBackup = async () => {
  if (!backupForm.name) { ElMessage.warning('请输入备份名称'); return }
  actionLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('vm_id', vmId.value)
    fd.append('name', backupForm.name)
    const res = await createBackup(fd)
    if (res?.data?.code === 200) { ElMessage.success('备份创建成功'); backupCreateVisible.value = false; loadBackups(); loadBackupQuota() }
    else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) } finally { actionLoading.value = false }
}

const handleRestoreBackup = (row) => {
  ElMessageBox.confirm(`确定要恢复备份「${row.name}」吗？`, '恢复确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
    .then(async () => {
      try {
        const fd = new FormData()
        fd.append('service_id', serviceId.value)
        fd.append('backup_id', row.id)
        fd.append('vm_id', vmId.value)
        const res = await restoreBackup(fd)
        if (res?.data?.code === 200) ElMessage.success('恢复操作已提交')
        else ElMessage.error(extractApiError(res?.data, '恢复失败'))
      } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '恢复失败')) }
    }).catch(() => {})
}

const handleDeleteBackup = (row) => {
  ElMessageBox.confirm(`确定要删除备份「${row.name}」吗？`, '删除确认', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
    .then(async () => {
      try {
        const fd = new FormData()
        fd.append('service_id', serviceId.value)
        fd.append('backup_id', row.id)
        fd.append('vm_id', row.vm_id)
        const res = await deleteBackup(fd)
        if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadBackups(); loadBackupQuota() }
        else ElMessage.error(extractApiError(res?.data, '删除失败'))
      } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
    }).catch(() => {})
}

const handleBackupProgress = async (row) => {
  taskProgressTitle.value = '备份任务进度'
  taskProgressData.value = null
  taskProgressVisible.value = true
  taskProgressLoading.value = true
  try {
    const res = await getBackupProgress({ service_id: serviceId.value, task_id: String(row.task_id || row.id) })
    if (res?.data?.code === 200) taskProgressData.value = res.data.data?.data ?? res.data.data
    else ElMessage.warning('暂无进度信息')
  } catch { ElMessage.warning('获取进度失败') } finally { taskProgressLoading.value = false }
}

const goBack = () => {
  tagsViewStore.delVisitedView(route)
  router.push({ path: '/virtualization/kvm-service-detail', query: { service_id: serviceId.value, service_name: serviceName.value } })
}

let loadedVmId = null
const initPage = () => {
  if (!vmId.value || loadedVmId === vmId.value) return
  loadedVmId = vmId.value
  metricsData.value = null
  stopPolling()
  disposeCharts()
  clearHistory()
  loadHostOptions()
  loadDetail()
  if (activeTab.value === 'monitor') startPolling()
}

watch(vmId, () => { if (isPageActive) initPage() })
watch(activeTab, (tab) => {
  if (tab === 'monitor' && detail.value) startPolling()
  else stopPolling()
  if (tab === 'network') loadVmNetworks()
  if (tab === 'volume') loadVmVolumes()
  if (tab === 'security') loadVmSecurityGroups()
  if (tab === 'snapshot') { loadSnapshots(); loadSnapshotQuota() }
  if (tab === 'backup') { loadBackups(); loadBackupQuota() }
})
onActivated(() => {
  isPageActive = true
  if (loadedVmId !== vmId.value) initPage()
  else if (activeTab.value === 'monitor') startPolling()
})
onDeactivated(() => { isPageActive = false; stopPolling() })
onBeforeUnmount(() => { isPageActive = false; stopPolling(); disposeCharts() })
onMounted(() => { isPageActive = true; initPage() })
</script>

<style scoped>
.vm-detail-page { padding: 0; background: #f5f7fa; min-height: 100vh; }
.page-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 24px; background: #fff; border-bottom: 1px solid #e8e8e8; }
.header-left { display: flex; align-items: center; gap: 0; }
.back-btn { font-size: 14px; color: #606266; }
.back-btn:hover { color: #409eff; }
.header-right { display: flex; gap: 8px; }
.main-content { padding: 20px 24px; }

/* 实例概览 */
.instance-overview { display: flex; justify-content: space-between; align-items: center; background: #fff; padding: 20px 24px; border-radius: 4px; margin-bottom: 0; border: 1px solid #e8e8e8; border-bottom: none; }
.instance-name { margin: 0; font-size: 18px; font-weight: 600; color: #1d2129; }
.instance-id { font-size: 14px; font-weight: 400; color: #86909c; margin-left: 8px; }
.overview-actions { display: flex; gap: 8px; }

/* 状态概览条 */
.status-bar { display: flex; gap: 0; background: #fff; padding: 16px 24px; border: 1px solid #e8e8e8; border-top: 1px solid #f0f0f0; border-radius: 0 0 4px 4px; margin-bottom: 16px; }
.status-item { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.status-item + .status-item { border-left: 1px solid #e8e8e8; padding-left: 24px; }
.status-label { font-size: 12px; color: #86909c; }
.status-value { font-size: 14px; color: #1d2129; display: flex; align-items: center; gap: 6px; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.dot-running { background: #00b42a; }
.dot-other { background: #86909c; }

/* 标签页 */
.detail-tabs { background: #fff; border-radius: 4px; border: 1px solid #e8e8e8; padding: 0 24px; }
:deep(.detail-tabs > .el-tabs__header) { margin-bottom: 0; }
:deep(.detail-tabs > .el-tabs__content) { padding: 0 0 24px; }

/* 区块 */
.section-block { margin-top: 24px; }
.section-title { font-size: 15px; font-weight: 600; color: #1d2129; margin: 0 0 16px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.section-header .section-title { margin-bottom: 0; padding-bottom: 0; border-bottom: none; }

/* 配置信息网格 */
.config-grid { border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; }
.config-row { display: flex; border-bottom: 1px solid #e8e8e8; }
.config-row:last-child { border-bottom: none; }
.config-cell { flex: 1; padding: 14px 16px; display: flex; flex-direction: column; gap: 6px; border-right: 1px solid #e8e8e8; }
.config-cell:last-child { border-right: none; }
.config-label { font-size: 12px; color: #86909c; line-height: 1; }
.config-value { font-size: 14px; color: #1d2129; line-height: 1.4; word-break: break-all; }
.spec-value { font-size: 13px; color: #4e5969; }
.ip-value { color: #165dff; font-weight: 500; }
.password-cell { display: flex; align-items: center; gap: 8px; }
.password-cell code { font-family: 'Cascadia Code', Consolas, monospace; font-size: 13px; color: #1d2129; }
.mono-text { font-family: 'Cascadia Code', Consolas, monospace; font-size: 13px; }

/* 监控指标 */
.metrics-summary { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 20px; }
.metric-card { flex: 1; min-width: 180px; background: #f7f8fa; border-radius: 8px; padding: 16px 20px; }
.metric-title { font-size: 13px; color: #86909c; margin-bottom: 6px; }
.metric-num { font-size: 28px; font-weight: 600; }
.charts-area { display: flex; flex-direction: column; gap: 20px; }
.chart-wrapper { background: #fafbfc; border: 1px solid #f0f0f0; border-radius: 8px; padding: 16px; }
.chart-label { margin: 0 0 8px; font-size: 14px; font-weight: 600; color: #4e5969; }
.chart-container { width: 100%; height: 220px; }

.net-speed-items { display: flex; gap: 20px; flex-wrap: wrap; }
.net-speed-item { display: flex; flex-direction: column; gap: 2px; }
.net-speed-label { font-size: 12px; color: #86909c; }
.net-speed-value { font-size: 20px; font-weight: 600; color: #1d2129; }

.vnc-result { margin-top: 12px; }
.pagination-wrapper { display: flex; justify-content: flex-end; margin-top: 16px; }
</style>
