222 lines
7.3 KiB
Vue
222 lines
7.3 KiB
Vue
<script setup>
|
||
import GlassCard from './GlassCard.vue'
|
||
import LocalIcon from './LocalIcon.vue'
|
||
|
||
defineProps({
|
||
stats: {
|
||
type: Array,
|
||
required: true,
|
||
},
|
||
records: {
|
||
type: Array,
|
||
required: true,
|
||
},
|
||
fileLimit: {
|
||
type: Number,
|
||
required: true,
|
||
},
|
||
minioCapacity: {
|
||
type: Number,
|
||
required: true,
|
||
},
|
||
})
|
||
|
||
defineEmits(['exit', 'save-config', 'update:file-limit', 'update:minio-capacity'])
|
||
|
||
function formatLimitValue(limitMb) {
|
||
const value = Number(limitMb) || 0
|
||
if (value >= 1024) {
|
||
return `${(value / 1024).toFixed(value % 1024 === 0 ? 0 : 1)} GB`
|
||
}
|
||
|
||
return `${value} MB`
|
||
}
|
||
|
||
function formatCapacityValue(capacityGb) {
|
||
const value = Number(capacityGb) || 0
|
||
if (value >= 1024) {
|
||
return `${(value / 1024).toFixed(value % 1024 === 0 ? 0 : 1)} TB`
|
||
}
|
||
|
||
return `${value} GB`
|
||
}
|
||
|
||
function getStatStyle(tone) {
|
||
if (tone === 'blue') {
|
||
return { color: 'var(--accent-blue)' }
|
||
}
|
||
|
||
if (tone === 'cyan') {
|
||
return { color: 'var(--accent-cyan)' }
|
||
}
|
||
|
||
if (tone === 'success') {
|
||
return { color: 'var(--success-green)' }
|
||
}
|
||
|
||
if (tone === 'danger') {
|
||
return { color: 'var(--danger-red)' }
|
||
}
|
||
|
||
return { color: 'var(--text-main)' }
|
||
}
|
||
|
||
function getRecordStyle(tone) {
|
||
if (tone === 'success') {
|
||
return { color: 'var(--success-green)', fontWeight: 500 }
|
||
}
|
||
|
||
if (tone === 'primary') {
|
||
return { color: 'var(--accent-blue)', fontWeight: 500 }
|
||
}
|
||
|
||
return { color: 'var(--danger-red)', fontWeight: 500 }
|
||
}
|
||
|
||
function getFluidStyle(percent) {
|
||
const value = Number(percent) || 0
|
||
return {
|
||
'--fluid-level': `${Math.max(0, Math.min(value, 100))}%`,
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="admin-panel active">
|
||
<div class="card admin-header-card">
|
||
<div class="transfer-head transfer-head-compact">
|
||
<div class="connected-to">
|
||
<h2 class="admin-title">管理控制台</h2>
|
||
<p class="admin-subtitle">AirShare Pro System Dashboard</p>
|
||
</div>
|
||
<button class="btn-small-primary" type="button" @click="$emit('exit')">退出管理</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="main-grid admin-summary-grid">
|
||
<GlassCard class="admin-stats-card" title="系统运行状态">
|
||
<div class="admin-stats-panel">
|
||
<div class="admin-stats-row">
|
||
<div
|
||
v-for="stat in stats"
|
||
:key="stat.label"
|
||
class="admin-stat-item"
|
||
:class="{ 'admin-stat-item-fluid': stat.kind === 'minio' }"
|
||
>
|
||
<template v-if="stat.kind === 'minio'">
|
||
<div class="admin-fluid-card" :style="getFluidStyle(stat.percent)">
|
||
<div class="admin-fluid-fill">
|
||
<span class="admin-fluid-wave admin-fluid-wave-a"></span>
|
||
<span class="admin-fluid-wave admin-fluid-wave-b"></span>
|
||
</div>
|
||
<div class="admin-fluid-content">
|
||
<div class="admin-fluid-icon">
|
||
<LocalIcon name="save" size="18" />
|
||
</div>
|
||
<div class="admin-fluid-copy">
|
||
<h3 :style="getStatStyle(stat.tone)">{{ stat.value }}</h3>
|
||
<p>{{ stat.label }}</p>
|
||
<small>{{ stat.detail }}</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<template v-else>
|
||
<span class="admin-stat-kicker">实时指标</span>
|
||
<h3 :style="getStatStyle(stat.tone)">
|
||
{{ stat.value }}<span v-if="stat.suffix" class="stat-suffix">{{ stat.suffix }}</span>
|
||
</h3>
|
||
<p>{{ stat.label }}</p>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</GlassCard>
|
||
|
||
<GlassCard class="admin-config-card" title="核心参数配置">
|
||
<div class="admin-config-stack">
|
||
<div class="text-input-group admin-config-row admin-config-row-field admin-config-row-last">
|
||
<div class="admin-field-meta">
|
||
<label class="admin-field-label" for="admin-file-limit">单文件大小限制</label>
|
||
<p class="admin-field-hint">单位为 MB,超过该阈值的文件会按当前后端策略处理。</p>
|
||
</div>
|
||
<div class="admin-field-control-row">
|
||
<input
|
||
id="admin-file-limit"
|
||
:value="fileLimit"
|
||
min="1"
|
||
placeholder="10240"
|
||
type="number"
|
||
@input="$emit('update:file-limit', Number($event.target.value) || 0)"
|
||
/>
|
||
<button title="保存配置" type="button" @click="$emit('save-config')">
|
||
<LocalIcon name="save" size="18" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="text-input-group admin-config-row admin-config-row-field admin-config-row-last">
|
||
<div class="admin-field-meta">
|
||
<label class="admin-field-label" for="admin-minio-capacity">MinIO 总容量</label>
|
||
<p class="admin-field-hint">单位为 GB,用于容量卡和液位比例计算。</p>
|
||
</div>
|
||
<div class="admin-field-control-row">
|
||
<input
|
||
id="admin-minio-capacity"
|
||
:value="minioCapacity"
|
||
min="1"
|
||
placeholder="120"
|
||
type="number"
|
||
@input="$emit('update:minio-capacity', Number($event.target.value) || 0)"
|
||
/>
|
||
<button title="保存配置" type="button" @click="$emit('save-config')">
|
||
<LocalIcon name="save" size="18" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="admin-config-insights">
|
||
<div class="admin-config-highlight">
|
||
<span class="admin-config-badge">ACTIVE POLICY</span>
|
||
<h3>{{ formatLimitValue(fileLimit) }}</h3>
|
||
<p>当前单文件阈值。超过该体积后,文件会按后端已设定的传输与存档策略处理。</p>
|
||
</div>
|
||
<div class="admin-config-highlight">
|
||
<span class="admin-config-badge">MINIO CAPACITY</span>
|
||
<h3>{{ formatCapacityValue(minioCapacity) }}</h3>
|
||
<p>当前 MinIO 总容量基线,用于后台容量展示与液位占比计算。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
|
||
<GlassCard class="admin-table-card" title="最近传输记录(Top 5)">
|
||
<div class="admin-table-wrapper">
|
||
<table class="admin-table">
|
||
<thead>
|
||
<tr>
|
||
<th>时间</th>
|
||
<th>发送端特征</th>
|
||
<th>传输类型</th>
|
||
<th>数据量</th>
|
||
<th>状态</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr v-for="record in records" :key="`${record.time}-${record.peer}`">
|
||
<td>{{ record.time }}</td>
|
||
<td>{{ record.peer }}</td>
|
||
<td class="admin-record-type-cell">
|
||
<span class="admin-record-type" :title="record.type">{{ record.type }}</span>
|
||
</td>
|
||
<td>{{ record.size }}</td>
|
||
<td :style="getRecordStyle(record.tone)">{{ record.status }}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</GlassCard>
|
||
</div>
|
||
</template>
|