Files
csm/web/src/views/Settings.vue
iven 60ee38a3c2 feat: 新增补丁管理和异常检测插件及相关功能
feat(protocol): 添加补丁管理和行为指标协议类型
feat(client): 实现补丁管理插件采集功能
feat(server): 添加补丁管理和异常检测API
feat(database): 新增补丁状态和异常检测相关表
feat(web): 添加补丁管理和异常检测前端页面
fix(security): 增强输入验证和防注入保护
refactor(auth): 重构认证检查逻辑
perf(service): 优化Windows服务恢复策略
style: 统一健康评分显示样式
docs: 更新知识库文档
2026-04-11 15:59:53 +08:00

221 lines
6.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>