feat: scaffold disk monitor page state
This commit is contained in:
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<div class="policy-form-card">
|
||||||
|
<div class="policy-header">
|
||||||
|
<div>
|
||||||
|
<h3 class="policy-title">全局策略</h3>
|
||||||
|
<p class="policy-description">配置监控总开关、启动监控与每日统一时间。</p>
|
||||||
|
</div>
|
||||||
|
<div class="policy-actions">
|
||||||
|
<el-button :loading="runLoading" :disabled="disabled" @click="emit('run')">立即执行监控</el-button>
|
||||||
|
<el-button type="primary" :loading="saveLoading" :disabled="disabled" @click="emit('save')">保存配置</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
class="policy-alert"
|
||||||
|
title="通知规则"
|
||||||
|
description="预警按状态变化通知,告警每次命中都通知"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-form label-width="130px" class="policy-form">
|
||||||
|
<el-form-item label="启用监控">
|
||||||
|
<el-switch
|
||||||
|
:model-value="modelValue.monitorEnabled"
|
||||||
|
:disabled="disabled"
|
||||||
|
@update:model-value="handleMonitorEnabledChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="启动即监控">
|
||||||
|
<el-switch
|
||||||
|
:model-value="modelValue.runOnAppStart"
|
||||||
|
:disabled="disabled"
|
||||||
|
@update:model-value="handleRunOnAppStartChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="每日执行时间">
|
||||||
|
<el-time-picker
|
||||||
|
:model-value="modelValue.dailyRunTime"
|
||||||
|
:disabled="disabled"
|
||||||
|
format="HH:mm:ss"
|
||||||
|
value-format="HH:mm:ss"
|
||||||
|
placeholder="选择时间"
|
||||||
|
@update:model-value="value => updatePolicy('dailyRunTime', typeof value === 'string' ? value : '')"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { DiskMonitor } from '@/api/system/diskMonitor/interface'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'DiskMonitorPolicyForm'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: DiskMonitor.PolicyItem
|
||||||
|
disabled?: boolean
|
||||||
|
saveLoading?: boolean
|
||||||
|
runLoading?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
saveLoading: false,
|
||||||
|
runLoading: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:modelValue': [value: DiskMonitor.PolicyItem]
|
||||||
|
save: []
|
||||||
|
run: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const handleMonitorEnabledChange = (value: string | number | boolean) => {
|
||||||
|
updatePolicy('monitorEnabled', Boolean(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRunOnAppStartChange = (value: string | number | boolean) => {
|
||||||
|
updatePolicy('runOnAppStart', Boolean(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePolicy = <K extends keyof DiskMonitor.PolicyItem>(key: K, value: DiskMonitor.PolicyItem[K]) => {
|
||||||
|
emit('update:modelValue', {
|
||||||
|
...props.modelValue,
|
||||||
|
[key]: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.policy-form-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-description {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-alert {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-form :deep(.el-form-item) {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.policy-header {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy-actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="disk-monitor-summary">
|
||||||
|
<div class="summary-card">
|
||||||
|
<div class="summary-label">监控状态</div>
|
||||||
|
<div class="summary-value">{{ policy.monitorEnabled ? '已启用' : '已停用' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<div class="summary-label">启动即监控</div>
|
||||||
|
<div class="summary-value">{{ policy.runOnAppStart ? '是' : '否' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<div class="summary-label">每日执行时间</div>
|
||||||
|
<div class="summary-value">{{ policy.dailyRunTime || '--' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<div class="summary-label">监控盘符数量</div>
|
||||||
|
<div class="summary-value">{{ targets.length }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<div class="summary-label">当前告警盘符</div>
|
||||||
|
<div class="summary-value">{{ alarmCount }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="summary-card">
|
||||||
|
<div class="summary-label">最近执行状态</div>
|
||||||
|
<div class="summary-value">{{ latestJob?.jobStatus || '--' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type { DiskMonitor } from '@/api/system/diskMonitor/interface'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'DiskMonitorSummary'
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
policy: DiskMonitor.PolicyItem
|
||||||
|
targets: DiskMonitor.TargetItem[]
|
||||||
|
latestJob: DiskMonitor.JobListItem | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const alarmCount = computed(() => props.targets.filter(item => item.lastStatus === 'ALARM').length)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.disk-monitor-summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-value {
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.disk-monitor-summary {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.disk-monitor-summary {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,168 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>磁盘监控页面占位</div>
|
<div v-loading="loading.init" class="table-box disk-monitor-page">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-main">
|
||||||
|
<el-button class="back-button" link type="primary" @click="handleBack">返回系统监控</el-button>
|
||||||
|
<h2 class="page-title">磁盘监控</h2>
|
||||||
|
<p class="page-description">查看当前策略状态并维护全局执行配置。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DiskMonitorSummary :policy="policyForm" :targets="targetList" :latest-job="latestJob" />
|
||||||
|
|
||||||
|
<DiskMonitorPolicyForm
|
||||||
|
v-model="policyForm"
|
||||||
|
:disabled="loading.init"
|
||||||
|
:save-loading="loading.save"
|
||||||
|
:run-loading="loading.run"
|
||||||
|
@save="handleSave"
|
||||||
|
@run="handleRun"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import {
|
||||||
|
getDiskMonitorJobList,
|
||||||
|
getDiskMonitorPolicyDetail,
|
||||||
|
runDiskMonitorJob,
|
||||||
|
saveDiskMonitorPolicy
|
||||||
|
} from '@/api/system/diskMonitor'
|
||||||
|
import type { DiskMonitor } from '@/api/system/diskMonitor/interface'
|
||||||
|
import DiskMonitorPolicyForm from './components/DiskMonitorPolicyForm.vue'
|
||||||
|
import DiskMonitorSummary from './components/DiskMonitorSummary.vue'
|
||||||
|
import { createDefaultPolicy, validatePolicy } from './utils/form'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'DiskMonitorPage'
|
name: 'DiskMonitorPage'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const policyForm = ref<DiskMonitor.PolicyItem>(createDefaultPolicy())
|
||||||
|
const targetList = ref<DiskMonitor.TargetItem[]>([])
|
||||||
|
const latestJob = ref<DiskMonitor.JobListItem | null>(null)
|
||||||
|
const loading = reactive({
|
||||||
|
init: false,
|
||||||
|
save: false,
|
||||||
|
run: false,
|
||||||
|
jobs: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleBack = async () => {
|
||||||
|
await router.push('/systemMonitor')
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPolicyDetail = async () => {
|
||||||
|
const response = await getDiskMonitorPolicyDetail()
|
||||||
|
const detail = response.data
|
||||||
|
|
||||||
|
if (!detail) return
|
||||||
|
|
||||||
|
policyForm.value = detail.policy || createDefaultPolicy()
|
||||||
|
targetList.value = detail.targets || []
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadLatestJob = async () => {
|
||||||
|
loading.jobs = true
|
||||||
|
try {
|
||||||
|
const response = await getDiskMonitorJobList({
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 1
|
||||||
|
})
|
||||||
|
latestJob.value = response.data?.records?.[0] || null
|
||||||
|
} finally {
|
||||||
|
loading.jobs = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPageData = async () => {
|
||||||
|
loading.init = true
|
||||||
|
try {
|
||||||
|
// 页面初始化时并行拉取策略和最近任务,保证摘要卡片和表单状态一致。
|
||||||
|
await Promise.all([loadPolicyDetail(), loadLatestJob()])
|
||||||
|
} finally {
|
||||||
|
loading.init = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
const errorMessage = validatePolicy(policyForm.value)
|
||||||
|
if (errorMessage) {
|
||||||
|
ElMessage.warning(errorMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.save = true
|
||||||
|
try {
|
||||||
|
await saveDiskMonitorPolicy({
|
||||||
|
policy: policyForm.value,
|
||||||
|
targets: targetList.value
|
||||||
|
})
|
||||||
|
ElMessage.success('配置保存成功')
|
||||||
|
// 保存完成后重新拉取数据,避免本地状态与服务端策略偏差。
|
||||||
|
await loadPageData()
|
||||||
|
} finally {
|
||||||
|
loading.save = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRun = async () => {
|
||||||
|
loading.run = true
|
||||||
|
try {
|
||||||
|
await runDiskMonitorJob({
|
||||||
|
jobSource: 'MANUAL'
|
||||||
|
})
|
||||||
|
ElMessage.success('监控任务已启动')
|
||||||
|
// 手动触发任务后刷新摘要,展示最新任务状态。
|
||||||
|
await loadPageData()
|
||||||
|
} finally {
|
||||||
|
loading.run = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadPageData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.disk-monitor-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
align-self: flex-start;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-description {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
54
frontend/src/views/systemMonitor/diskMonitor/utils/form.ts
Normal file
54
frontend/src/views/systemMonitor/diskMonitor/utils/form.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { DiskMonitor } from '@/api/system/diskMonitor/interface'
|
||||||
|
|
||||||
|
export const createDefaultPolicy = (): DiskMonitor.PolicyItem => ({
|
||||||
|
policyName: '默认磁盘监控策略',
|
||||||
|
monitorEnabled: true,
|
||||||
|
runOnAppStart: true,
|
||||||
|
dailyRunTime: '08:30:00',
|
||||||
|
warningNotifyMode: 'STATUS_CHANGE',
|
||||||
|
alarmNotifyMode: 'EVERY_TIME',
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createEmptyPathTarget = (): DiskMonitor.NotifyPathTarget => ({
|
||||||
|
path: '',
|
||||||
|
name: '',
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createEmptyHttpTarget = (): DiskMonitor.NotifyHttpTarget => ({
|
||||||
|
url: '',
|
||||||
|
name: '',
|
||||||
|
method: 'POST',
|
||||||
|
timeoutMs: 5000,
|
||||||
|
enabled: true
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createEmptyTarget = (): DiskMonitor.TargetItem => ({
|
||||||
|
driveLetter: '',
|
||||||
|
monitorEnabled: true,
|
||||||
|
warningUsagePercent: 80,
|
||||||
|
alarmUsagePercent: 90,
|
||||||
|
notifyPathEnabled: false,
|
||||||
|
notifyPathList: [],
|
||||||
|
notifyHttpEnabled: false,
|
||||||
|
notifyHttpList: [],
|
||||||
|
lastStatus: 'UNKNOWN',
|
||||||
|
lastScanTime: null,
|
||||||
|
lastUsedPercent: null,
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
export const validatePolicy = (policy: DiskMonitor.PolicyItem) => {
|
||||||
|
if (!policy.dailyRunTime) return '每日统一执行时间不能为空'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateTarget = (target: DiskMonitor.TargetItem, exists: string[]) => {
|
||||||
|
if (!target.driveLetter) return '盘符不能为空'
|
||||||
|
if (exists.includes(target.driveLetter)) return '盘符不能重复'
|
||||||
|
if (target.warningUsagePercent < 1 || target.warningUsagePercent > 100) return '预警使用率必须在 1-100 之间'
|
||||||
|
if (target.alarmUsagePercent < 1 || target.alarmUsagePercent > 100) return '告警使用率必须在 1-100 之间'
|
||||||
|
if (target.alarmUsagePercent < target.warningUsagePercent) return '告警使用率不能小于预警使用率'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user