feat(protocol): 添加补丁管理和行为指标协议类型 feat(client): 实现补丁管理插件采集功能 feat(server): 添加补丁管理和异常检测API feat(database): 新增补丁状态和异常检测相关表 feat(web): 添加补丁管理和异常检测前端页面 fix(security): 增强输入验证和防注入保护 refactor(auth): 重构认证检查逻辑 perf(service): 优化Windows服务恢复策略 style: 统一健康评分显示样式 docs: 更新知识库文档
221 lines
6.5 KiB
Vue
221 lines
6.5 KiB
Vue
<template>
|
||
<div class="page-container">
|
||
<el-row :gutter="20">
|
||
<el-col :span="12">
|
||
<div class="csm-card">
|
||
<div class="csm-card-header">系统信息</div>
|
||
<div class="csm-card-body">
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<span class="info-label">系统版本</span>
|
||
<span class="info-value">v{{ version }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">数据库</span>
|
||
<span class="info-value">{{ dbInfo }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">在线终端</span>
|
||
<span class="info-value">{{ health.connected_clients }} 台</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="csm-card" style="margin-top: 20px">
|
||
<div class="csm-card-header">修改密码</div>
|
||
<div class="csm-card-body">
|
||
<el-form :model="pwdForm" label-width="100px" size="default">
|
||
<el-form-item label="当前密码">
|
||
<el-input v-model="pwdForm.oldPassword" type="password" show-password placeholder="输入当前密码" />
|
||
</el-form-item>
|
||
<el-form-item label="新密码">
|
||
<el-input v-model="pwdForm.newPassword" type="password" show-password placeholder="输入新密码" />
|
||
</el-form-item>
|
||
<el-form-item label="确认密码">
|
||
<el-input v-model="pwdForm.confirmPassword" type="password" show-password placeholder="再次输入新密码" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" :loading="pwdLoading" @click="changePassword">修改密码</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
|
||
<el-col :span="12">
|
||
<div class="csm-card">
|
||
<div class="csm-card-header">数据维护</div>
|
||
<div class="csm-card-body">
|
||
<div class="maintenance-item">
|
||
<div>
|
||
<div style="font-weight:500">历史数据保留</div>
|
||
<div style="font-size:12px;color:var(--csm-text-tertiary);margin-top:4px">配置数据保留策略,自动清理过期数据</div>
|
||
</div>
|
||
<el-button @click="showRetentionInfo">查看策略</el-button>
|
||
</div>
|
||
<el-divider style="margin:12px 0" />
|
||
<div class="maintenance-item">
|
||
<div>
|
||
<div style="font-weight:500">手动清理</div>
|
||
<div style="font-size:12px;color:var(--csm-text-tertiary);margin-top:4px">立即清理过期的历史数据</div>
|
||
</div>
|
||
<el-button type="warning" plain @click="manualCleanup">执行清理</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="csm-card" style="margin-top: 20px">
|
||
<div class="csm-card-header">当前用户</div>
|
||
<div class="csm-card-body">
|
||
<div class="user-card">
|
||
<div class="user-avatar-large">{{ user.username.charAt(0).toUpperCase() }}</div>
|
||
<div class="user-detail">
|
||
<div style="font-weight:600;font-size:16px">{{ user.username }}</div>
|
||
<el-tag size="small" type="warning" effect="light" style="margin-top:4px">{{ user.role }}</el-tag>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { api, getCachedUser } from '@/lib/api'
|
||
|
||
const version = ref('0.1.0')
|
||
const dbInfo = ref('SQLite (WAL mode)')
|
||
const health = reactive({ connected_clients: 0, db_size_bytes: 0 })
|
||
const user = reactive({ username: 'admin', role: 'admin' })
|
||
|
||
const pwdForm = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
||
const pwdLoading = ref(false)
|
||
|
||
onMounted(() => {
|
||
const cached = getCachedUser()
|
||
if (cached) {
|
||
user.username = cached.username
|
||
user.role = cached.role
|
||
}
|
||
|
||
api.get<any>('/health')
|
||
.then((data: any) => {
|
||
if (data.version) version.value = data.version
|
||
health.connected_clients = data.connected_clients || 0
|
||
const bytes = data.db_size_bytes || 0
|
||
dbInfo.value = `SQLite (WAL) - ${(bytes / 1024 / 1024).toFixed(2)} MB`
|
||
})
|
||
.catch((e) => { console.error('Failed to fetch health status', e) })
|
||
})
|
||
|
||
async function changePassword() {
|
||
if (!pwdForm.oldPassword) {
|
||
ElMessage.error('请输入当前密码')
|
||
return
|
||
}
|
||
if (pwdForm.newPassword.length < 6) {
|
||
ElMessage.error('新密码至少6位')
|
||
return
|
||
}
|
||
if (pwdForm.newPassword !== pwdForm.confirmPassword) {
|
||
ElMessage.error('两次输入的密码不一致')
|
||
return
|
||
}
|
||
pwdLoading.value = true
|
||
try {
|
||
await api.put('/api/auth/change-password', {
|
||
old_password: pwdForm.oldPassword,
|
||
new_password: pwdForm.newPassword,
|
||
})
|
||
ElMessage.success('密码修改成功')
|
||
pwdForm.oldPassword = ''
|
||
pwdForm.newPassword = ''
|
||
pwdForm.confirmPassword = ''
|
||
} catch (e: any) {
|
||
ElMessage.error(e.message || '密码修改失败')
|
||
} finally {
|
||
pwdLoading.value = false
|
||
}
|
||
}
|
||
|
||
function showRetentionInfo() {
|
||
ElMessage.info('数据保留策略在 config.toml 中配置')
|
||
}
|
||
|
||
function manualCleanup() {
|
||
ElMessage.warning('手动清理功能需通过服务器配置触发')
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.csm-card-header {
|
||
font-weight: 600;
|
||
font-size: 15px;
|
||
color: var(--csm-text-primary);
|
||
padding: 16px 20px;
|
||
border-bottom: 1px solid var(--csm-border-color);
|
||
}
|
||
|
||
.csm-card-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.info-grid {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 13px;
|
||
color: var(--csm-text-secondary);
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: var(--csm-text-primary);
|
||
}
|
||
|
||
.maintenance-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.user-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.user-avatar-large {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
background: var(--csm-primary);
|
||
color: #fff;
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.user-detail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
</style>
|