update:213功能需求开发,bug修复

This commit is contained in:
2026-02-12 16:49:12 +08:00
parent b54836e404
commit 22d47b1162
7 changed files with 355 additions and 36 deletions

View File

@@ -20,3 +20,11 @@ export const stopHpcJobApi = (params: any) => {
export const delHpcJobsApi = (params: any) => {
return post(`${PREFIX}pbs/delHpcJobs`, params);
};
/**
* 查询hpc作业资源接口
* @returns
*/
export const queryHpcResourceApi = () => {
return get(`${PREFIX}pbs/queryHpcResource`);
};

View File

@@ -2,9 +2,9 @@
<div class="comp-hpc-list">
<Dialog v-model="visible" diaTitle="作业执行列表" width="80%" height="90%" @close="closeFun">
<div class="content">
<el-tabs v-model="activeName" type="card" class="demo-tabs">
<el-tabs v-model="activeName" type="card" class="demo-tabs" @tab-change="changeTabsFun">
<el-tab-pane label="HPC作业列表" name="cloud">
<div class="table">
<div class="table white">
<BaseTable
ref="baseTableRef"
tableName="HPC_LIST_TABLE"
@@ -40,9 +40,53 @@
</BaseTable>
</div>
</el-tab-pane>
<!-- <el-tab-pane label="本地作业执行列表" name="local">
<div class="table"></div>
</el-tab-pane> -->
<el-tab-pane label="HPC资源详情" name="resources">
<div class="table">
<div class="chart-box">
<div class="chart-box-left">
<commonFilterChart
title="资源运行状态"
:charts-id="'chart-1'"
:bar-type="'pieChart'"
:option="chartOptionOne"
></commonFilterChart>
</div>
<div class="chart-box-right">
<commonFilterChart
title="节点分配状态"
:charts-id="'chart-2'"
:bar-type="'pieChart'"
:option="chartOptionTwo"
></commonFilterChart>
</div>
</div>
<div class="pege-table">
<BaseTable
ref="baseTableRef2"
:showSetting="false"
tableName="HPC_RESOURCE_LIST"
fullHeight
hidePagination
:data="hpcSourceList"
>
<template #nodeStatus="{ row }">
<el-tag type="success" v-if="row.nodeStatus === 'Online'">在线</el-tag>
<el-tag type="info" v-else-if="row.nodeStatus === 'Offline'">离线</el-tag>
<el-tag type="warning" v-else>异常</el-tag>
</template>
<template #efficiency="{ row }">
<span>{{ row.freeCores }}</span
>/<span>{{ row.totalCores }}</span
>|<span>{{ getefficiencyFun(row) }}</span>
</template>
<template #nodeName="{ row }">
<el-button link type="primary">{{ row.nodeName }}</el-button>
</template>
</BaseTable>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</Dialog>
@@ -51,13 +95,14 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { delHpcJobsApi, queryJobsApi, stopHpcJobApi } from '@/api/pbs/pbs';
import { delHpcJobsApi, queryHpcResourceApi, queryJobsApi, stopHpcJobApi } from '@/api/pbs/pbs';
import Dialog from '@/components/common/dialog/index.vue';
import BaseTable from '@/components/common/table/baseTable.vue';
import { ElMessage } from 'element-plus';
import { useDict } from '@/utils/useDict';
import { queryAllApplicationApi } from '@/api/system/application';
import { FileUtil } from '@/utils/file';
import commonFilterChart from '@/components/common/echartCard/commonFilterChart.vue';
const { WORK_STATUS } = useDict('WORK_STATUS');
@@ -70,6 +115,7 @@ const props = withDefaults(defineProps<Props>(), {
});
const baseTableRef = ref();
const baseTableRef2 = ref();
const emit = defineEmits(['update:modelValue']);
const visible = ref(false);
const activeName = ref('cloud');
@@ -192,6 +238,135 @@ const getAppInfo = async () => {
} catch {}
};
const hpcSourceList = ref<any>([]);
const changeTabsFun = () => {
if (activeName.value === 'resources') {
queryHpcResourceFun();
}
};
const queryHpcResourceFun = async () => {
try {
const res: any = await queryHpcResourceApi();
if (res && res.code === 200) {
hpcSourceList.value = res.data.nodeList;
initChartOneFun(res.data);
initChartTwoFun(res.data);
}
} catch (error) {
console.log(error);
}
};
const colorList = ref<any>(['#409eff', '#67c23a', '#909399']);
const chartOptionOne = ref();
const initChartOneFun = (data: any) => {
chartOptionOne.value = {
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: '70%',
top: 'center',
icon: 'circle',
},
series: [
{
type: 'pie',
radius: ['60%', '70%'],
center: ['30%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold',
},
},
data: [
{ value: data.totalNodes || '', name: '全部' },
{ value: data.totalNodes - data.freeNodes || '', name: '已使用' },
{ value: data.freeNodes || '', name: '未使用' },
],
itemStyle: {
color: function (params: any) {
return colorList.value[params.dataIndex];
},
},
},
],
};
};
const chartOptionTwo = ref();
const initChartTwoFun = (data: any) => {
chartOptionTwo.value = {
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: '70%',
top: 'center',
icon: 'circle',
},
series: [
{
type: 'pie',
radius: ['60%', '70%'],
center: ['30%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold',
},
},
data: [
{ value: data.totalCores || '', name: '全部' },
{ value: data.usedCores || '', name: '已使用' },
{ value: data.freeCores || '', name: '未使用' },
],
itemStyle: {
color: function (params: any) {
return colorList.value[params.dataIndex];
},
},
},
],
};
};
// 获取CPU使用效率
const getefficiencyFun = (row: any) => {
let num: any = '0';
if (row.totalCores) {
if (row.freeCores === row.totalCores || !row.usedCores) {
num = '0';
} else if (row.usedCores === row.totalCores || !row.freeCores) {
num = '100%';
} else {
num = (row.usedCores / row.totalCores) * 100 + '%';
}
}
return num;
};
onMounted(async () => {
await getAppInfo();
});
@@ -203,8 +378,37 @@ onMounted(async () => {
.content {
height: 100%;
.table {
height: 100%;
padding: 10px;
background-color: #f2f3f5;
.chart-box {
width: 100%;
height: 350px;
display: flex;
align-items: center;
justify-content: space-between;
.chart-box-left,
.chart-box-right {
width: calc(50% - 5px);
height: 100%;
background-color: #fff;
}
}
.pege-table {
width: 100%;
height: calc(100% - 360px);
margin-top: 10px;
background-color: #fff;
}
}
.white {
background-color: #fff;
}
}

View File

@@ -469,3 +469,17 @@ export const base64ToStrFun = (str: any) => {
const base64Str = btoa(binaryStr);
return base64Str;
};
/**
* 判断字符串是否符合JSON标准
* @param str 输入字符串
* @returns 返回是否
*/
export const isJSONFun = (str: any) => {
try {
JSON.parse(str);
return true;
} catch {
return false;
}
};

View File

@@ -3,13 +3,14 @@ import { nextTick } from 'vue';
import { registerCustomNode } from './registerNode';
import { queryAllApplicationApi, queryApplicationConfigApi } from '@/api/system/application';
import { CommonStore } from '@/stores/common';
import { FileUtil } from '@/utils/file';
import { FileUtil, isJSONFun } from '@/utils/file';
// import startImg from '@/assets/imgs/dragFlow/dragIcon/start.svg';
import stencilStartImg from '@/assets/imgs/dragFlow/dragIcon/stencil_start.svg';
import stencilEndImg from '@/assets/imgs/dragFlow/dragIcon/stencil_end.svg';
import { FLOW_CREATE_ID } from './initGraph';
import { FLOW_NODE_TYPE_ENUM } from '@/utils/enum/flow';
import { v4 as uuidv4 } from 'uuid';
import { getUserId } from '@/utils/user';
const commonStore = CommonStore();
@@ -155,6 +156,19 @@ export const getNodeList = async (noload?: any) => {
const apps: any = res.data.data || [];
for (let i = 0; i < apps.length; i++) {
const paths = isJSONFun(apps[i].appPath)
? JSON.parse(apps[i].appPath as string)
: apps[i].appPath;
if (paths && paths instanceof Object) {
const keys = Object.keys(paths);
if (keys.includes(getUserId())) {
apps[i].appPath = paths[getUserId()];
} else {
apps[i].appPath = '';
}
}
if (apps[i].appStatus === 1) {
// if (!noload) {
// apps[i].nodeParamConfigName = await getAppConfigListFun(apps[i].uuid);

View File

@@ -7,6 +7,15 @@
:close-on-click-modal="false"
@close="closeFun"
>
<!-- <Dialog
v-model="visible"
:diaTitle="`${currentRow?.id ? '编辑' : '新增'}应用`"
:width="'25%'"
:height="'70%'"
@close="closeFun"
show-footer
> -->
<div class="content" v-loading="loading">
<TableForm ref="tableFormRef" :tableName="tableName" />
@@ -20,18 +29,21 @@
<el-button type="primary" @click="submitFun">确定</el-button>
</div>
</template>
<!-- </Dialog> -->
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue';
import { ref, watch, nextTick, onMounted } from 'vue';
import TableForm from '@/components/common/table/tableForm.vue';
import appNameBg from '@/views/task/appCenter/components/appNameBg.vue';
import html2canvas from 'html2canvas';
import { dataUploadAvatarApi } from '@/api/data/data';
import { delayTime } from '@/utils/common';
import { cloneDeep } from 'lodash-es';
import Dialog from '@/components/common/dialog/index.vue';
const props = defineProps(['currentAppInfo', 'tableName']);
const emit = defineEmits(['cancel', 'submit']);
@@ -98,22 +110,35 @@ const updatePngFun = async (name: any) => {
}
};
watch(
() => props.currentAppInfo,
(newVal) => {
if (newVal) {
currentRow.value = cloneDeep(newVal);
nextTick(() => {
if (currentRow.value?.id) {
tableFormRef.value.setFormDataFun(currentRow.value);
} else {
tableFormRef.value.resetFun();
}
});
}
},
{ immediate: true }
);
// watch(
// () => props.currentAppInfo,
// (newVal) => {
// if (newVal) {
// currentRow.value = cloneDeep(newVal);
// nextTick(() => {
// if (currentRow.value?.id) {
// tableFormRef.value.setFormDataFun(currentRow.value);
// } else {
// tableFormRef.value.resetFun();
// }
// });
// }
// },
// { immediate: true }
// );
onMounted(() => {
if (props.currentAppInfo) {
currentRow.value = cloneDeep(props.currentAppInfo);
nextTick(() => {
if (currentRow.value?.id) {
tableFormRef.value.setFormDataFun(currentRow.value);
} else {
tableFormRef.value.resetFun();
}
});
}
});
</script>
<style lang="scss" scoped>

View File

@@ -100,6 +100,7 @@ import appNameBg from '@/views/task/appCenter/components/appNameBg.vue';
import { getDictionaryDataApi } from '@/api/system/systemData';
import { getUserId } from '@/utils/user';
import appUseChart from './components/appUseChart.vue';
import { cloneDeep } from 'lodash-es';
const env = import.meta.env;
const baseTableRef = ref();
@@ -164,12 +165,35 @@ const setTableDataFun = async (data: any) => {
for (let i = 0; i < res?.data?.data?.length; i++) {
res.data.data[i].appStatus = res?.data?.data[i].appStatus.toString();
res.data.data[i].appType = res?.data?.data[i].appType.toString();
res.data.data[i].userAppPath = cloneDeep(res.data.data[i].appPath);
const paths = isJSONFun(res.data.data[i].appPath)
? JSON.parse(res.data.data[i].appPath as string)
: res.data.data[i].appPath;
if (paths && paths instanceof Object) {
const keys = Object.keys(paths);
if (keys.includes(getUserId())) {
res.data.data[i].appPath = paths[getUserId()];
} else {
res.data.data[i].appPath = '';
}
}
}
}
return res;
};
const isJSONFun = (str: any) => {
try {
JSON.parse(str);
return true;
} catch {
return false;
}
};
const currentAppInfo = ref<any>({});
// 打开编辑应用弹窗
const editAppInfoFun = (row?: any) => {
@@ -177,7 +201,7 @@ const editAppInfoFun = (row?: any) => {
// if (uuid) {
if (row) {
currentAppInfo.value = row;
currentAppInfo.value = cloneDeep(row);
} else {
currentAppInfo.value = {};
}
@@ -234,16 +258,24 @@ const deleteAppFun = async (row: any) => {
};
const updateAppInfoFun = async (data: any) => {
console.log(data, 'datadatadatadata');
const uuid =
data.appType === 1 || data.appType === '1' ? localStorage.getItem('USER_UUID') : '--';
detailVisible.value = false;
if (uuid) {
if (data?.id) {
let paths: any = {};
if (isJSONFun(currentAppInfo.value.userAppPath)) {
paths = JSON.parse(currentAppInfo.value.userAppPath);
}
paths[getUserId()] = data.appPath || '';
const res: any = await updateApplicationApi({
appName: data.appName,
appType: Number(data.appType),
appPath: data.appPath,
appPath: JSON.stringify(paths) || '',
appImage: data.appImage.toString(),
appStatus: Number(data.appStatus),
appVersion: data.appVersion,
@@ -256,11 +288,15 @@ const updateAppInfoFun = async (data: any) => {
if (res && res.code === 200) {
}
} else {
const path = JSON.stringify({
[getUserId()]: data.appPath,
});
const param = {
appName: data.appName,
appType: Number(data.appType),
appStatus: Number(data.appStatus),
appPath: data.appPath || '',
appPath: path || '',
appImage: data?.appImage ? data.appImage.toString() : '',
appVersion: data.appVersion || '',
appVendor: data.appVendor || '',

View File

@@ -353,7 +353,7 @@ import { getDictionaryDataApi } from '@/api/system/systemData';
import { getUserData, getUserId, getUserTenantId } from '@/utils/user';
import reportResult from './runPagecomponent/reportResult.vue';
import dayjs from 'dayjs';
import { base64ToStrFun } from '@/utils/file';
import { base64ToStrFun, isJSONFun } from '@/utils/file';
const props = defineProps({
runInfo: {
@@ -734,6 +734,21 @@ const getAppInfo = async (uuid: any) => {
try {
const res: any = await queryAllApplicationApi(param);
if (res && res.code === 200) {
for (let i = 0; i < res.data.data.length; i++) {
const paths = isJSONFun(res.data.data[i].appPath)
? JSON.parse(res.data.data[i].appPath as string)
: res.data.data[i].appPath;
if (paths && paths instanceof Object) {
const keys = Object.keys(paths);
if (keys.includes(getUserId())) {
res.data.data[i].appPath = paths[getUserId()];
} else {
res.data.data[i].appPath = '';
}
}
}
const appPath = res.data.data.find((item: any) => {
return item.uuid === uuid;
})?.appPath;
@@ -906,18 +921,21 @@ const getNodeFileIdAndNames = async () => {
fileIds.push(list[i].id.toString());
fileNames.push(list[i].originalName);
}
if (flowNodeParamData.value.nodeExeCommand) {
taskCmdParam[flowNodeParamData.value.nodeExeCommand] = fileNames.join(',');
// = fileNames.map((item: any) => {
// return { [flowNodeParamData.value.nodeExeCommand]: item };
// });
} else {
taskCmdParam = {};
}
// if (flowNodeParamData.value.nodeExeCommand) {
// taskCmdParam[flowNodeParamData.value.nodeExeCommand] = './' + fileNames.join(',');
// // = fileNames.map((item: any) => {
// // return { [flowNodeParamData.value.nodeExeCommand]: item };
// // });
// } else {
// taskCmdParam = {};
// }
if (Object.keys(flowNodeData.value.userParams).includes('--export')) {
taskCmdParam['--export'] = flowNodeData.value.userParams['--export'];
}
if (Object.keys(flowNodeData.value.userParams).includes('--import')) {
taskCmdParam['--import'] = './' + fileNames.join(',');
}
return {
fileIds,
fileNames,