|
@@ -1,6 +1,34 @@
|
|
|
<template>
|
|
|
<!-- monitor.vue -->
|
|
|
<div class="monitor-container">
|
|
|
+ <!-- 告警信息显示 -->
|
|
|
+ <div class="alert-notifications">
|
|
|
+ <transition-group name="el-fade-in">
|
|
|
+ <el-alert v-for="alert in triggeredAlerts" :key="alert.name" type="error" effect="dark" :closable="true"
|
|
|
+ @close="handleCloseAlert(alert)" class="alert-item">
|
|
|
+ <div class="alert-content">
|
|
|
+ <div class="alert-metric">
|
|
|
+ <el-icon style="height: 100%;">
|
|
|
+ <warning />
|
|
|
+ </el-icon>
|
|
|
+ 触发告警:{{ alert.name }}
|
|
|
+ </div>
|
|
|
+ <div class="alert-metric">
|
|
|
+ {{ metricMap[alert.metric] }}超出阈值
|
|
|
+ </div>
|
|
|
+ <div class="alert-details">
|
|
|
+ <span class="alert-threshold">
|
|
|
+ 阈值:{{ alert.threshold }}{{ alert.metric === 'cpu' ? '%' : 'MB' }}
|
|
|
+ </span>
|
|
|
+ <span class="alert-handle">
|
|
|
+ 方案:{{ handleMap[alert.handle] }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-alert>
|
|
|
+ </transition-group>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 系统状态概览 -->
|
|
|
<div class="system-monitor">
|
|
|
<el-row :gutter="16" class="chart-grid">
|
|
@@ -81,36 +109,79 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
- <el-table :data="processList" v-loading="loading">
|
|
|
- <el-table-column prop="name" label="进程名称" min-width="150" />
|
|
|
- <el-table-column label="CPU占用">
|
|
|
+ <el-table ref="treeTableRef" :data="groupedProcessList" row-key="missionId"
|
|
|
+ :tree-props="{ children: 'processes' }" @row-click="expandMissionRow">
|
|
|
+ <el-table-column label="" min-width="30">
|
|
|
<template #default="{ row }">
|
|
|
- {{ row.cpu.toFixed(1) }}%
|
|
|
+ <span v-if="row.isGroup" class="mission-info">
|
|
|
+ 任务进程:
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="内存占用">
|
|
|
+ <!-- 创建者信息 -->
|
|
|
+ <el-table-column label="创建者" width="160">
|
|
|
<template #default="{ row }">
|
|
|
- {{ (row.memory / 1024).toFixed(1) }} MB
|
|
|
+ <span v-if="row.isGroup" class="mission-info">
|
|
|
+ {{ row.creator }}
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column prop="runtime" label="运行时间">
|
|
|
+ <!-- 任务信息 -->
|
|
|
+ <el-table-column label="任务ID" width="160">
|
|
|
<template #default="{ row }">
|
|
|
- {{ formatRuntime(row.startTime) }}
|
|
|
+ <span v-if="row.isGroup" class="mission-info">
|
|
|
+ {{ row.missionId }}
|
|
|
+ </span>
|
|
|
+ <span v-else class="mission-info">
|
|
|
+ 下属子进程:
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- 规划信息 -->
|
|
|
+ <el-table-column label="规划ID" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span v-if="row.isGroup" class="total-cpu" />
|
|
|
+ <span v-else>{{ row.planId }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- CPU占用 -->
|
|
|
+ <el-table-column label="CPU占用" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <span v-if="row.isGroup" class="total-cpu">
|
|
|
+ {{ row.totalCPU.toFixed(1) }}%
|
|
|
+ </span>
|
|
|
+ <span v-else>{{ row.cpu.toFixed(1) }}%</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="磁盘IO">
|
|
|
+
|
|
|
+ <!-- 内存占用 -->
|
|
|
+ <el-table-column label="内存占用" width="160">
|
|
|
<template #default="{ row }">
|
|
|
- ↑{{ row.io.read }} ↓{{ row.io.write }} KB/s
|
|
|
+ <span v-if="row.isGroup" class="total-mem">
|
|
|
+ {{ (row.totalMemory / 1024).toFixed(1) }} GB
|
|
|
+ </span>
|
|
|
+ <span v-else>{{ (row.memory / 1024).toFixed(1) }} GB</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- 运行时间 -->
|
|
|
+ <el-table-column label="运行时间" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ formatRuntime(row.startTime) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="操作" width="150">
|
|
|
+ <!-- 操作 -->
|
|
|
+ <el-table-column label="操作" width="300">
|
|
|
<template #default="{ row }">
|
|
|
- <el-button size="small" @click="pauseProcess(row.pid)">
|
|
|
- 暂停
|
|
|
- </el-button>
|
|
|
- <el-button size="small" type="danger" @click="killProcess(row.pid)">
|
|
|
- 终止
|
|
|
- </el-button>
|
|
|
+ <div v-if="row.processes" class="group-actions">
|
|
|
+ <el-button size="small" @click.stop="pauseMission(row.missionId)"
|
|
|
+ :disabled="row.status === 'paused'">
|
|
|
+ {{ row.status === 'paused' ? '已暂停' : '暂停任务' }}
|
|
|
+ </el-button>
|
|
|
+ <el-button size="small" type="danger"
|
|
|
+ @click.stop="stopMission(row.missionId)">
|
|
|
+ 终止任务
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
@@ -136,7 +207,8 @@
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="80">
|
|
|
<template #default="{ row }">
|
|
|
- <el-button type="danger" size="small" @click="deleteAlert(row.id)">删除</el-button>
|
|
|
+ <el-button type="danger" size="small"
|
|
|
+ @click.stop="deleteAlert(row.name)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
@@ -146,7 +218,7 @@
|
|
|
</div>
|
|
|
<!-- 告警创建模态框 -->
|
|
|
<el-dialog v-model="alertDialogVisible" title="创建告警规则">
|
|
|
- <el-form :model="newAlert" :rules="alertRules" ref="alertForm">
|
|
|
+ <el-form :model="newAlert" :rules="alertRules" ref="createAlertForm">
|
|
|
<el-form-item label="规则名称" prop="name">
|
|
|
<el-input v-model="newAlert.name" />
|
|
|
</el-form-item>
|
|
@@ -170,16 +242,17 @@
|
|
|
<el-input v-model.number="newAlert.threshold">
|
|
|
<template #append>
|
|
|
<span v-if="newAlert.metric === 'cpu'">%</span>
|
|
|
- <span v-else>GB</span>
|
|
|
+ <span v-else>MB</span>
|
|
|
</template>
|
|
|
</el-input>
|
|
|
</el-form-item>
|
|
|
|
|
|
- <el-form-item label="处理方案" prop="action">
|
|
|
- <el-select v-model="newAlert.action">
|
|
|
- <el-option label="终止高占用进程" value="1" />
|
|
|
- <el-option label="终止最新进程" value="2" />
|
|
|
- <el-option label="不做处理" value="3" />
|
|
|
+ <el-form-item label="处理方案" prop="handle">
|
|
|
+ <el-select v-model="newAlert.handle">
|
|
|
+ <el-option label="不做处理" value="noAction" />
|
|
|
+ <el-option label="关闭最新进程" value="closeLatest" />
|
|
|
+ <el-option label="关闭CPU占用最高进程" value="closeHighestCpu" />
|
|
|
+ <el-option label="关闭MEM占用最高进程" value="closeHighestMem" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
@@ -200,10 +273,10 @@
|
|
|
{{ metricMap[selectedAlert?.metric] }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="阈值">
|
|
|
- {{ selectedAlert?.threshold }}{{ selectedAlert?.metric === 'cpu' ? '%' : 'GB' }}
|
|
|
+ {{ selectedAlert?.threshold }}{{ selectedAlert?.metric === 'cpu' ? '%' : 'MB' }}
|
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="处理方案">
|
|
|
- {{ actionMap[selectedAlert?.action] }}
|
|
|
+ {{ handleMap[selectedAlert?.handle] }}
|
|
|
</el-descriptions-item>
|
|
|
<!-- <el-descriptions-item label="创建时间">
|
|
|
{{ new Date(selectedAlert?.createTime).toLocaleString() }}
|
|
@@ -215,9 +288,11 @@
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, computed, onMounted, onBeforeUnmount, reactive } from 'vue'
|
|
|
+import { CaretRight, CaretBottom, Warning } from '@element-plus/icons-vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
import LineChart from '@/views/components/monitor/LineChart.vue'
|
|
|
import PieChart from '@/views/components/monitor/PieChart.vue'
|
|
|
+import { postData, getData } from '@/api/axios'
|
|
|
|
|
|
// 模拟系统状态数据
|
|
|
const systemStatus = ref({
|
|
@@ -232,7 +307,79 @@ const cpuHistory = ref([])
|
|
|
const memoryHistory = ref([])
|
|
|
const processHistory = ref([])
|
|
|
const processList = ref([])
|
|
|
-const loading = ref(false)
|
|
|
+// 任务、进程表格的引用
|
|
|
+const treeTableRef = ref(null)
|
|
|
+
|
|
|
+// 为方便以任务分组显示,将process分组组织
|
|
|
+const groups = new Map()
|
|
|
+const groupedProcessList = computed(() => {
|
|
|
+ // 初始化每个mission的CPU和MEM总和
|
|
|
+ groups.forEach(group => {
|
|
|
+ group.totalCPU = 0
|
|
|
+ group.totalMemory = 0
|
|
|
+ group.processes = []
|
|
|
+ })
|
|
|
+ const currentMissionIds = new Set()
|
|
|
+ // 处理原始数据分组
|
|
|
+ processList.value.forEach(process => {
|
|
|
+ currentMissionIds.add(process.missionId)
|
|
|
+ if (!groups.has(process.missionId)) {
|
|
|
+ groups.set(process.missionId, {
|
|
|
+ missionId: process.missionId,
|
|
|
+ creator: process.user,
|
|
|
+ totalCPU: 0,
|
|
|
+ totalMemory: 0,
|
|
|
+ processes: [],
|
|
|
+ isGroup: true,
|
|
|
+ status: 'running',
|
|
|
+ startTime: process.startTime,
|
|
|
+ expanded: false,
|
|
|
+ remain: 5,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const group = groups.get(process.missionId)
|
|
|
+ group.totalCPU += process.cpu
|
|
|
+ group.totalMemory += process.memory
|
|
|
+ group.processes.push({
|
|
|
+ ...process,
|
|
|
+ isGroup: false
|
|
|
+ })
|
|
|
+
|
|
|
+ // 保留最早启动时间
|
|
|
+ if (new Date(process.startTime) < new Date(group.startTime)) {
|
|
|
+ group.startTime = process.startTime
|
|
|
+ }
|
|
|
+ })
|
|
|
+ Object.keys(groups).forEach(missionId => {
|
|
|
+ if (!currentMissionIds.has(missionId)) {
|
|
|
+ groups[missionId].remain -= 1
|
|
|
+ if (groups[missionId].remain <= 0) {
|
|
|
+ delete groups[missionId]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ console.log(Array.from(groups.values()))
|
|
|
+ return Array.from(groups.values())
|
|
|
+})
|
|
|
+
|
|
|
+const toggleRowExpansion = (row) => {
|
|
|
+ if (!treeTableRef.value) return
|
|
|
+
|
|
|
+ // 通过 Element Plus 的实例方法操作
|
|
|
+ treeTableRef.value.toggleRowExpansion(row)
|
|
|
+}
|
|
|
+
|
|
|
+const expandMissionRow = (row, column, event) => {
|
|
|
+ if (row.isGroup) {
|
|
|
+ // 阻止事件冒泡到父元素
|
|
|
+ event.stopPropagation()
|
|
|
+ row.expanded = !row.expanded
|
|
|
+ toggleRowExpansion(row)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const loading = ref(true)
|
|
|
const activeProcessCount = computed(() => systemStatus.value.processes)
|
|
|
const systemStats = computed(() => [
|
|
|
{ title: 'CPU', value: `${systemStatus.value.cpu}%` },
|
|
@@ -244,13 +391,23 @@ const systemStats = computed(() => [
|
|
|
// 告警相关数据
|
|
|
const alerts = ref([])
|
|
|
const alertDialogVisible = ref(false)
|
|
|
-const newAlert = reactive({
|
|
|
+const initAlert = {
|
|
|
name: '',
|
|
|
level: 'system',
|
|
|
metric: 'cpu',
|
|
|
threshold: null,
|
|
|
- action: '3'
|
|
|
-})
|
|
|
+ handle: 'noAction'
|
|
|
+}
|
|
|
+// 当前触发的告警列表
|
|
|
+const triggeredAlerts = ref([])
|
|
|
+// 关闭告警提示框
|
|
|
+const handleCloseAlert = (alert) => {
|
|
|
+ triggeredAlerts.value = triggeredAlerts.value.filter(a => a.name !== alert.name)
|
|
|
+}
|
|
|
+const newAlert = ref({ ...initAlert })
|
|
|
+//创建告警的模态框
|
|
|
+const createAlertForm = ref(null)
|
|
|
+
|
|
|
const detailDialogVisible = ref(false)
|
|
|
const selectedAlert = ref(null)
|
|
|
const metricMap = {
|
|
@@ -259,10 +416,11 @@ const metricMap = {
|
|
|
disk: '磁盘空间'
|
|
|
}
|
|
|
|
|
|
-const actionMap = {
|
|
|
- '1': '终止高占用进程',
|
|
|
- '2': '终止最新进程',
|
|
|
- '3': '不做任何处理'
|
|
|
+const handleMap = {
|
|
|
+ 'noAction': '不做处理',
|
|
|
+ 'closeLatest': '关闭最新进程',
|
|
|
+ 'closeHighestCpu': '关闭CPU占用最高进程',
|
|
|
+ 'closeHighestMem': '关闭MEM占用最高进程',
|
|
|
}
|
|
|
|
|
|
|
|
@@ -275,7 +433,8 @@ const alertRules = {
|
|
|
{ required: true, message: '请输入阈值', trigger: 'blur' },
|
|
|
{
|
|
|
validator: (_, value, callback) => {
|
|
|
- if (newAlert.metric === 'cpu') {
|
|
|
+ if (newAlert.value.metric === 'cpu') {
|
|
|
+ console.log(value)
|
|
|
return value >= 0 && value <= 100 ? callback() : callback('CPU阈值需在0-100之间')
|
|
|
}
|
|
|
return value > 0 ? callback() : callback('阈值必须大于0')
|
|
@@ -283,15 +442,15 @@ const alertRules = {
|
|
|
trigger: 'blur'
|
|
|
}
|
|
|
],
|
|
|
- action: [{ required: true, message: '请选择处理方案', trigger: 'change' }]
|
|
|
+ handle: [{ required: true, message: '请选择处理方案', trigger: 'change' }]
|
|
|
}
|
|
|
|
|
|
// 显示创建告警规则modal
|
|
|
const showCreateAlert = () => {
|
|
|
+ newAlert.value = { ...initAlert }
|
|
|
alertDialogVisible.value = true
|
|
|
}
|
|
|
|
|
|
-
|
|
|
// 显示告警规则详情
|
|
|
const showAlertDetail = (row) => {
|
|
|
selectedAlert.value = row
|
|
@@ -299,82 +458,128 @@ const showAlertDetail = (row) => {
|
|
|
}
|
|
|
|
|
|
// 创建告警
|
|
|
-const createAlert = () => {
|
|
|
- //post告警规则到后端
|
|
|
+const createAlert = async () => {
|
|
|
+ try {
|
|
|
+ await createAlertForm.value.validate()
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error("新建告警表单验证失败,请核对输入")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ for (let i = 0; i < alerts.value.length; i++) {
|
|
|
+ if (alerts.value[i].name == newAlert.value.name) {
|
|
|
+ ElMessage.error("新建告警表单验证失败,禁止创建重名告警")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // post告警规则到后端
|
|
|
+ const response = postData('/alert/', {
|
|
|
+ ...newAlert.value,
|
|
|
+ operation: 'create',
|
|
|
+ })
|
|
|
+ console.log((response))
|
|
|
+ //保存告警规则到前端
|
|
|
+ console.log(newAlert)
|
|
|
alerts.value.push({
|
|
|
- ...newAlert,
|
|
|
+ ...newAlert.value,
|
|
|
id: Date.now()
|
|
|
})
|
|
|
alertDialogVisible.value = false
|
|
|
}
|
|
|
|
|
|
// 删除告警
|
|
|
-const deleteAlert = (id) => {
|
|
|
- alerts.value = alerts.value.filter(a => a.id !== id)
|
|
|
+const deleteAlert = async (name) => {
|
|
|
+ const response = await postData("/alert/", { operation: 'delete', name: name })
|
|
|
+ // 成功删除
|
|
|
+ if (response.status == 'success') {
|
|
|
+ alerts.value = alerts.value.filter(a => a.name !== name)
|
|
|
+ } else {
|
|
|
+ ElMessage.error("删除告警失败: " + name)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // event.stopPropagation()
|
|
|
}
|
|
|
|
|
|
const formatRuntime = (timestamp) => {
|
|
|
- const seconds = Math.floor((Date.now() - timestamp) / 1000)
|
|
|
+ // const isoFormat = timestamp.replace(' ', 'T') + 'Z'
|
|
|
+ const targetTime = new Date(timestamp)
|
|
|
+ const seconds = Math.floor((Date.now() - targetTime.getTime()) / 1000)
|
|
|
const h = Math.floor(seconds / 3600)
|
|
|
const m = Math.floor((seconds % 3600) / 60)
|
|
|
- return `${h}h ${m}m`
|
|
|
+ const s = Math.floor((seconds % 60))
|
|
|
+ return `${h}h ${m}m ${s}s`
|
|
|
}
|
|
|
|
|
|
const fetchSystemStatus = async () => {
|
|
|
try {
|
|
|
// 实际应调用API接口
|
|
|
- const mockData = {
|
|
|
- cpu: +(Math.random() * 100).toFixed(2),
|
|
|
- memory: { used: +(8 + Math.random() * 4).toFixed(2), total: 32 },
|
|
|
- disk: { used: +(256 + Math.random() * 10).toFixed(2), total: 512 },
|
|
|
- processes: processList.value.length
|
|
|
+ // 获取system的详细性能信息
|
|
|
+ const response = await getData("/systemPerformance")
|
|
|
+ loading.value = false
|
|
|
+ console.log(response)
|
|
|
+ systemStatus.value = {
|
|
|
+ cpu: response.data.cpu,
|
|
|
+ memory: { used: response.data.mem_used, total: response.data.mem_total },
|
|
|
+ disk: {
|
|
|
+ // 换算成GB
|
|
|
+ used: Math.round(response.data.disk_used / 10737418.24) / 100,
|
|
|
+ total: Math.round(response.data.disk_total / 10737418.24) / 100,
|
|
|
+ },
|
|
|
+ processes: response.data.processes.length,
|
|
|
}
|
|
|
- systemStatus.value = mockData
|
|
|
+ // 获取当前触发的告警
|
|
|
+ triggeredAlerts.value = response.data.triggeredAlerts
|
|
|
+
|
|
|
cpuHistory.value = [...cpuHistory.value.slice(-29), systemStatus.value.cpu]
|
|
|
memoryHistory.value = [...memoryHistory.value.slice(-29), systemStatus.value.memory.used]
|
|
|
processHistory.value = [...processHistory.value.slice(-29), systemStatus.value.processes]
|
|
|
+
|
|
|
+ // 将processes放入进程列表
|
|
|
+ processList.value = []
|
|
|
+ response.data.processes.forEach(p => {
|
|
|
+ processList.value.push({
|
|
|
+ pid: p.pid,
|
|
|
+ missionId: p.missionId,
|
|
|
+ planId: p.planId,
|
|
|
+ algorithm: p.algorithm,
|
|
|
+ cpu: p.cpu,
|
|
|
+ user: p.user,
|
|
|
+ memory: p.mem_used,
|
|
|
+ startTime: p.startTime,
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
} catch (error) {
|
|
|
+ console.log(error)
|
|
|
ElMessage.error('获取系统状态失败')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const fetchProcessList = async () => {
|
|
|
- loading.value = true
|
|
|
+const fetchAlerts = async () => {
|
|
|
try {
|
|
|
- // 模拟进程数据
|
|
|
- processList.value = Array.from({ length: 15 }, (_, i) => ({
|
|
|
- pid: 1000 + i,
|
|
|
- name: `Process ${i + 1}`,
|
|
|
- cpu: Math.random() * 100,
|
|
|
- memory: Math.random() * 1024 * 1024,
|
|
|
- startTime: Date.now() - Math.random() * 3600000,
|
|
|
- io: {
|
|
|
- read: Math.random() * 100,
|
|
|
- write: Math.random() * 50
|
|
|
- }
|
|
|
- }))
|
|
|
- } finally {
|
|
|
- loading.value = false
|
|
|
+ const response = await getData('/alert/')
|
|
|
+ response.data.forEach(alert => {
|
|
|
+ alerts.value.push(alert)
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ console.log(error)
|
|
|
+ ElMessage.error('获取告警列表失败')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const processAction = async (pid, action) => {
|
|
|
- try {
|
|
|
- const res = await fetch(`/api/process/${pid}`, {
|
|
|
- method: 'POST',
|
|
|
- body: JSON.stringify({ action })
|
|
|
- })
|
|
|
-
|
|
|
- if (!res.ok) throw new Error()
|
|
|
- ElMessage.success(`${action === 'pause' ? '暂停' : '终止'}成功`)
|
|
|
- await fetchProcessList()
|
|
|
- } catch (error) {
|
|
|
- ElMessage.error('操作失败,请检查权限或进程状态')
|
|
|
+const processAction = async (missionId, action) => {
|
|
|
+ const response = await postData('/calculate/', {
|
|
|
+ command: action,
|
|
|
+ missionId: missionId,
|
|
|
+ })
|
|
|
+ if (response.status !== 'success') {
|
|
|
+ ElMessage.error(`对任务:${missionId} 执行${action === 'pause' ? '暂停' : '停止'}操作失败`)
|
|
|
}
|
|
|
+ ElMessage.success(`${action === 'pause' ? '暂停' : '终止'}成功`)
|
|
|
}
|
|
|
|
|
|
-const pauseProcess = (pid) => processAction(pid, 'pause')
|
|
|
-const killProcess = (pid) => processAction(pid, 'kill')
|
|
|
+const pauseMission = (missionId) => processAction(missionId, 'pause')
|
|
|
+const stopMission = (missionId) => processAction(missionId, 'stop')
|
|
|
|
|
|
const refreshProcess = () => {
|
|
|
fetchProcessList()
|
|
@@ -382,8 +587,9 @@ const refreshProcess = () => {
|
|
|
|
|
|
onMounted(() => {
|
|
|
fetchSystemStatus()
|
|
|
- fetchProcessList()
|
|
|
updateInterval = setInterval(fetchSystemStatus, 1000)
|
|
|
+ // 仅获取一次alert,当多个管理员登陆时可能出现问题
|
|
|
+ fetchAlerts()
|
|
|
})
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
@@ -589,5 +795,210 @@ onBeforeUnmount(() => {
|
|
|
color: #303133;
|
|
|
padding-left: 20px !important;
|
|
|
}
|
|
|
+
|
|
|
+ .sub-processes {
|
|
|
+ padding: 8px 0;
|
|
|
+
|
|
|
+ .sub-process {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 4px 0;
|
|
|
+
|
|
|
+ >span {
|
|
|
+ margin-right: 20px;
|
|
|
+
|
|
|
+ &.pid {
|
|
|
+ width: 80px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.cpu {
|
|
|
+ width: 80px;
|
|
|
+ color: #67c23a;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.memory {
|
|
|
+ width: 100px;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.time {
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.mission-header {
|
|
|
+ padding: 8px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.process-detail {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .pid {
|
|
|
+ width: 80px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ .algorithm {
|
|
|
+ flex: 1;
|
|
|
+ color: #409eff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.mission-info {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.total-cpu {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #67c23a;
|
|
|
+}
|
|
|
+
|
|
|
+.total-mem {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.mission-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ flex: 1;
|
|
|
+ padding: 8px 12px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-table__row--level-0) {
|
|
|
+ background-color: #f8faff;
|
|
|
+
|
|
|
+ td {
|
|
|
+ background-color: inherit !important;
|
|
|
+ border-bottom: 2px solid #ebeef5;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 自定义展开图标布局
|
|
|
+.mission-header-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ .expand-icon {
|
|
|
+ margin-right: 8px;
|
|
|
+ transition: transform 0.2s;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ &.is-expanded {
|
|
|
+ transform: rotate(90deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .mission-header {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0; // 防止内容溢出
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 覆盖默认表格样式
|
|
|
+:deep(.el-table) {
|
|
|
+ .el-table__expand-icon {
|
|
|
+ // 隐藏默认展开箭头
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调整行高适应新布局
|
|
|
+ .el-table__row--level-0 {
|
|
|
+ td {
|
|
|
+ padding: 12px 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.alert-notifications {
|
|
|
+ position: fixed;
|
|
|
+ padding: 0px 4px;
|
|
|
+ top: 20px;
|
|
|
+ right: 20px;
|
|
|
+ z-index: 9999;
|
|
|
+ max-height: calc(100vh - 40px);
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .alert-item {
|
|
|
+ width: 320px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ border: 1px solid #f3d19e; // 调整为浅橙色边框
|
|
|
+ border-radius: 8px;
|
|
|
+ background-color: #fffbe6; // 浅黄色背景
|
|
|
+ box-shadow: 0 2px 12px rgba(230, 162, 60, 0.2); // 橙色系阴影
|
|
|
+ transition: all 0.3s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateX(-4px);
|
|
|
+ background-color: #fff8dc; // 悬停时稍深的背景
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-content {
|
|
|
+ .alert-metric {
|
|
|
+ color: #e6a23c; // 主警告色
|
|
|
+ font-weight: 600;
|
|
|
+ display: flex;
|
|
|
+ align-items: center; // 确保flex容器垂直居中
|
|
|
+ height: 32px; // 添加固定高度
|
|
|
+ line-height: 32px; // 保持行高与高度一致
|
|
|
+
|
|
|
+ .el-icon {
|
|
|
+ color: #e6a23c; // 图标颜色同步
|
|
|
+ margin-right: 8px;
|
|
|
+ font-size: 18px; // 调大图标尺寸
|
|
|
+ vertical-align: middle; // 添加垂直对齐
|
|
|
+ position: relative;
|
|
|
+ top: -0.5px; // 微调图标位置
|
|
|
+ }
|
|
|
+
|
|
|
+ &>span {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%;
|
|
|
+ line-height: normal; // 重置行高
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .alert-details {
|
|
|
+ span {
|
|
|
+ &.alert-threshold {
|
|
|
+ color: #d48836; // 深橙色
|
|
|
+ }
|
|
|
+
|
|
|
+ &.alert-handle {
|
|
|
+ color: #b88230; // 棕橙色
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 覆盖element默认样式
|
|
|
+ :deep(.el-alert__title) {
|
|
|
+ color: #d48836; // 标题使用深橙色
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 关闭按钮颜色调整
|
|
|
+ :deep(.el-alert__closebtn) {
|
|
|
+ color: #d48836;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #e6a23c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|