zs 5 months ago
parent
commit
7c775d0b9c

BIN
public/images/partition.png


+ 40 - 0
src/store/api/core/performance.js

@@ -0,0 +1,40 @@
+import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+import { get } from 'lodash-es'
+const url = '/performance'
+const axios = new AxiosWrapper()
+
+export const PerformanceStore = defineStore('performance', () => {
+  const query = async ({ skip = 0, limit = undefined, ...info } = {}) => {
+    let cond = {}
+    if (skip) cond.skip = skip
+    if (limit) cond.limit = limit
+    cond = { ...cond, ...info }
+    const res = await axios.$get(`${url}`, cond)
+    return res
+  }
+  const fetch = async (payload) => {
+    const res = await axios.$get(`${url}/${payload}`)
+    return res
+  }
+  const create = async (payload) => {
+    const res = await axios.$post(`${url}`, payload)
+    return res
+  }
+  const update = async (payload) => {
+    const id = get(payload, 'id', get(payload, '_id'))
+    const res = await axios.$post(`${url}/${id}`, payload)
+    return res
+  }
+  const del = async (payload) => {
+    const res = await axios.$delete(`${url}/${payload}`)
+    return res
+  }
+  return {
+    query,
+    fetch,
+    create,
+    update,
+    del
+  }
+})

+ 80 - 6
src/views/control/performance/index.vue

@@ -1,20 +1,94 @@
 <template>
-  <div id="index">
-    <el-row>
-      <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 性能管理 </el-col>
-      </el-col>
-    </el-row>
+  <div class="main animate__animated animate__backInRight" v-loading="loading">
+    <custom-search-bar :fields="fields.filter((f) => f.isSearch)" v-model="searchForm" @search="search" @reset="toReset"></custom-search-bar>
+    <custom-button-bar :fields="buttonFields" @add="toAdd"></custom-button-bar>
+    <custom-table :data="data" :fields="fields" @query="search" :total="total" :opera="opera" @edit="toEdit" @delete="toDelete" :select="true"> </custom-table>
+    <el-dialog v-model="dialog" title="数据维护信息" :destroy-on-close="false" @close="toClose" width="50%">
+      <custom-form v-model="form" :fields="fields" :rules="{}" @save="toSave"> </custom-form>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
+const $checkRes = inject('$checkRes')
+import { cloneDeep, get } from 'lodash-es'
+const { t } = useI18n()
+// 接口
+import { PerformanceStore } from '@/store/api/core/performance'
+const store = PerformanceStore()
+const data = ref([])
+const searchForm = ref({})
+const fields = [
+  { label: '节点名称', model: 'node_name', isSearch: true },
+  { label: '管理IP', model: 'admin_ip' },
+  { label: '系统IP', model: 'system_ip' },
+  { label: '开关机状态', model: 'switch' },
+  { label: 'CPU利用率(%)', model: 'cpu_utilize' },
+  { label: 'GPU利用率(%)', model: 'gpu_utilize' },
+  { label: '内存利用率(%)', model: 'memory' },
+  { label: '网络端口收发速度', model: 'speed' },
+  { label: '负载', model: 'load' }
+]
+const opera = [
+  { label: t('common.update'), method: 'edit' },
+  { label: t('common.delete'), method: 'delete', confirm: true, type: 'danger' }
+]
+const buttonFields = [{ label: t('common.add'), method: 'add' }]
+let skip = 0
+let limit = inject('limit')
+const total = ref(20)
 // 加载中
 const loading = ref(false)
+const dialog = ref(false)
+const form = ref({})
 // 请求
 onMounted(async () => {
   loading.value = true
+  await search({ skip, limit })
   loading.value = false
 })
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit, ...searchForm.value }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data.data
+    total.value = res.data.total
+  }
+}
+// 添加
+const toAdd = () => {
+  dialog.value = true
+}
+// 修改
+const toEdit = (data) => {
+  form.value = data
+  dialog.value = true
+}
+// 删除
+const toDelete = async (data) => {
+  const res = await store.del(data._id)
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+  }
+}
+const toSave = async () => {
+  const data = cloneDeep(form.value)
+  let res
+  if (get(data, '_id')) res = await store.update(data)
+  else res = await store.create({ ...data })
+  if ($checkRes(res, true)) {
+    search({ skip: 0, limit })
+    toClose()
+  }
+}
+// 重置
+const toReset = async () => {
+  searchForm.value = {}
+  await search({ skip, limit })
+}
+const toClose = () => {
+  form.value = {}
+  dialog.value = false
+}
 </script>
 <style scoped lang="scss"></style>

+ 59 - 0
src/views/control/resource/echarts/echarts1.vue

@@ -0,0 +1,59 @@
+<template>
+  <div ref="echarts1" class="echarts1"></div>
+</template>
+<style scoped lang="scss">
+.echarts1 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts1 = ref()
+onMounted(() => {
+  echarts1View()
+})
+function echarts1View() {
+  const myChart1 = echarts.init(echarts1.value)
+  const option1 = {
+    tooltip: {
+      trigger: 'item'
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left'
+    },
+    series: [
+      {
+        name: '分区状态',
+        type: 'pie',
+        radius: '80%',
+        itemStyle: {
+          normal: {
+            label: {
+              show: true,
+              formatter: '{b} : ({c})'
+            }
+          },
+          labelLine: { show: true }
+        },
+        data: [
+          { value: 5, name: '满载' },
+          { value: 24, name: '空闲' }
+        ],
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  }
+  myChart1.setOption(option1)
+  window.addEventListener('resize', function () {
+    myChart1.resize()
+  })
+}
+</script>

+ 143 - 2
src/views/control/resource/index.vue

@@ -2,19 +2,160 @@
   <div id="index">
     <el-row>
       <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 资源管理 </el-col>
+        <el-row :gutter="20" class="one">
+          <el-col :span="6">
+            <el-card>
+              <div class="one_1">
+                <el-image class="image" :src="partition" fit="fill" />
+                <div class="title">分区数量:{{ partition_total || 0 }}</div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card>
+              <div class="one_1">
+                <el-tag class="tag" v-for="item in statusList" :key="item.id" :type="item.type" effect="dark" round> {{ item.name }} : {{ item.total }} </el-tag>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card>
+              <div class="one_2">
+                <div class="left">
+                  <span>CPU(核)</span>
+                  <span>{{ cpu.total || 0 }}/{{ cpu.yes || 0 }}</span>
+                </div>
+                <div class="right">
+                  <el-progress width="90" type="circle" :percentage="22.4" />
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="6">
+            <el-card>
+              <div class="one_2">
+                <div class="left">
+                  <span>GPU(个)</span>
+                  <span>{{ gpu.total || 0 }}/{{ gpu.yes || 0 }}</span>
+                </div>
+                <div class="right">
+                  <el-progress width="90" type="circle" :percentage="0" />
+                </div>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20" class="two">
+          <el-col :span="12" v-for="(item, index) in list" :key="index">
+            <el-card>
+              <template #header>
+                <div class="card-header" @click="toView(item)">
+                  <span>{{ item.name }}></span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts1></echarts1>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script setup>
+// 图片引入
+import partition from '/images/partition.png'
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+// 接口
+import { PartitionStore } from '@/store/api/core/partition'
+const partitionStore = PartitionStore()
+// 路由
+const router = useRouter()
+// 分区总数
+const partition_total = ref(2)
+const cpu = ref({ total: 5464, yes: 1224, n0: 0 })
+const gpu = ref({ total: 0, yes: 0, n0: 0 })
+const statusList = ref([
+  { id: 1, total: 11, name: '满载', type: 'danger' },
+  { id: 2, total: 5, name: '半载', type: 'warning' },
+  { id: 3, total: 33, name: '空闲', type: 'primary' },
+  { id: 4, total: 0, name: '其他', type: 'info' }
+])
+const list = ref([
+  { id: 1, name: 'cu-amd', cpu: '616/1568', gpu: '0/0' },
+  { id: 2, name: 'computerPartion', cpu: '608/3840', gpu: '0/0' }
+])
+
 // 加载中
 const loading = ref(false)
+
 // 请求
 onMounted(async () => {
   loading.value = true
+  await search()
   loading.value = false
 })
+const search = async (query = { skip: 0, limit: 1 }) => {
+  const info = { skip: query.skip, limit: query.limit }
+  const res = await partitionStore.query(info)
+  if (res.errcode == '0') partition_total.value = res.data.total
+}
+// 查看分区
+const toView = (item) => {
+  console.log(item)
+  router.push({ path: '/colony/partition' })
+}
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.main {
+  .one {
+    margin: 20px 0 0 0;
+    .one_1 {
+      height: 95px;
+      display: flex;
+      align-items: center;
+      .image {
+        width: 50px;
+        height: 50px;
+      }
+      .title {
+        font-weight: bold;
+        margin: 0 10px;
+      }
+      .tag {
+        margin: 0 5px;
+      }
+    }
+    .one_2 {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .left {
+        display: flex;
+        flex-direction: column;
+        span:first-child {
+          color: #909399;
+        }
+        span:last-child {
+          font-size: 20px;
+          font-weight: bold;
+          margin: 10px 0 0 0;
+        }
+      }
+    }
+  }
+  .two {
+    margin: 20px 0 0 0;
+    .card-header {
+      color: #409eff;
+      cursor: pointer;
+    }
+    .echarts {
+      height: 200px;
+    }
+  }
+}
+</style>

+ 1 - 1
src/views/job/history/index.vue

@@ -19,7 +19,7 @@ const store = HistoryStore()
 const data = ref([])
 const searchForm = ref({})
 const fields = [
-  { label: '作业Id', model: 'work_id' },
+  { label: '作业ID', model: 'work_id' },
   { label: '作业名称', model: 'work_name', isSearch: true },
   { label: '用户', model: 'user_name', isSearch: true },
   { label: '组织', model: 'organization', isSearch: true },

+ 73 - 0
src/views/job/monitor/echarts/echarts1.vue

@@ -0,0 +1,73 @@
+<template>
+  <div ref="echarts1" class="echarts1"></div>
+</template>
+<style scoped lang="scss">
+.echarts1 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts1 = ref()
+onMounted(() => {
+  echarts1View()
+})
+function echarts1View() {
+  const myChart1 = echarts.init(echarts1.value)
+  const option1 = {
+    tooltip: {
+      // 鼠标悬浮提示数据
+      trigger: 'axis',
+      borderWidth: 15,
+      textStyle: {
+        // 文字提示样式
+        fontSize: '16'
+      },
+      axisPointer: {
+        // 坐标轴虚线
+        type: 'cross',
+        label: {
+          backgroundColor: '#6a7985'
+        }
+      }
+    },
+
+    // },
+    grid: {
+      // 控制图表的位置
+      left: '5%',
+      right: '5%',
+      top: '5%',
+      bottom: '5%',
+      containLabel: true
+    },
+    xAxis: {
+      axisLabel: {
+        // X轴线 标签修改
+        textStyle: {
+          color: '#333', //更改坐标轴文字颜色
+          fontSize: 14 //更改坐标轴文字大小
+        }
+      },
+      data: ['运行', '排队', '挂起', '其他']
+    },
+    yAxis: {},
+    series: [
+      {
+        data: [23, 5, 0, 0],
+        type: 'bar',
+        label: {
+          show: true, // 显示标签
+          position: 'top' // 标签位置顶部
+        },
+        barWidth: '40%' //调整柱状图宽度
+      }
+    ]
+  }
+  myChart1.setOption(option1)
+  window.addEventListener('resize', function () {
+    myChart1.resize()
+  })
+}
+</script>

+ 151 - 0
src/views/job/monitor/echarts/echarts2.vue

@@ -0,0 +1,151 @@
+<template>
+  <div ref="echarts2" class="echarts2"></div>
+</template>
+<style scoped lang="scss">
+.echarts2 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts2 = ref()
+onMounted(() => {
+  echarts2View()
+})
+function echarts2View() {
+  let ydata = ['warp', 'warp', 'warp', 'warp', 'qe-250']
+  let xdata = [10, 11, 12, 13, 50]
+  const myChart2 = echarts.init(echarts2.value)
+  const option2 = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      left: '15%',
+      right: '5%',
+      bottom: '0%',
+      top: '0%',
+      containLabel: false
+    },
+    xAxis: {
+      type: 'value',
+      show: false
+    },
+    yAxis: {
+      type: 'category',
+      data: ydata,
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        margin: 70,
+        width: 60,
+        align: 'left',
+        overflow: 'truncate',
+        formatter: function (value, index) {
+          let ind = index + 1
+          if (ind == ydata.length) {
+            return '{one|' + (ydata.length - index) + '} {a|' + value + '}'
+          } else if (ind + 1 == ydata.length) {
+            return '{two|' + (ydata.length - index) + '} {b|' + value + '}'
+          } else if (ind + 2 == ydata.length) {
+            return '{three|' + (ydata.length - index) + '} {c|' + value + '}'
+          }
+          if (ydata.length - index > 9) {
+            return '{five|' + (ydata.length - index) + '} {d|' + value + '}'
+          }
+          return '{four|' + (ydata.length - index) + '} {d|' + value + '}'
+        },
+        rich: {
+          a: {
+            color: '#59c9f9'
+          },
+          b: {
+            color: '#59c9f9'
+          },
+          c: {
+            color: '#59c9f9'
+          },
+          d: {
+            color: '#59c9f9'
+          },
+          // 第一名
+          one: {
+            backgroundColor: '#E86452',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 第二名
+          two: {
+            backgroundColor: '#FF9845',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 第三名
+          three: {
+            backgroundColor: '#F6BD16',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 一位数
+          four: {
+            backgroundColor: 'rgba(0,0,0,0.15)',
+            color: 'white',
+            width: 12,
+            height: 16,
+            padding: [1, 0, 0, 5],
+            borderRadius: 10,
+            fontSize: 11
+          },
+          // 两位数
+          five: {
+            backgroundColor: 'rgba(0,0,0,0.15)',
+            color: 'white',
+            width: 16,
+            height: 16,
+            padding: [1, 0, 0, 1],
+            borderRadius: 10,
+            fontSize: 11
+          }
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        showBackground: true,
+        label: {
+          show: true,
+          position: 'right',
+          color: 'rgba(0,0,0,0.45)'
+        },
+        barWidth: 20,
+        itemStyle: {
+          color: '#5B8FF9'
+        },
+        data: xdata
+      }
+    ]
+  }
+  myChart2.setOption(option2)
+  window.addEventListener('resize', function () {
+    myChart2.resize()
+  })
+}
+</script>

+ 69 - 0
src/views/job/monitor/echarts/echarts3.vue

@@ -0,0 +1,69 @@
+<template>
+  <div ref="echarts3" class="echarts3"></div>
+</template>
+<style scoped lang="scss">
+.echarts3 {
+  height: 100%;
+  width: 100%;
+}
+</style>
+<script setup>
+import * as echarts from 'echarts'
+const echarts3 = ref()
+onMounted(() => {
+  echarts3View()
+})
+function echarts3View() {
+  const myChart3 = echarts.init(echarts3.value)
+  const rawData = [
+    [100, 302, 301, 334, 390, 330, 320],
+    [320, 132, 101, 134, 90, 230, 210],
+    [0, 0, 0, 0, 0, 0, 0],
+    [0, 0, 0, 0, 0, 0, 0],
+  ]
+  const totalData = []
+  for (let i = 0; i < rawData[0].length; ++i) {
+    let sum = 0
+    for (let j = 0; j < rawData.length; ++j) {
+      sum += rawData[j][i]
+    }
+    totalData.push(sum)
+  }
+
+  const grid = {
+    left: '15%',
+    right: '5%',
+    bottom: '10%',
+    top: '20%',
+    containLabel: false
+  }
+
+  const series = ['运行', '排队', '挂起', '其他'].map((name, sid) => {
+    return {
+      name,
+      type: 'bar',
+      stack: 'total',
+      barWidth: '60%',
+      data: rawData[sid].map((d, did) => (totalData[did] <= 0 ? 0 : d / totalData[did]))
+    }
+  })
+  const option3 = {
+    legend: {
+      selectedMode: false
+    },
+    grid,
+    yAxis: {
+      type: 'value'
+    },
+    xAxis: {
+      type: 'category',
+      data: ['分区1', '分区2', '分区3', '分区4', '分区5']
+    },
+    series
+  }
+  myChart3.setOption(option3)
+  window.addEventListener('resize', function () {
+    myChart3.resize()
+  })
+}
+</script>

+ 79 - 2
src/views/job/monitor/index.vue

@@ -2,19 +2,96 @@
   <div id="index">
     <el-row>
       <el-col :span="24" class="main animate__animated animate__backInRight" v-loading="loading">
-        <el-col :span="24" class="one"> 监控作业 </el-col>
+        <el-row :gutter="20" class="one">
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>集群作业状态</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts1></echarts1>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>作业等待时长TOP5</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts2></echarts2>
+              </div>
+            </el-card>
+          </el-col>
+          <el-col :span="8">
+            <el-card>
+              <template #header>
+                <div class="card-header">
+                  <span>分区作业统计</span>
+                </div>
+              </template>
+              <div class="echarts">
+                <echarts3></echarts3>
+              </div>
+            </el-card>
+          </el-col>
+        </el-row>
+        <el-col :span="24" class="two">
+          <custom-table :data="data" height="45vh" :fields="fields" @query="search" :total="total"> </custom-table>
+        </el-col>
       </el-col>
     </el-row>
   </div>
 </template>
 
 <script setup>
+// 组件
+import echarts1 from './echarts/echarts1.vue'
+import echarts2 from './echarts/echarts2.vue'
+import echarts3 from './echarts/echarts3.vue'
+// 接口
+import { RealtimeStore } from '@/store/api/core/realtime'
+const store = RealtimeStore()
+const data = ref([])
+const fields = [
+  { label: '作业ID', model: 'work_id' },
+  { label: '作业名称', model: 'work_name' },
+  { label: '状态', model: 'status' },
+  { label: '等待时长', model: 'wait_time' },
+  { label: '运行时长', model: 'time' },
+  { label: 'CPU(核)', model: 'cpu' },
+  { label: 'GPU(个)', model: 'gpu' }
+]
+let skip = 0
+let limit = inject('limit')
+const total = ref(10)
 // 加载中
 const loading = ref(false)
 // 请求
 onMounted(async () => {
   loading.value = true
+  await search({ skip, limit })
   loading.value = false
 })
+const search = async (query = { skip: 0, limit }) => {
+  const info = { skip: query.skip, limit: query.limit }
+  const res = await store.query(info)
+  if (res.errcode == '0') {
+    data.value = res.data.data
+    total.value = res.data.total
+  }
+}
 </script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.main {
+  .one {
+    .echarts {
+      height: 200px;
+    }
+  }
+}
+</style>