first commit
This commit is contained in:
219
frontend/src/components/AdminPanel.vue
Normal file
219
frontend/src/components/AdminPanel.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<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>{{ record.type }}</td>
|
||||
<td>{{ record.size }}</td>
|
||||
<td :style="getRecordStyle(record.tone)">{{ record.status }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user