refactor: 移除冗余checkAuth调用,优化访客卡片样式
This commit is contained in:
@@ -184,7 +184,7 @@ import {
|
||||
PieChart, Lightning, Cpu, VideoPlay, Link, Timer, List,
|
||||
ArrowRight, Refresh, Grid, Platform, Service
|
||||
} from '@element-plus/icons-vue'
|
||||
import { fetchMachines, fetchAllServices, fetchRelationships, fetchPVEHosts, checkAuth } from '@/api'
|
||||
import { fetchMachines, fetchAllServices, fetchRelationships, fetchPVEHosts } from '@/api'
|
||||
import { getAuth } from '@/router'
|
||||
|
||||
const isAdmin = getAuth().is_admin
|
||||
@@ -240,7 +240,6 @@ const pveMachines = computed(() => {
|
||||
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
await checkAuth()
|
||||
})
|
||||
|
||||
async function load() {
|
||||
|
||||
@@ -369,7 +369,7 @@ import {
|
||||
fetchServices, createService, updateService, deleteService,
|
||||
fetchRelationships, createRelationship, updateRelationship, deleteRelationship,
|
||||
sshInfo, syncSSH, fetchMachines, fetchOfflineLogs, fetchPVEHosts,
|
||||
checkAuth, uiRefreshInterval,
|
||||
uiRefreshInterval,
|
||||
startVM as apiStartVM, stopVM as apiStopVM, fetchVMStatus,
|
||||
} from '@/api'
|
||||
import { getAuth } from '@/router'
|
||||
@@ -424,7 +424,6 @@ onMounted(async () => {
|
||||
} catch (e) {}
|
||||
await loadVMStatus()
|
||||
}
|
||||
await checkAuth()
|
||||
const interval = uiRefreshInterval || 10000
|
||||
if (interval > 0) {
|
||||
timer = setInterval(async () => {
|
||||
|
||||
@@ -30,112 +30,153 @@
|
||||
<div class="cards-grid" v-if="machines.length">
|
||||
<!-- 管理员卡片:完整信息 + 可点击 -->
|
||||
<div v-if="isAdmin" v-for="m in machines" :key="m.id" class="server-card"
|
||||
:class="[{ 'offline-card': !m.is_online }]"
|
||||
:class="[{ 'offline-card': !m.is_online }, osClass(m.os_type)]"
|
||||
@click="goDetail(m.id)">
|
||||
<div class="card-header">
|
||||
<div class="title-row">
|
||||
<div class="os-badge" :class="osClass(m.os_type)" :title="m.os_type">
|
||||
<el-icon :size="12">
|
||||
<component :is="osIcon(m.os_type)" />
|
||||
</el-icon>
|
||||
<span>{{ osShort(m.os_type) }}</span>
|
||||
</div>
|
||||
<span class="hostname">{{ m.hostname }}</span>
|
||||
<el-tag :type="m.is_online ? 'success' : 'danger'" size="small" effect="light" round class="status-tag">
|
||||
<el-icon :size="10"><component :is="m.is_online ? CircleCheck : CircleClose" /></el-icon>
|
||||
{{ m.is_online ? '在线' : '离线' }}
|
||||
</el-tag>
|
||||
<el-tag v-if="m.service_count" size="small" effect="plain" class="svc-tag" round>
|
||||
<el-icon :size="10"><Service /></el-icon> {{ m.service_count }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-ip">
|
||||
<el-icon :size="10"><Link /></el-icon> {{ m.ip }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<el-icon :size="10"><Cpu /></el-icon> {{ m.os_type }}
|
||||
</span>
|
||||
<span v-if="m.os_version" class="meta-item">{{ m.os_version }}</span>
|
||||
<span v-if="m.uptime" class="meta-uptime">
|
||||
<el-icon :size="10"><Timer /></el-icon> {{ m.uptime }}
|
||||
</span>
|
||||
<span v-if="m.pve_host_id && m.pve_vmid" class="meta-pve">
|
||||
<el-tag size="small" :type="m.pve_vm_status === 'running' ? 'success' : 'danger'" effect="light" round class="vm-tag">
|
||||
<el-icon :size="10"><component :is="m.pve_vm_status === 'running' ? VideoPlay : VideoPause" /></el-icon>
|
||||
{{ m.pve_vm_status === 'running' ? 'VM运行中' : m.pve_vm_status === 'stopped' ? 'VM已停止' : 'VM检测中' }}
|
||||
<div class="card-content">
|
||||
<div class="card-header">
|
||||
<div class="title-row">
|
||||
<div class="os-badge" :class="osClass(m.os_type)" :title="m.os_type">
|
||||
<el-icon :size="12">
|
||||
<component :is="osIcon(m.os_type)" />
|
||||
</el-icon>
|
||||
<span>{{ osShort(m.os_type) }}</span>
|
||||
</div>
|
||||
<span class="hostname">{{ m.hostname }}</span>
|
||||
<el-tag :type="m.is_online ? 'success' : 'danger'" size="small" effect="light" round class="status-tag">
|
||||
<el-icon :size="10"><component :is="m.is_online ? CircleCheck : CircleClose" /></el-icon>
|
||||
{{ m.is_online ? '在线' : '离线' }}
|
||||
</el-tag>
|
||||
</span>
|
||||
<el-tag v-if="m.service_count" size="small" effect="plain" class="svc-tag" round>
|
||||
<el-icon :size="10"><Service /></el-icon> {{ m.service_count }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-ip">
|
||||
<el-icon :size="10"><Link /></el-icon> {{ m.ip }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<el-icon :size="10"><Cpu /></el-icon> {{ m.os_type }}
|
||||
</span>
|
||||
<span v-if="m.os_version" class="meta-item">{{ m.os_version }}</span>
|
||||
<span v-if="m.uptime" class="meta-uptime">
|
||||
<el-icon :size="10"><Timer /></el-icon> {{ m.uptime }}
|
||||
</span>
|
||||
<span v-if="m.pve_host_id && m.pve_vmid" class="meta-pve">
|
||||
<el-tag size="small" :type="m.pve_vm_status === 'running' ? 'success' : 'danger'" effect="light" round class="vm-tag">
|
||||
<el-icon :size="10"><component :is="m.pve_vm_status === 'running' ? VideoPlay : VideoPause" /></el-icon>
|
||||
{{ m.pve_vm_status === 'running' ? 'VM运行中' : m.pve_vm_status === 'stopped' ? 'VM已停止' : 'VM检测中' }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="m.cpu_info || m.memory_info || m.disk_info" class="stats-row">
|
||||
<div v-if="m.cpu_info" class="stat-pill">
|
||||
<div class="pill-icon cpu">
|
||||
<el-icon :size="12"><Cpu /></el-icon>
|
||||
<div v-if="m.cpu_info || m.memory_info || m.disk_info" class="stats-row">
|
||||
<div v-if="m.cpu_info" class="stat-pill">
|
||||
<div class="pill-icon cpu">
|
||||
<el-icon :size="12"><Cpu /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">CPU</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: extractPercent(m.cpu_info) + '%' }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ extractPercent(m.cpu_info) }}%</div>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">CPU</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: extractPercent(m.cpu_info) + '%' }"></div></div>
|
||||
<div v-if="m.memory_info" class="stat-pill">
|
||||
<div class="pill-icon mem">
|
||||
<el-icon :size="12"><Collection /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">RAM</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: extractPercent(m.memory_info) + '%', background: getMemBarColor(extractPercent(m.memory_info)) }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ extractDetail(m.memory_info) }}</div>
|
||||
</div>
|
||||
<div v-if="getMainDisk(m.disk_info)" class="stat-pill">
|
||||
<div class="pill-icon disk">
|
||||
<el-icon :size="12"><Histogram /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">DISK</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: getMainDisk(m.disk_info).percent + '%', background: getDiskBarColor(getMainDisk(m.disk_info).percent) }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ getMainDisk(m.disk_info).detail }}</div>
|
||||
</div>
|
||||
<div class="pill-value">{{ extractPercent(m.cpu_info) }}%</div>
|
||||
</div>
|
||||
<div v-if="m.memory_info" class="stat-pill">
|
||||
<div class="pill-icon mem">
|
||||
<el-icon :size="12"><Collection /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">RAM</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: extractPercent(m.memory_info) + '%', background: getMemBarColor(extractPercent(m.memory_info)) }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ extractDetail(m.memory_info) }}</div>
|
||||
</div>
|
||||
<div v-if="getMainDisk(m.disk_info)" class="stat-pill">
|
||||
<div class="pill-icon disk">
|
||||
<el-icon :size="12"><Histogram /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">DISK</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: getMainDisk(m.disk_info).percent + '%', background: getDiskBarColor(getMainDisk(m.disk_info).percent) }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ getMainDisk(m.disk_info).detail }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="m.listen_ports" class="ports-row">
|
||||
<el-tag v-for="p in m.listen_ports.split(',').slice(0,6)" :key="p" size="small" effect="plain" round class="port-tag">
|
||||
<el-icon :size="9"><Connection /></el-icon> {{ p.trim() }}
|
||||
</el-tag>
|
||||
<span v-if="m.listen_ports.split(',').length > 6" class="more-ports">+{{ m.listen_ports.split(',').length - 6 }}</span>
|
||||
</div>
|
||||
<div v-if="m.listen_ports" class="ports-row">
|
||||
<el-tag v-for="p in m.listen_ports.split(',').slice(0,6)" :key="p" size="small" effect="plain" round class="port-tag">
|
||||
<el-icon :size="9"><Connection /></el-icon> {{ p.trim() }}
|
||||
</el-tag>
|
||||
<span v-if="m.listen_ports.split(',').length > 6" class="more-ports">+{{ m.listen_ports.split(',').length - 6 }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="m.ssh_synced_at" class="sync-time">
|
||||
<el-icon :size="10"><Clock /></el-icon>
|
||||
同步于 {{ formatTime(m.ssh_synced_at) }}
|
||||
<div v-if="m.ssh_synced_at" class="sync-time">
|
||||
<el-icon :size="10"><Clock /></el-icon>
|
||||
同步于 {{ formatTime(m.ssh_synced_at) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未登录用户卡片:仅主机名 + 状态 + OS,不可点击 -->
|
||||
<div v-if="!isAdmin" v-for="m in machines" :key="m.id" class="server-card guest-card"
|
||||
:class="[{ 'offline-card': !m.is_online }]">
|
||||
<div class="card-header">
|
||||
<div class="title-row">
|
||||
<div class="os-badge" :class="osClass(m.os_type)" :title="m.os_type">
|
||||
<el-icon :size="12">
|
||||
<component :is="osIcon(m.os_type)" />
|
||||
</el-icon>
|
||||
<span>{{ osShort(m.os_type) }}</span>
|
||||
<!-- 未登录用户卡片:机器名 + 状态 + OS + IP,不可点击 -->
|
||||
<div v-if="!isAdmin" v-for="m in machines" :key="m.id" class="server-card public-card"
|
||||
:class="[{ 'offline-card': !m.is_online }, osClass(m.os_type)]">
|
||||
<div class="card-content">
|
||||
<div class="card-header">
|
||||
<div class="title-row">
|
||||
<div class="os-badge" :class="osClass(m.os_type)" :title="m.os_type">
|
||||
<el-icon :size="12">
|
||||
<component :is="osIcon(m.os_type)" />
|
||||
</el-icon>
|
||||
<span>{{ osShort(m.os_type) }}</span>
|
||||
</div>
|
||||
<span class="hostname">{{ m.hostname }}</span>
|
||||
<el-tag :type="m.is_online ? 'success' : 'danger'" size="small" effect="light" round class="status-tag">
|
||||
<el-icon :size="10"><component :is="m.is_online ? CircleCheck : CircleClose" /></el-icon>
|
||||
{{ m.is_online ? '在线' : '离线' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-ip">
|
||||
<el-icon :size="10"><Link /></el-icon> {{ m.ip }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<el-icon :size="10"><Cpu /></el-icon> {{ m.os_type }} {{ m.os_version || '' }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="hostname">{{ m.hostname }}</span>
|
||||
<el-tag :type="m.is_online ? 'success' : 'danger'" size="small" effect="light" round class="status-tag">
|
||||
<el-icon :size="10"><component :is="m.is_online ? CircleCheck : CircleClose" /></el-icon>
|
||||
{{ m.is_online ? '在线' : '离线' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-item">
|
||||
<el-icon :size="10"><Cpu /></el-icon> {{ m.os_type }}
|
||||
</span>
|
||||
|
||||
<!-- 未登录用户也展示资源条(只读) -->
|
||||
<div v-if="m.cpu_info || m.memory_info || m.disk_info" class="stats-row">
|
||||
<div v-if="m.cpu_info" class="stat-pill">
|
||||
<div class="pill-icon cpu">
|
||||
<el-icon :size="12"><Cpu /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">CPU</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: extractPercent(m.cpu_info) + '%' }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ extractPercent(m.cpu_info) }}%</div>
|
||||
</div>
|
||||
<div v-if="m.memory_info" class="stat-pill">
|
||||
<div class="pill-icon mem">
|
||||
<el-icon :size="12"><Collection /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">RAM</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: extractPercent(m.memory_info) + '%', background: getMemBarColor(extractPercent(m.memory_info)) }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ extractDetail(m.memory_info) }}</div>
|
||||
</div>
|
||||
<div v-if="getMainDisk(m.disk_info)" class="stat-pill">
|
||||
<div class="pill-icon disk">
|
||||
<el-icon :size="12"><Histogram /></el-icon>
|
||||
</div>
|
||||
<div class="pill-body">
|
||||
<div class="pill-label">DISK</div>
|
||||
<div class="pill-bar"><div class="pill-fill" :style="{ width: getMainDisk(m.disk_info).percent + '%', background: getDiskBarColor(getMainDisk(m.disk_info).percent) }"></div></div>
|
||||
</div>
|
||||
<div class="pill-value">{{ getMainDisk(m.disk_info).detail }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -238,7 +279,7 @@ import {
|
||||
Collection, Histogram, Connection, Clock, VideoPlay, VideoPause, Lock, User, Key,
|
||||
EditPen, Grid, InfoFilled, Warning
|
||||
} from '@element-plus/icons-vue'
|
||||
import { fetchMachines, createMachine, updateMachine, checkAuth, uiRefreshInterval, exportData, importData, fetchPVEHosts, fetchVMStatus } from '@/api'
|
||||
import { fetchMachines, createMachine, updateMachine, uiRefreshInterval, exportData, importData, fetchPVEHosts, fetchVMStatus } from '@/api'
|
||||
import { getAuth } from '@/router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
@@ -255,7 +296,6 @@ let timer = null
|
||||
|
||||
onMounted(async () => {
|
||||
await load()
|
||||
await checkAuth()
|
||||
if (isAdmin) {
|
||||
try {
|
||||
const res = await fetchPVEHosts()
|
||||
@@ -431,7 +471,7 @@ function formatTime(t) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 24px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.search-wrap {
|
||||
@@ -455,39 +495,82 @@ function formatTime(t) {
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
gap: 20px;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.cards-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* ─── Neon Tech Card ─── */
|
||||
.server-card {
|
||||
position: relative;
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--radius);
|
||||
padding: 18px;
|
||||
box-shadow: var(--shadow-card);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--border);
|
||||
cursor: pointer;
|
||||
transition: all .15s ease;
|
||||
transition: all .3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.server-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
border-color: var(--border-strong);
|
||||
|
||||
/* OS 顶部色条 — 使用 border-top */
|
||||
.server-card.os-linux {
|
||||
border-top: 3px solid #58a6ff;
|
||||
}
|
||||
.server-card.guest-card {
|
||||
cursor: default;
|
||||
.server-card.os-windows {
|
||||
border-top: 3px solid #3fb950;
|
||||
}
|
||||
.server-card.guest-card:hover {
|
||||
transform: none;
|
||||
box-shadow: var(--shadow-card);
|
||||
border-color: var(--border);
|
||||
.server-card.os-macos {
|
||||
border-top: 3px solid #a371f7;
|
||||
}
|
||||
.server-card.os-other {
|
||||
border-top: 3px solid #8b949e;
|
||||
}
|
||||
.server-card.offline-card {
|
||||
opacity: 0.7;
|
||||
border-left-color: var(--danger);
|
||||
border-top: 3px solid #f85149;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.server-card:hover.os-linux {
|
||||
border-top: 4px solid #58a6ff;
|
||||
box-shadow: 0 0 20px rgba(88, 166, 255, 0.15), 0 0 60px rgba(88, 166, 255, 0.08), var(--shadow-lg);
|
||||
}
|
||||
.server-card:hover.os-windows {
|
||||
border-top: 4px solid #3fb950;
|
||||
box-shadow: 0 0 20px rgba(63, 185, 80, 0.15), 0 0 60px rgba(63, 185, 80, 0.08), var(--shadow-lg);
|
||||
}
|
||||
.server-card:hover.os-macos {
|
||||
border-top: 4px solid #a371f7;
|
||||
box-shadow: 0 0 20px rgba(163, 113, 247, 0.15), 0 0 60px rgba(163, 113, 247, 0.08), var(--shadow-lg);
|
||||
}
|
||||
.server-card:hover.os-other {
|
||||
border-top: 4px solid #8b949e;
|
||||
}
|
||||
.server-card.offline-card:hover {
|
||||
border-top: 4px solid #f85149;
|
||||
box-shadow: 0 0 20px rgba(248, 81, 73, 0.15), 0 0 60px rgba(248, 81, 73, 0.08), var(--shadow-lg);
|
||||
}
|
||||
|
||||
.server-card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: var(--card-bg);
|
||||
border-radius: 0 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px);
|
||||
padding: 16px 18px 18px;
|
||||
}
|
||||
|
||||
.server-card.public-card {
|
||||
cursor: default;
|
||||
}
|
||||
.server-card.public-card:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.server-card.offline-card .hostname {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
@@ -495,7 +578,7 @@ function formatTime(t) {
|
||||
.card-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
}
|
||||
.title-row {
|
||||
display: flex;
|
||||
@@ -578,7 +661,7 @@ function formatTime(t) {
|
||||
.vm-tag { font-size: 11px; height: 20px; padding: 0 8px; display: inline-flex; align-items: center; gap: 4px; }
|
||||
|
||||
.stats-row {
|
||||
margin-top: 14px;
|
||||
margin-top: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
|
||||
Reference in New Issue
Block a user