Procházet zdrojové kódy

'430添加在线输入'

Lan před 2 měsíci
rodič
revize
1283c6f43f

binární
backend/db.sqlite3


+ 7 - 3
scheduler/processManager.py

@@ -100,9 +100,13 @@ class ProcessManager:
         # 放入进程性能占用信息
         report_performance['process'] = []
         for pid in self.processes:
-            ps_proc = psutil.Process(pid)
-            cpu = round(ps_proc.cpu_percent(interval=1) / psutil.cpu_count(), 1)
-            mem = ps_proc.memory_info().rss / (1024**2)  # 转为MB
+            try:
+                ps_proc = psutil.Process(pid)
+                cpu = round(ps_proc.cpu_percent(interval=1) / psutil.cpu_count(), 1)
+                mem = ps_proc.memory_info().rss / (1024**2)  # 转为MB
+            except psutil.NoSuchProcess:
+                # 该进程已退出
+                continue
             self.logger.info(f"获取到进程{pid}信息: CPU:{cpu} MEM:{mem}")
             report_performance['process'].append({
                 'planId': self.processes[pid]['meta']['plan']['id'],

+ 283 - 4
viewer/src/views/dashoard/analyze.vue

@@ -6,8 +6,8 @@
         <el-card class="upload-section">
           <div class="input-method-select" v-if="inputMethod != 'done'">
             <el-radio-group v-model="inputMethod">
-              <el-radio-button label="upload">文件上传</el-radio-button>
-              <el-radio-button label="online">在线输入</el-radio-button>
+              <el-radio-button value="upload">文件上传</el-radio-button>
+              <el-radio-button value="online">在线输入</el-radio-button>
             </el-radio-group>
           </div>
 
@@ -50,8 +50,86 @@
             <el-progress v-if="uploadProgress > 0" :percentage="uploadProgress" :status="uploadStatus"
               class="progress-bar" />
           </div>
+          <!-- 在线输入节点与边 -->
+
+
           <div v-else class="upload-area">
-            <router-view></router-view>
+            <div class="online-input">
+              <el-row :gutter="20">
+                <!-- 节点输入列 -->
+                <el-col :span="12">
+                  <div class="input-section">
+                    <h4>输入节点</h4>
+                    <div v-for="(node, index) in nodes" :key="index" class="input-row">
+                      <el-row :gutter="10" align="middle">
+                        <el-col :span="4">
+                          <div class="node-id">ID:{{ node.id }}</div>
+                        </el-col>
+                        <el-col :span="6">
+                          <el-select v-model="node.type" placeholder="选择类型" @change="handleNodeTypeChange(index)">
+                            <el-option label="S" value="S" />
+                            <el-option label="D" value="D" />
+                            <el-option label="I" value="I" />
+                          </el-select>
+                        </el-col>
+                        <el-col :span="10">
+                          <el-input v-model="node.name" placeholder="输入节点名称" />
+                        </el-col>
+                        <el-col :span="4">
+                          <el-button v-if="index < nodes.length - 1" @click="deleteNode(index)" type="danger" plain
+                            size="small">
+                            删除
+                          </el-button>
+                        </el-col>
+                      </el-row>
+                    </div>
+                  </div>
+                </el-col>
+
+                <!-- 边输入列 -->
+                <el-col :span="12">
+                  <div class="input-section">
+                    <h4>输入边</h4>
+                    <div v-for="(edge, index) in edges" :key="index" class="input-row">
+                      <el-row :gutter="10" align="middle">
+                        <el-col :span="4">
+                          <div class="node-id">ID:{{ index + 1 }}</div>
+                        </el-col>
+                        <!-- 起始节点 -->
+                        <el-col :span="8">
+                          <el-select v-model="edge.from" placeholder="起始节点" :disabled="nodeOptions.length === 0"
+                            @change="handleEdgeChange(index)">
+                            <el-option v-for="node in nodeOptions" :key="node.id"
+                              :label="`ID:${node.id}-${node.name || '未命名'}`" :value="node.id" />
+                          </el-select>
+                        </el-col>
+
+                        <!-- 终止节点 -->
+                        <el-col :span="8">
+                          <el-select v-model="edge.to" placeholder="终止节点" :disabled="nodeOptions.length === 0"
+                            @change="handleEdgeChange(index)">
+                            <el-option v-for="node in nodeOptions" :key="node.id"
+                              :label="`ID:${node.id}-${node.name || '未命名'}`" :value="node.id" />
+                          </el-select>
+                        </el-col>
+
+                        <!-- 删除按钮 -->
+                        <el-col :span="4">
+                          <el-button v-if="index < edges.length - 1" @click="deleteEdge(index)" type="danger" plain
+                            size="small">
+                            删除
+                          </el-button>
+                        </el-col>
+                      </el-row>
+                    </div>
+
+                  </div>
+                </el-col>
+              </el-row>
+            </div>
+            <el-button type="primary" @click="handleValidation" class="validate-button">
+              开始输入验证
+            </el-button>
           </div>
 
         </el-card>
@@ -140,7 +218,7 @@
 import { ref, computed, onMounted, inject } from 'vue'
 import { useRouter } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Upload, ArrowDown } from '@element-plus/icons-vue'
+import { Upload, ArrowDown, Plus } from '@element-plus/icons-vue'
 import { getData, postData, deleteData, postFile } from '@/api/axios.js'
 
 
@@ -149,18 +227,186 @@ const useAnalyzeInfo = inject('analyzeInfo')
 
 // 响应式数据
 const inputMethod = ref('upload')
+// 上传的节点和边文件
 const nodeFile = ref(null)
 const edgeFile = ref(null)
+// 在线输入的节点和边
+const nodes = ref([{ id: 1, type: '', name: '' }])
+const edges = ref([{ from: '', to: '' }])
+
 const uploadProgress = ref(0)
 const uploadStatus = ref('')
 const fileHistory = ref([])
 const router = useRouter()
+
+
+
 // 计算属性
+// 是否允许上传文件
 const canUpload = computed(() => {
   return nodeFile.value && edgeFile.value
 })
+// 在线输入边时节点的可选项
+const nodeOptions = computed(() => {
+  return nodes.value
+    .filter(node => node.type !== '')
+    .map(node => ({
+      id: node.id,
+      name: node.name || '未命名',
+      type: node.type
+    }))
+})
 
 // 方法
+
+// 在线输入自动添加节点
+const handleNodeTypeChange = (index) => {
+  // 当最后一个节点选择类型后自动添加新行
+  if (index === nodes.value.length - 1) {
+    nodes.value.push({ id: nodes.value.length + 1, type: '', name: '' })
+  }
+}
+
+// 删除节点
+const deleteNode = (index) => {
+  // 保存原始ID列表
+  const originalIds = nodes.value.map(n => n.id)
+  const deletedId = originalIds[index]
+  // 删除关联边
+  edges.value = edges.value.filter(edge =>
+    edge.from !== deletedId && edge.to !== deletedId
+  )
+  // 删除节点并创建新数组
+  const newNodes = [...nodes.value]
+  newNodes.splice(index, 1)
+
+  // 创建ID映射表(旧ID -> 新ID)
+  const idMap = new Map()
+  newNodes.forEach((node, i) => {
+    const newId = i + 1
+    idMap.set(node.id, newId)  // 记录原始ID到新ID的映射
+    node.id = newId            // 更新节点ID
+  })
+
+  // 更新节点数组
+  nodes.value = newNodes
+
+  // 更新边数据中的ID引用
+  edges.value = edges.value.map(edge => ({
+    from: idMap.get(edge.from),
+    to: idMap.get(edge.to)
+  }))
+  // 确保至少保留一个空行
+  if (nodes.value.length === 0) {
+    nodes.value.push({ id: 1, type: '', name: '' })
+  }
+}
+
+// 获取有效节点ID列表
+const validNodeIds = computed(() => {
+  return nodes.value
+    .filter(node => node.type !== '')
+    .map(node => node.id)
+})
+
+// 在线输入自动添加边
+const handleEdgeChange = (index) => {
+  const currentEdge = edges.value[index]
+  // 当最后一个边填写完整后自动添加新行
+  if (index === edges.value.length - 1 && currentEdge.from && currentEdge.to) {
+    edges.value.push({ from: '', to: '' })
+  }
+}
+
+// 删除边
+const deleteEdge = (index) => {
+  edges.value.splice(index, 1)
+}
+
+// 验证在线输入
+const handleValidation = async () => {
+  const errors = []
+  const edgeSet = new Set()
+
+
+  // 检查边数据
+  // 因为始终有空行,所有没有输入的时候长度为1
+  if(edges.value.length == 1){
+    errors.push(`没有输入边`)
+  }
+  if(nodes.value.length == 1){
+    errors.push(`没有输入节点`)
+  }
+  edges.value.slice(0, -1).forEach((edge, index) => {
+    // 检查节点是否存在
+    if (!validNodeIds.value.includes(edge.from)) {
+      errors.push(`边 ${index + 1}: 起始节点 ${edge.from} 不存在`)
+    }
+    if (!validNodeIds.value.includes(edge.to)) {
+      errors.push(`边 ${index + 1}: 终止节点 ${edge.to} 不存在`)
+    }
+
+    // 自环边检查
+    if (edge.from === edge.to) {
+      errors.push(`边 ${index + 1}: 不允许连接自身(自环边)`)
+    }
+    // 归一化两个方向的边
+    const [min, max] = [edge.from, edge.to].sort()
+    // 默认检查无向边
+    const checkDirection = false
+    const edgeKey = checkDirection
+      ? `${edge.from},${edge.to}`
+      : `${min},${max}`
+
+    // 检查重复边
+    if (edgeSet.has(edgeKey)) {
+      errors.push(`发现重复边: ${edge.from} → ${edge.to}`)
+    } else {
+      edgeSet.add(edgeKey)
+    }
+  })
+
+  // 处理验证结果
+  if (errors.length > 0) {
+    ElMessage.error({
+      message: `发现 ${errors.length} 个错误:<br>${errors.join('<br>')}`,
+      duration: 5000,
+      dangerouslyUseHTMLString: true, // 启用 HTML 解析
+      customClass: 'error-message'    // 可选:添加自定义样式类
+    })
+    return
+  }
+
+  // 构造上传数据
+  const uploadData = {
+    nodes: nodes.value
+      .filter(node => node.type !== '')
+      .map(node => ({
+        id: node.id,
+        type: node.type,
+        name: node.name
+      })),
+    edges: edges.value
+      .filter(edge => edge.from && edge.to)
+      .map(edge => ({
+        source: edge.from,
+        target: edge.to
+      }))
+  }
+
+  // 执行上传
+  try {
+    // await postData('/api/upload', uploadData)
+    ElMessage.success('数据验证通过,上传成功')
+  } catch (error) {
+    ElMessage.error('上传失败: ' + error.message)
+  }
+}
+
+
+
+// 上传文件处理
+
 const handleNodeFileChange = (file, fileList) => {
   if (fileList.length > 1) {
     fileList.splice(0, 1);
@@ -392,4 +638,37 @@ onMounted(() => {
     }
   }
 }
+
+.online-input {
+  padding: 15px;
+
+  .input-section {
+    border: 1px solid var(--el-border-color);
+    border-radius: 8px;
+    padding: 15px;
+    margin-bottom: 20px;
+
+    h4 {
+      margin-bottom: 15px;
+      color: var(--el-text-color-primary);
+    }
+
+    .input-row {
+      margin-bottom: 12px;
+
+      .node-id {
+        padding: 8px 12px;
+        background: var(--el-fill-color-light);
+        border-radius: 4px;
+        text-align: center;
+      }
+    }
+  }
+}
+
+.el-message.error-message {
+  white-space: pre-line;
+  line-height: 1.6;
+  padding: 15px 20px;
+}
 </style>

+ 11 - 16
viewer/src/views/dashoard/monitor.vue

@@ -101,11 +101,6 @@
                             <template #header>
                                 <div class="table-header">
                                     <span>运行中进程({{ processList.length }})</span>
-                                    <el-button type="primary" size="small" @click="refreshProcess">
-                                        <el-icon>
-                                            <Refresh />
-                                        </el-icon>
-                                    </el-button>
                                 </div>
                             </template>
 
@@ -173,10 +168,10 @@
                                 <el-table-column label="操作" width="300">
                                     <template #default="{ row }">
                                         <div v-if="row.processes" class="group-actions">
-                                            <el-button size="small" @click.stop="pauseMission(row.missionId)"
+                                            <!-- <el-button size="small" @click.stop="pauseMission(row.missionId)"
                                                 :disabled="row.status === 'paused'">
                                                 {{ row.status === 'paused' ? '已暂停' : '暂停任务' }}
-                                            </el-button>
+                                            </el-button> -->
                                             <el-button size="small" type="danger"
                                                 @click.stop="stopMission(row.missionId)">
                                                 终止任务
@@ -319,6 +314,7 @@ const groupedProcessList = computed(() => {
         group.totalMemory = 0
         group.processes = []
     })
+    // 暂存当前运行的mission
     const currentMissionIds = new Set()
     // 处理原始数据分组
     processList.value.forEach(process => {
@@ -350,15 +346,18 @@ const groupedProcessList = computed(() => {
             group.startTime = process.startTime
         }
     })
-    Object.keys(groups).forEach(missionId => {
+    console.log(currentMissionIds)
+
+    groups.forEach((mission, missionId) => {
         if (!currentMissionIds.has(missionId)) {
-            groups[missionId].remain -= 1
-            if (groups[missionId].remain <= 0) {
-                delete groups[missionId]
+            mission.remain -= 1
+            console.log(mission.remain)
+            if (mission.remain <= 0) {
+                groups.delete(missionId)
             }
         }
     })
-    console.log(Array.from(groups.values()))
+    console.log(Array.from(groups))
     return Array.from(groups.values())
 })
 
@@ -581,10 +580,6 @@ const processAction = async (missionId, action) => {
 const pauseMission = (missionId) => processAction(missionId, 'pause')
 const stopMission = (missionId) => processAction(missionId, 'stop')
 
-const refreshProcess = () => {
-    fetchProcessList()
-}
-
 onMounted(() => {
     fetchSystemStatus()
     updateInterval = setInterval(fetchSystemStatus, 1000)

+ 2 - 2
viewer/src/views/dashoard/select.vue

@@ -5,7 +5,7 @@
         <div class="action-button" @click="navigateTo('analyze')" @mouseenter="hoverLeft = true"
           @mouseleave="hoverLeft = false">
           <SvgIcon :name="hoverLeft ? 'analyze-hover' : 'analyze'" />
-          <div class="button-text">我要分析图</div>
+          <div class="button-text">创建计算任务</div>
         </div>
       </div>
 
@@ -13,7 +13,7 @@
         <div class="action-button" @click="navigateTo('view')" @mouseenter="hoverRight = true"
           @mouseleave="hoverRight = false">
           <SvgIcon :name="hoverRight ? 'view-hover' : 'view'" />
-          <div class="button-text">我要查看图</div>
+          <div class="button-text">查看计算任务</div>
         </div>
       </div>
     </div>