main
youys 5 days ago
commit 25be954fd7

@ -26,7 +26,7 @@
"core-js": "^3.8.3", "core-js": "^3.8.3",
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"echarts": "5.4.3", "echarts": "5.4.3",
"element-plus": "^2.4.1", "element-plus": "2.10.4",
"generate-avatar": "1.4.10", "generate-avatar": "1.4.10",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",

@ -2,23 +2,14 @@
<block-box :title="title"> <block-box :title="title">
<template #extra> <template #extra>
<el-radio-group v-model="tabActive" size="small" @change="handleTabChange"> <el-radio-group v-model="tabActive" size="small" @change="handleTabChange">
<el-radio-button <el-radio-button v-for="{ tab, key } in config" :label="tab" :value="key" />
v-for="{ tab, key } in config"
:label="tab"
:value="key"
/>
</el-radio-group> </el-radio-group>
</template> </template>
<echarts-plus <echarts-plus :options="getTopOptions(
:options=" currentConfig.find((item) => item.key === tabActive)?.data || [],
getTopOptions( )
currentConfig.find((item) => item.key === tabActive)?.data || [], " :onClick="handleClick" style="min-height: 250px; height: 100%" />
)
"
:onClick="handleClick"
style="min-height: 250px; height: 100%"
/>
</block-box> </block-box>
</template> </template>
@ -28,6 +19,7 @@ import { onMounted, ref } from 'vue';
import EchartsPlus from '@/components/Echarts-plus.vue'; import EchartsPlus from '@/components/Echarts-plus.vue';
import cardApi from '~/vgpu/api/card'; import cardApi from '~/vgpu/api/card';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { formatSmartPercentage } from '@/utils';
const props = defineProps({ const props = defineProps({
title: String, title: String,
@ -65,7 +57,7 @@ const getTopOptions = () => {
res += res +=
params[i].marker + params[i].marker +
params[i].seriesName + params[i].seriesName +
(+params[i].value).toFixed(0) + formatSmartPercentage(params[i].value) +
`${config.unit || '%'}<br/>`; `${config.unit || '%'}<br/>`;
} }
return res; return res;
@ -111,7 +103,6 @@ onMounted(async () => {
query: v.query, query: v.query,
}) })
.then((res) => { .then((res) => {
console.log(v.query, res, 'res')
currentConfig.value[i].data = res.data.map((item) => ({ currentConfig.value[i].data = res.data.map((item) => ({
name: item.metric[v.nameKey], name: item.metric[v.nameKey],
value: item.value, value: item.value,

@ -1,4 +1,4 @@
import { timeParse } from '@/utils'; import { timeParse, formatSmartPercentage } from '@/utils';
export default ({ percent, title, unit = '%' }) => { export default ({ percent, title, unit = '%' }) => {
const value = percent.toFixed(1); const value = percent.toFixed(1);
@ -232,7 +232,7 @@ export const getLineOptions = ({ data = [], unit = '%' }) => {
var res = params[0].name + '<br/>'; var res = params[0].name + '<br/>';
for (var i = 0; i < params.length; i++) { for (var i = 0; i < params.length; i++) {
res += res +=
params[i].marker + (+params[i].value).toFixed(0) + ` ${unit}<br/>`; params[i].marker + formatSmartPercentage(params[i].value) + ` ${unit}<br/>`;
} }
return res; return res;
}, },
@ -240,7 +240,7 @@ export const getLineOptions = ({ data = [], unit = '%' }) => {
grid: { grid: {
top: 7, // 上边距 top: 7, // 上边距
bottom: 20, // 下边距 bottom: 20, // 下边距
left: '7%', // 左边距 left: '10%', // 左边距
right: 10, // 右边距 right: 10, // 右边距
}, },
xAxis: { xAxis: {
@ -258,7 +258,7 @@ export const getLineOptions = ({ data = [], unit = '%' }) => {
series: [ series: [
{ {
data: data.map((item) => { data: data.map((item) => {
return item.value.toFixed(1); return item.value;
}), }),
type: 'line', type: 'line',
areaStyle: { areaStyle: {

@ -198,6 +198,7 @@ const columns = [
}, },
{ {
label: '使用模式', label: '使用模式',
width: 120,
value: 'mode', value: 'mode',
render: ({ mode, type }) => ( render: ({ mode, type }) => (
<el-tag disable-transitions> <el-tag disable-transitions>
@ -207,26 +208,26 @@ const columns = [
} }
]; ];
const cp = useInstantVector( // const cp = useInstantVector(
[ // [
{ // {
label: 'vGPU超配', // label: 'vGPU',
count: '0', // count: '0',
query: `avg(sum(hami_vgpu_count{node=~"$node"}) by (instance))`, // query: `avg(sum(hami_vgpu_count{node=~"$node"}) by (instance))`,
}, // },
{ // {
label: '算力超配', // label: '',
count: '0', // count: '0',
query: `avg(sum(hami_vcore_scaling{node=~"$node"}) by (instance))`, // query: `avg(sum(hami_vcore_scaling{node=~"$node"}) by (instance))`,
}, // },
{ // {
label: '显存超配', // label: '',
count: '1.5', // count: '1.5',
query: `avg(sum(hami_vmemory_scaling{node=~"$node"}) by (instance))`, // query: `avg(sum(hami_vmemory_scaling{node=~"$node"}) by (instance))`,
}, // },
], // ],
(query) => query.replaceAll('$node', props.detail.name), // (query) => query.replaceAll('$node', props.detail.name),
); // );
const gaugeConfig = useInstantVector( const gaugeConfig = useInstantVector(
[ [

@ -1,4 +1,4 @@
import { timeParse } from '@/utils'; import { timeParse, formatSmartPercentage } from '@/utils';
export const getRangeOptions = ({ core = [], memory = [] }) => { export const getRangeOptions = ({ core = [], memory = [] }) => {
return { return {
@ -17,7 +17,7 @@ export const getRangeOptions = ({ core = [], memory = [] }) => {
params[i].marker + params[i].marker +
params[i].seriesName + params[i].seriesName +
' : ' + ' : ' +
(+params[i].value).toFixed(0) + formatSmartPercentage(params[i].value) +
`%<br/>`; `%<br/>`;
} }
@ -27,7 +27,7 @@ export const getRangeOptions = ({ core = [], memory = [] }) => {
grid: { grid: {
top: 37, // 上边距 top: 37, // 上边距
bottom: 20, // 下边距 bottom: 20, // 下边距
left: '7%', // 左边距 left: '10%', // 左边距
right: 10, // 右边距 right: 10, // 右边距
}, },
xAxis: { xAxis: {

@ -66,6 +66,7 @@ const columns = [
}, },
{ {
title: '使用模式', title: '使用模式',
width: 120,
dataIndex: 'mode', dataIndex: 'mode',
render: ({ mode, type }) => ( render: ({ mode, type }) => (
<el-tag disable-transitions> <el-tag disable-transitions>
@ -79,6 +80,7 @@ const columns = [
}, },
{ {
title: '所属资源池', title: '所属资源池',
width: 100,
dataIndex: 'resourcePools', dataIndex: 'resourcePools',
render: ({ resourcePools }) => `${resourcePools.join('、')}`, render: ({ resourcePools }) => `${resourcePools.join('、')}`,
}, },
@ -97,6 +99,7 @@ const columns = [
}, },
{ {
title: '算力(已分配/总量)', title: '算力(已分配/总量)',
width: 120,
dataIndex: 'used', dataIndex: 'used',
render: ({ coreTotal, coreUsed, isExternal }) => ( render: ({ coreTotal, coreUsed, isExternal }) => (
<span> <span>
@ -107,6 +110,7 @@ const columns = [
{ {
title: '显存(已分配/总量)', title: '显存(已分配/总量)',
dataIndex: 'w', dataIndex: 'w',
width: 120,
render: ({ memoryTotal, memoryUsed, isExternal }) => ( render: ({ memoryTotal, memoryUsed, isExternal }) => (
<span> <span>
{isExternal ? '--' : roundToDecimal(memoryUsed / 1024, 1)}/ {isExternal ? '--' : roundToDecimal(memoryUsed / 1024, 1)}/

@ -106,7 +106,7 @@ export const rangeConfigInit = [
}, },
{ {
name: 'CPU', name: 'CPU',
query: `sum(hami_container_vcore_allocated) / sum(hami_core_size) * 100`, query: `sum(hami_core_used) / sum(hami_core_size) * 100`,
data: [], data: [],
type: 'line', type: 'line',
areaStyle: { areaStyle: {
@ -140,7 +140,7 @@ export const rangeConfigInit = [
}, },
{ {
name: '内存', name: '内存',
query: `sum(hami_container_vmemory_allocated) / sum(hami_memory_size) * 100`, query: `sum(hami_memory_used) / sum(hami_memory_size) * 100`,
data: [], data: [],
type: 'line', type: 'line',
areaStyle: { areaStyle: {

@ -1,4 +1,4 @@
import { timeParse } from '@/utils'; import { timeParse, formatSmartPercentage } from '@/utils';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import nodeApi from '~/vgpu/api/node'; import nodeApi from '~/vgpu/api/node';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
@ -323,16 +323,16 @@ export const getCardOptions = (list, chartWidth) => {
rich: { rich: {
cnt: { cnt: {
fontSize: 10, fontSize: 10,
color: '#999' color: '#999',
} },
} },
}, },
labelLayout: function (params) { labelLayout: function (params) {
const isLeft = params.labelRect.x < chartWidth / 2; const isLeft = params.labelRect.x < chartWidth / 2;
const points = params.labelLinePoints; const points = params.labelLinePoints;
points[2][0] = isLeft points[2][0] = isLeft
? params.labelRect.x ? params.labelRect.x
: params.labelRect.x + params.labelRect.width; : params.labelRect.x + params.labelRect.width;
return { return {
labelLinePoints: points, labelLinePoints: points,
}; };
@ -343,7 +343,6 @@ export const getCardOptions = (list, chartWidth) => {
})), })),
}, },
], ],
}; };
}; };
@ -385,7 +384,7 @@ export const getLineOptions = ({
grid: { grid: {
top: 37, // 上边距 top: 37, // 上边距
bottom: 20, // 下边距 bottom: 20, // 下边距
left: '7%', // 左边距 left: '10%', // 左边距
right: 10, // 右边距 right: 10, // 右边距
}, },
xAxis: { xAxis: {
@ -496,7 +495,7 @@ export const getRangeOptions = (data) => {
params[i].marker + params[i].marker +
params[i].seriesName + params[i].seriesName +
' : ' + ' : ' +
(+params[i].value).toFixed(0) + formatSmartPercentage(params[i].value) +
`%<br/>`; `%<br/>`;
} }
@ -506,7 +505,7 @@ export const getRangeOptions = (data) => {
grid: { grid: {
top: 37, // 上边距 top: 37, // 上边距
bottom: 20, // 下边距 bottom: 20, // 下边距
left: '7%', // 左边距 left: '10%', // 左边距
right: 10, // 右边距 right: 10, // 右边距
}, },
xAxis: { xAxis: {

@ -44,7 +44,7 @@
<Block v-for="{ title, dataSource } in rangeConfig" :title="title" :key="title"> <Block v-for="{ title, dataSource } in rangeConfig" :title="title" :key="title">
<template #extra> <template #extra>
<time-picker v-model="times" type="datetimerange" size="small" :key="`time-picker-${title}`" /> <time-picker v-model="times" type="datetimerange" size="small" />
</template> </template>
<echarts-plus :options="getRangeOptions(dataSource)" style="height: 250px" /> <echarts-plus :options="getRangeOptions(dataSource)" style="height: 250px" />
</Block> </Block>
@ -481,14 +481,14 @@ const fetchRangeData = () => {
} }
cardApi // cardApi
.getRangeVector({ // .getRangeVector({
...params, // ...params,
query: `sum({__name__=~"alert:.*:count"})`, // query: `sum({__name__=~"alert:.*:count"})`,
}) // })
.then((res) => { // .then((res) => {
alarmData.value = res.data[0].values; // alarmData.value = res.data[0].values;
}); // });
}; };

@ -92,21 +92,22 @@
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
gap: 12px; gap: 12px;
li { li {
height: 80px; // height: 80px;
flex-shrink: 0; flex-shrink: 0;
border-radius: 6px; border-radius: 6px;
background: #f5f7fa; background: #f5f7fa;
padding: 16px; padding: 14px;
display: flex; display: flex;
align-items: center;
//&:hover { //&:hover {
// color: var(--el-color-primary); // color: var(--el-color-primary);
// cursor: pointer; // cursor: pointer;
//} //}
.avatar { .avatar {
display: flex; display: flex;
width: 48px; width: 30px;
height: 48px; height: 30px;
padding: 14px; padding: 6px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
@ -117,16 +118,16 @@
rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 0%,
#fff 100% #fff 100%
); );
margin-right: 16px; margin-right: 8px;
} }
.count { .count {
font-family: Roboto; font-family: Roboto;
font-size: 20px; font-size: 14px;
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
line-height: 100%; /* 20px */ line-height: 100%; /* 20px */
margin-top: 10px; margin-top: 8px;
} }
} }
} }

@ -240,7 +240,7 @@ const gaugeConfig = useInstantVector(
percent: 0, percent: 0,
query: ``, query: ``,
totalQuery: ``, totalQuery: ``,
percentQuery: `avg(sum(hami_container_vcore_allocated{node=~"$node"}) by (instance) / sum(hami_core_size{node=~"$node"}) by (instance) * 100)`, percentQuery: `avg(sum(hami_core_used{node=~"$node"}) by (instance) / sum(hami_core_size{node=~"$node"}) by (instance) * 100)`,
total: 0, total: 0,
used: 0, used: 0,
unit: '核', unit: '核',
@ -250,7 +250,7 @@ const gaugeConfig = useInstantVector(
percent: 0, percent: 0,
query: ``, query: ``,
totalQuery: ``, totalQuery: ``,
percentQuery: `avg(sum(hami_container_vmemory_allocated{node=~"$node"}) by (instance) / sum(hami_memory_size{node=~"$node"}) by (instance) * 100)`, percentQuery: `avg(sum(hami_memory_used{node=~"$node"}) by (instance) / sum(hami_memory_size{node=~"$node"}) by (instance) * 100)`,
total: 0, total: 0,
used: 0, used: 0,
unit: 'GiB', unit: 'GiB',
@ -264,6 +264,7 @@ const detailColumns = [
{ {
label: '节点状态', label: '节点状态',
value: 'status', value: 'status',
width: 100,
render: ({ isSchedulable, isExternal }) => { render: ({ isSchedulable, isExternal }) => {
if (detail.value && detail.value.isSchedulable !== undefined) { if (detail.value && detail.value.isSchedulable !== undefined) {
return ( return (

@ -1,4 +1,8 @@
import { timeParse } from '@/utils'; import {
timeParse,
formatSmartPercentage,
getFirstNonEmptyArray,
} from '@/utils';
export const getRangeOptions = ({ export const getRangeOptions = ({
core = [], core = [],
@ -6,6 +10,8 @@ export const getRangeOptions = ({
cpu = [], cpu = [],
internal = [], internal = [],
}) => { }) => {
const xData = getFirstNonEmptyArray([core, memory, cpu, internal]);
return { return {
legend: { legend: {
// data: [], // data: [],
@ -22,7 +28,8 @@ export const getRangeOptions = ({
params[i].marker + params[i].marker +
params[i].seriesName + params[i].seriesName +
' : ' + ' : ' +
(+params[i].value).toFixed(0) + // (+params[i].value).toFixed(0) +
formatSmartPercentage(params[i].value) +
`%<br/>`; `%<br/>`;
} }
@ -32,12 +39,12 @@ export const getRangeOptions = ({
grid: { grid: {
top: 37, // 上边距 top: 37, // 上边距
bottom: 20, // 下边距 bottom: 20, // 下边距
left: '7%', // 左边距 left: '10%', // 左边距
right: 10, // 右边距 right: 10, // 右边距
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: core.map((item) => timeParse(+item.timestamp)), data: xData.map((item) => timeParse(+item.timestamp)),
axisLabel: { axisLabel: {
formatter: function (value) { formatter: function (value) {
return timeParse(value, 'HH:mm'); return timeParse(value, 'HH:mm');

@ -25,7 +25,7 @@
</div> </div>
</template> </template>
</div> </div>
<template #footer> <template v-if="nodeList && nodeList.length > 0" #footer>
<el-button @click="dialogVisible = false">取消</el-button> <el-button @click="dialogVisible = false">取消</el-button>
<el-button :loading="btnLoading" type="primary" @click="handleOk"></el-button> <el-button :loading="btnLoading" type="primary" @click="handleOk"></el-button>
</template> </template>
@ -128,6 +128,7 @@ const columns = [
}, },
{ {
title: '节点状态', title: '节点状态',
width: 100,
dataIndex: 'isSchedulable', dataIndex: 'isSchedulable',
render: ({ isSchedulable, isExternal }) => ( render: ({ isSchedulable, isExternal }) => (
<el-tag disable-transitions type={isExternal ? 'warning' : (isSchedulable ? 'success' : 'danger')}> <el-tag disable-transitions type={isExternal ? 'warning' : (isSchedulable ? 'success' : 'danger')}>
@ -173,6 +174,7 @@ const columns = [
}, },
{ {
title: '所属资源池', title: '所属资源池',
width: 100,
dataIndex: 'resourcePools', dataIndex: 'resourcePools',
render: ({ resourcePools }) => `${resourcePools.join('、')}`, render: ({ resourcePools }) => `${resourcePools.join('、')}`,
}, },

@ -1,6 +1,6 @@
<template> <template>
<back-header> <back-header>
资源池管理 > {{ route.query?.name || '' }} 资源池管理 > {{ route.query?.poolName || '' }}
</back-header> </back-header>
<table-plus v-loading="loading" :dataSource="list" :columns="columns" :rowAction="rowAction" :hasPagination="false" <table-plus v-loading="loading" :dataSource="list" :columns="columns" :rowAction="rowAction" :hasPagination="false"
style="margin-bottom: 15px; height: auto;" hideTag ref="table" static :hasActionBar="false"> style="margin-bottom: 15px; height: auto;" hideTag ref="table" static :hasActionBar="false">
@ -39,10 +39,6 @@ const data = computed(() => {
return result; return result;
}) })
watchEffect(() => {
console.log(data.value, 'data')
})
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
const res = await pollApi.getDetailNodeList({ pool_id: route.params.uid }) const res = await pollApi.getDetailNodeList({ pool_id: route.params.uid })
@ -68,6 +64,7 @@ const columns = [
}, },
{ {
title: '节点状态', title: '节点状态',
width: 100,
dataIndex: 'isSchedulable', dataIndex: 'isSchedulable',
render: ({ isSchedulable, isExternal }) => ( render: ({ isSchedulable, isExternal }) => (
<el-tag disable-transitions type={isExternal ? 'warning' : (isSchedulable ? 'success' : 'danger')}> <el-tag disable-transitions type={isExternal ? 'warning' : (isSchedulable ? 'success' : 'danger')}>

@ -65,10 +65,6 @@ import { getRangeOptions } from './getOptions';
const props = defineProps(['data']) const props = defineProps(['data'])
watchEffect(() => {
console.log(props.data, 'data2')
})
const end = new Date(); const end = new Date();
const start = new Date(); const start = new Date();
start.setTime(start.getTime() - 3600 * 1000); start.setTime(start.getTime() - 3600 * 1000);

@ -1,4 +1,8 @@
import { timeParse } from '@/utils'; import {
timeParse,
formatSmartPercentage,
getFirstNonEmptyArray,
} from '@/utils';
export const getRangeOptions = ({ export const getRangeOptions = ({
core = [], core = [],
@ -6,6 +10,8 @@ export const getRangeOptions = ({
cpu = [], cpu = [],
internal = [], internal = [],
}) => { }) => {
const xData = getFirstNonEmptyArray([core, memory, cpu, internal]);
return { return {
legend: { legend: {
// data: [], // data: [],
@ -22,7 +28,8 @@ export const getRangeOptions = ({
params[i].marker + params[i].marker +
params[i].seriesName + params[i].seriesName +
' : ' + ' : ' +
(+params[i].value).toFixed(0) + // (+params[i].value).toFixed(0) +
formatSmartPercentage(params[i].value) +
`%<br/>`; `%<br/>`;
} }
@ -32,12 +39,12 @@ export const getRangeOptions = ({
grid: { grid: {
top: 37, // 上边距 top: 37, // 上边距
bottom: 20, // 下边距 bottom: 20, // 下边距
left: '7%', // 左边距 left: '10%', // 左边距
right: 10, // 右边距 right: 10, // 右边距
}, },
xAxis: { xAxis: {
type: 'category', type: 'category',
data: core.map((item) => timeParse(+item.timestamp)), data: xData.map((item) => timeParse(+item.timestamp)),
axisLabel: { axisLabel: {
formatter: function (value) { formatter: function (value) {
return timeParse(value, 'HH:mm'); return timeParse(value, 'HH:mm');

@ -21,7 +21,7 @@
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<el-button @click="sendRouteChange(`/admin/vgpu/poll/admin/${poolId}?name=${poolName}`)" <el-button @click="sendRouteChange(`/admin/vgpu/poll/admin/${poolId}?poolName=${poolName}`)"
type="text">查看详情</el-button> type="text">查看详情</el-button>
<template v-if="index === 0 && currentPage === 1"> <template v-if="index === 0 && currentPage === 1">
<el-button @click="sendRouteChange(linkUrl, 'open')" type="text">配置</el-button> <el-button @click="sendRouteChange(linkUrl, 'open')" type="text">配置</el-button>
@ -137,10 +137,6 @@ const paginatedList = computed(() => {
return list.value.slice(start, end) return list.value.slice(start, end)
}) })
// watchEffect(()=>{
// console.log(currentPage.value, pageSize.value, 88)
// })
// //
const handleSizeChange = (val) => { const handleSizeChange = (val) => {
pageSize.value = val pageSize.value = val

@ -41,19 +41,21 @@
</div> </div>
</block-box> </block-box>
<block-box v-for="{ title, data } in lineConfig" :key="title" :title="title"> <template v-for="({ title, data }, index) in lineConfig">
<template #extra v-if="detail.type && detail.type.startsWith('NVIDIA')"> <block-box v-if="detail.deviceIds?.length || [2, 3].includes(index)" :key="title + index" :title="title">
<time-picker v-model="times" type="datetimerange" size="small" /> <template #extra v-if="detail.type && detail.type.startsWith('NVIDIA')">
</template> <time-picker v-model="times" type="datetimerange" size="small" />
<div style="height: 200px">
<template v-if="detail.type && !detail.type.startsWith('NVIDIA')">
<el-empty description="该设备厂商暂不支持任务维度监控" :image-size="60" />
</template> </template>
<template v-else> <div style="height: 200px">
<echarts-plus :options="getLineOptions({ data })" /> <template v-if="detail.type && !detail.type.startsWith('NVIDIA')">
</template> <el-empty description="该设备厂商暂不支持任务维度监控" :image-size="60" />
</div> </template>
</block-box> <template v-else>
<echarts-plus :options="getLineOptions({ data })" />
</template>
</div>
</block-box>
</template>
</template> </template>
<script setup lang="jsx"> <script setup lang="jsx">
@ -298,9 +300,9 @@ onMounted(async () => {
if (foundCard) { if (foundCard) {
detail.value.type = foundCard.type; detail.value.type = foundCard.type;
} }
if (!detail.value.deviceIds?.length) { // if (!detail.value.deviceIds?.length) {
lineConfig.value.splice(0, 2); // lineConfig.value.splice(0, 2);
} // }
// const start = new Date(); // const start = new Date();
// start.setTime(start.getTime() - 3600 * 1000 * 24 * 7); // start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);

@ -58,6 +58,7 @@ const columns = [
title: '任务状态', title: '任务状态',
dataIndex: 'status', dataIndex: 'status',
render: ({ status, deviceIds }) => { render: ({ status, deviceIds }) => {
if (!status) return '/';
const enums = { const enums = {
closed: { text: '已完成', color: '#999' }, closed: { text: '已完成', color: '#999' },
success: { text: '运行中', color: '#2563eb' }, success: { text: '运行中', color: '#2563eb' },
@ -109,22 +110,26 @@ const columns = [
}, },
{ {
title: '使用者角色', title: '使用者角色',
dataIndex: 'nataskTypeme', width: 100,
render: ({ taskType }) => taskType === 'big_model' ? '大模型' : '实训',
},
{
title: '用户名',
dataIndex: 'role', dataIndex: 'role',
render: ({ role }) => role || '/', render: ({ role }) => role || '/',
}, },
{ {
title: '所属资源池', title: '用户名',
dataIndex: 'resourcePools', dataIndex: 'username',
render: ({ resourcePools }) => `${resourcePools.join('、')}`, render: ({ username }) => username || '/',
}, },
// {
// title: '',
// width: 100,
// dataIndex: 'resourcePools',
// render: ({ resourcePools }) => `${resourcePools?.length ? resourcePools.join('') : '/'}`,
// },
{ {
title: '所属节点', title: '所属节点',
dataIndex: 'nodeName', dataIndex: 'nodeName',
render: ({ nodeName }) => nodeName || '/',
}, },
{ {
title: 'CPU', title: 'CPU',
@ -138,6 +143,7 @@ const columns = [
}, },
{ {
title: '分配 vGPU', title: '分配 vGPU',
width: 100,
dataIndex: 'deviceIds', dataIndex: 'deviceIds',
render: ({ deviceIds }) => { render: ({ deviceIds }) => {
return ( return (
@ -178,6 +184,7 @@ const columns = [
{ {
title: '任务创建时间', title: '任务创建时间',
width: 140,
dataIndex: 'createTime', dataIndex: 'createTime',
render: ({ createTime }) => timeParse(createTime), render: ({ createTime }) => timeParse(createTime),
}, },

@ -63,22 +63,22 @@ const topConfig = [
title: '任务资源申请 Top5', title: '任务资源申请 Top5',
key: 'apply', key: 'apply',
config: [ config: [
{ // {
tab: 'CPU', // tab: 'CPU',
key: 'cpu', // key: 'cpu',
data: [], // data: [],
nameKey: 'container_pod_uuid', // nameKey: 'container_pod_uuid',
unit: '核', // unit: '',
query: 'topk(5, sum by(container_pod_uuid) (hami_container_vcore_allocated))', // query: 'topk(5, sum by(container_pod_uuid) (hami_container_vcore_allocated))',
}, // },
{ // {
tab: '内存', // tab: '',
key: 'internal', // key: 'internal',
data: [], // data: [],
unit: 'GiB', // unit: 'GiB',
nameKey: 'container_pod_uuid', // nameKey: 'container_pod_uuid',
query: 'topk(5, sum by(container_pod_uuid) (hami_container_vmemory_allocated))', // query: 'topk(5, sum by(container_pod_uuid) (hami_container_vmemory_allocated))',
}, // },
{ {
tab: '算力', tab: '算力',
key: 'core', key: 'core',

@ -1,6 +1,5 @@
<template> <template>
<el-date-picker <el-date-picker
ref="pickerRef"
v-model="value" v-model="value"
:type="type" :type="type"
range-separator="至" range-separator="至"
@ -8,36 +7,24 @@
end-placeholder="结束时间" end-placeholder="结束时间"
unlink-panels unlink-panels
:shortcuts="type.includes('range') && shortcuts" :shortcuts="type.includes('range') && shortcuts"
:teleported="false"
:append-to-body="false"
class="date-picker" class="date-picker"
:disabled-date="disabledDate" :disabled-date="disabledDate"
@visible-change="onVisibleChange"
v-bind="$attrs" v-bind="$attrs"
/> ></el-date-picker>
</template> </template>
<script setup lang="jsx"> <script setup lang="jsx">
import { computed, defineProps, defineEmits, ref } from 'vue'; import { computed, defineProps, defineEmits } from 'vue';
import { ElDatePicker } from 'element-plus';
// props emits
const props = defineProps({ const props = defineProps({
modelValue: {}, modelValue: {},
type: { type: String, default: 'date' }, type: { type: String, default: 'date' },
parse: { type: Function, default: (times) => times }, parse: { type: Function, default: (times) => times },
}); });
const emits = defineEmits(['update:modelValue']);
// ref
const pickerRef = ref();
const visible = ref(false);
// const emits = defineEmits(['update:modelValue']);
const onVisibleChange = (val) => {
visible.value = val;
};
//
const value = computed({ const value = computed({
get() { get() {
return props.modelValue; return props.modelValue;
@ -47,44 +34,105 @@ const value = computed({
}, },
}); });
// const shortcuts = [
function genShortcut(text, hoursAgo) { {
return { text: '前 1 小时',
text,
value: () => { value: () => {
const end = new Date(); const end = new Date();
const start = new Date(end.getTime() - hoursAgo * 3600 * 1000); const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 1);
//
setTimeout(() => {
if (visible.value) {
pickerRef.value?.handleClose?.();
}
}, 0);
return [start, end]; return [start, end];
}, },
}; },
} {
text: '前 6 小时',
// value: () => {
const shortcuts = [ const end = new Date();
genShortcut('前 1 小时', 1), const start = new Date();
genShortcut('前 6 小时', 6), start.setTime(start.getTime() - 3600 * 1000 * 6);
genShortcut('前 12 小时', 12), return [start, end];
genShortcut('前 1 天', 24), },
genShortcut('前 2 天', 48), },
genShortcut('前 3 天', 72), {
genShortcut('前 1 周', 168), text: '前 12 小时',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 12);
return [start, end];
},
},
{
text: '前 1 天',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24);
return [start, end];
},
},
{
text: '前 2 天',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 2);
return [start, end];
},
},
{
text: '前 3 天',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
return [start, end];
},
},
{
text: '前 1 周',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
},
},
// {
// text: '',
// value: () => {
// const end = new Date();
// const start = new Date();
// start.setTime(start.getTime() - 3600 * 1000 * 24 * 14);
// return [start, end];
// },
// },
// {
// text: '',
// value: () => {
// const end = new Date();
// const start = new Date();
// start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
// return [start, end];
// },
// },
// {
// text: '',
// value: () => {
// const end = new Date();
// const start = new Date();
// start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
// return [start, end];
// },
// },
]; ];
//
const disabledDate = (time) => { const disabledDate = (time) => {
return time.getTime() >= Date.now(); return time.getTime() >= Date.now();
}; };
</script> </script>
<style scoped> <style>
.date-picker { .date-picker {
max-width: 450px; max-width: 450px;
} }

@ -1,5 +1,5 @@
<template> <template>
<div id="content" :style="{ paddingLeft: hasParentWindow || isHome ? 0 : '20px' }"> <div id="content" :class="hasParentWindow && 'impose'">
<!-- <transition name="fade-transform" mode="out-in"> --> <!-- <transition name="fade-transform" mode="out-in"> -->
<!-- <keep-alive> --> <!-- <keep-alive> -->
<router-view /> <router-view />
@ -26,7 +26,15 @@ const isHome = computed(() => route.fullPath === '/admin/home');
margin: 0; margin: 0;
flex: 1; flex: 1;
overflow: auto; overflow: auto;
height: 100%; height: 100%;
padding-left: 20px;
&.impose {
margin: 0 auto;
overflow: inherit;
max-width: 1200px;
padding-left: 0px;
}
} }
</style> </style>

@ -1,6 +1,7 @@
<template> <template>
<TopBar v-if="!hasParentWindow" /> <TopBar v-if="!hasParentWindow" />
<div class="page" :style="{ padding: hasParentWindow ? '20px 30px' : '76px 20px 20px 20px' }"> <div class="page"
:style="{ padding: hasParentWindow ? '20px 30px' : '76px 20px 20px 20px', height: hasParentWindow ? 'auto' : '100vh' }">
<sidebar v-if="!hasParentWindow && !isNoSidebar()" /> <sidebar v-if="!hasParentWindow && !isNoSidebar()" />
<app-main /> <app-main />
</div> </div>

@ -588,3 +588,37 @@ export function parseUrl(url) {
return { pathname, query }; return { pathname, query };
} }
export function formatSmartPercentage(value) {
if (value === 0) return '0';
// 整数直接返回
if (Number.isInteger(value)) return value.toString();
const str = value.toString();
const decimal = str.split('.')[1] || '';
// 统计前导0的个数
let leadingZeros = 0;
for (const char of decimal) {
if (char === '0') {
leadingZeros++;
} else {
break;
}
}
const keep = leadingZeros + 2; // 0 的个数 + 1 位有效数字 + 1 位补充位
const rounded = value.toFixed(keep);
return parseFloat(rounded).toString(); // 去除多余的 0 和小数点
}
export function getFirstNonEmptyArray(arrays, defaultValue = []) {
for (const arr of arrays) {
if (arr && arr.length > 0) {
return arr;
}
}
return defaultValue;
}

Loading…
Cancel
Save