@@ -3,7 +3,7 @@ | |||
<a class="{{if .PageIsAdminDashboard}}active{{end}} item" href="{{AppSubUrl}}/admin"> | |||
{{.i18n.Tr "admin.dashboard"}} | |||
</a> | |||
<a class="item item-first" href="javascript:void;"> | |||
<a class="item item-first" href="javascript:void(0);"> | |||
{{.i18n.Tr "admin.user_management"}} | |||
</a> | |||
<a class="{{if .PageIsAdminUsers}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/users"> | |||
@@ -27,7 +27,7 @@ | |||
<a class="{{if .PageIsAdminImages}}active{{end}} item" href="{{AppSubUrl}}/admin/images"> | |||
{{.i18n.Tr "explore.images"}} | |||
</a> | |||
<a class="item item-first" href="javascript:void;"> | |||
<a class="item item-first" href="javascript:void(0);"> | |||
{{.i18n.Tr "admin.resource_management"}} | |||
</a> | |||
<a class="{{if .PageIsAdminResourcesQueue}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/queue"> | |||
@@ -39,7 +39,7 @@ | |||
<a class="{{if .PageIsAdminResourcesScene}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/scene"> | |||
{{.i18n.Tr "admin.application_scenario"}} | |||
</a> | |||
<a class="item item-first" href="javascript:void;"> | |||
<a class="item item-first" href="javascript:void(0);"> | |||
{{.i18n.Tr "admin.system_configuration"}} | |||
</a> | |||
<a class="{{if .PageIsAdminMonitor}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/monitor"> | |||
@@ -0,0 +1,20 @@ | |||
import service from '../service'; | |||
// 查看资源池(队列) | |||
export const getQueueList = (params) => { | |||
return service({ | |||
url: '/admin/resources/queue', | |||
method: 'get', | |||
params, | |||
}); | |||
} | |||
// 编辑资源池(队列) | |||
export const setQueueList = (data) => { | |||
return service({ | |||
url: '/reward/point/account', | |||
method: 'get', | |||
params: {}, | |||
data, | |||
}); | |||
} |
@@ -1,27 +1,10 @@ | |||
<template> | |||
<div class="base-dlg"> | |||
<el-dialog | |||
:visible.sync="dialogShow" | |||
:title="title" | |||
:width="width" | |||
:fullscreen="fullscreen" | |||
:top="top" | |||
:modal="modal" | |||
:modal-append-to-body="modalAppendToBody" | |||
:append-to-body="appendToBody" | |||
:lock-scroll="lockScroll" | |||
:custom-class="customClass" | |||
:close-on-click-modal="closeOnClickModal" | |||
:close-on-press-escape="closeOnPressEscape" | |||
:show-close="showClose" | |||
:center="center" | |||
:destroy-on-close="destroyOnClose" | |||
:before-close="beforeClose" | |||
@open="open" | |||
@opened="opened" | |||
@close="close" | |||
@closed="closed" | |||
> | |||
<el-dialog :visible.sync="dialogShow" :title="title" :width="width" :fullscreen="fullscreen" :top="top" | |||
:modal="modal" :modal-append-to-body="modalAppendToBody" :append-to-body="appendToBody" :lock-scroll="lockScroll" | |||
:custom-class="customClass" :close-on-click-modal="closeOnClickModal" :close-on-press-escape="closeOnPressEscape" | |||
:show-close="showClose" :center="center" :destroy-on-close="destroyOnClose" :before-close="beforeClose" | |||
@open="open" @opened="opened" @close="close" @closed="closed"> | |||
<template v-slot:title> | |||
<slot name="title"></slot> | |||
</template> | |||
@@ -55,7 +38,7 @@ export default { | |||
center: { type: Boolean, default: false }, | |||
destroyOnClose: { type: Boolean, default: false }, | |||
}, | |||
data: function () { | |||
data() { | |||
return { | |||
dialogShow: false, | |||
}; | |||
@@ -66,16 +49,16 @@ export default { | |||
}, | |||
}, | |||
methods: { | |||
open: function () { | |||
open() { | |||
this.$emit("open"); | |||
}, | |||
opened: function () { | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close: function () { | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed: function () { | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
@@ -96,16 +79,19 @@ export default { | |||
font-weight: 500; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
.el-dialog__title { | |||
font-weight: 500; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
} | |||
.el-dialog__headerbtn { | |||
top: 15px; | |||
right: 15px; | |||
} | |||
} | |||
/deep/ .el-dialog__body { | |||
padding: 15px 15px; | |||
} | |||
@@ -9,7 +9,7 @@ | |||
<span>资源池(队列)名称</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="point" placeholder=""></el-input> | |||
<el-input v-model="dataInfo.name" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
@@ -17,7 +17,7 @@ | |||
<span>所属集群</span> | |||
</div> | |||
<div class="content"> | |||
<el-select :value="selectValue"></el-select> | |||
<el-select v-model="dataInfo.cluster"></el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
@@ -25,7 +25,7 @@ | |||
<span>智算中心</span> | |||
</div> | |||
<div class="content"> | |||
<el-select :value="selectValue"></el-select> | |||
<el-select v-model="dataInfo.computingCenter"></el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
@@ -33,7 +33,7 @@ | |||
<span>计算资源</span> | |||
</div> | |||
<div class="content"> | |||
<el-select :value="selectValue"></el-select> | |||
<el-select v-model="dataInfo.computingType"></el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
@@ -41,7 +41,7 @@ | |||
<span>卡类型</span> | |||
</div> | |||
<div class="content"> | |||
<el-select :value="selectValue"></el-select> | |||
<el-select v-model="dataInfo.cardType"></el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
@@ -49,14 +49,14 @@ | |||
<span>卡数</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="point" type="number" placeholder=""></el-input> | |||
<el-input v-model="dataInfo.cardNum" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row" style="margin-top: 10px"> | |||
<div class="title"><span>备注</span></div> | |||
<div class="content" style="width: 400px"> | |||
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 4 }" | |||
:placeholder="true ? '请输入充值操作备注' : '请输入扣减操作备注'" v-model="remark"> | |||
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 4 }" :placeholder="'请输入备注'" | |||
v-model="dataInfo.remark"> | |||
</el-input> | |||
</div> | |||
</div> | |||
@@ -73,8 +73,8 @@ | |||
</div> | |||
</template> | |||
<script> | |||
import BaseDialog from '~/components/BaseDialog.vue'; | |||
import { getQueueList } from '~/apis/modules/resources'; | |||
export default { | |||
name: "QueueDialog", | |||
@@ -87,13 +87,12 @@ export default { | |||
components: { | |||
BaseDialog | |||
}, | |||
data: function () { | |||
data() { | |||
return { | |||
dialogShow: false, | |||
dataInfo: {}, | |||
point: '', | |||
remark: '', | |||
selectValue: '', | |||
dataInfo: { | |||
}, | |||
}; | |||
}, | |||
watch: { | |||
@@ -102,32 +101,49 @@ export default { | |||
}, | |||
}, | |||
methods: { | |||
open: function () { | |||
resetDataInfo() { | |||
this.dataInfo = { | |||
id: '', | |||
name: '', | |||
cluster: '', | |||
computingCenter: '', | |||
computingType: '', | |||
cardType: '', | |||
cardNum: '', | |||
remark: '', | |||
} | |||
}, | |||
open() { | |||
this.resetDataInfo(); | |||
if (this.type === 'add') { | |||
// | |||
} else if (this.type === 'edit') { | |||
this.dataInfo = Object.assign(this.dataInfo, { ...this.data }); | |||
} | |||
console.log('open', this.type, this.data); | |||
this.$emit("open"); | |||
}, | |||
opened: function () { | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close: function () { | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed: function () { | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
confirm: function () { | |||
confirm() { | |||
}, | |||
cancel: function () { | |||
cancel() { | |||
this.dialogShow = false; | |||
this.$emit("update:visible", false); | |||
} | |||
}, | |||
mounted() { | |||
this.resetDataInfo(); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
@@ -0,0 +1,219 @@ | |||
<template> | |||
<div class="base-dlg"> | |||
<BaseDialog :visible.sync="dialogShow" :width="`750px`" :title="type === 'add' ? `新建算力资源应用场景` : '编辑算力资源应用场景'" | |||
@open="open" @opened="opened" @close="close" @closed="closed"> | |||
<div class="dlg-content"> | |||
<div class="form"> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>应用场景名称</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.name" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>任务类型</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.taskType"> | |||
<el-option v-for="item in taskTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>是否专属</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.isExclusive" @change="changeIsExclusive"> | |||
<el-option v-for="item in isExclusiveList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row" v-if="dataInfo.isExclusive === '1'"> | |||
<div class="title required"> | |||
<span>专属组织</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.exclusiveOrg" placeholder="多个组织名之间用英文分号隔开"></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>资源池队列</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.queue" @change="changeQueue"> | |||
<el-option v-for="item in queueList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>资源规格</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.specs" multiple collapse-tags> | |||
<el-option v-for="item in specsList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row" style="margin-top: 20px"> | |||
<div class="title"></div> | |||
<div class="content"> | |||
<el-button type="primary" class="btn confirm-btn" @click="confirm">确定</el-button> | |||
<el-button class="btn" @click="cancel">取消</el-button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</BaseDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import BaseDialog from '~/components/BaseDialog.vue'; | |||
import { getQueueList } from '~/apis/modules/resources'; | |||
export default { | |||
name: "QueueDialog", | |||
props: { | |||
visible: { type: Boolean, default: false }, | |||
title: { type: String, default: '' }, | |||
type: { type: String, defalut: 'add' }, | |||
data: { type: Object, default: () => ({}) }, | |||
}, | |||
components: { | |||
BaseDialog | |||
}, | |||
data() { | |||
return { | |||
dialogShow: false, | |||
dataInfo: {}, | |||
taskTypeList: [{ k: '1', v: '调试任务' }, { k: '2', v: '训练任务' }, { k: '3', v: '评测任务' }, { k: '4', v: '推理任务' }], | |||
isExclusiveList: [{ k: '', v: '否' }, { k: '1', v: '是' }], | |||
queueList: [{ k: '1', v: '资源池1' }, { k: '2', v: '资源池2' }], | |||
specsList: [{ k: '1', v: 'spec1' }, { k: '2', v: 'spec2' }, { k: '3', v: 'spec3' }, { k: '4', v: 'spec4' }, { k: '5', v: 'spec5' }, { k: '6', v: 'spec6' }], | |||
}; | |||
}, | |||
watch: { | |||
visible: function (val) { | |||
this.dialogShow = val; | |||
}, | |||
}, | |||
methods: { | |||
resetDataInfo() { | |||
this.dataInfo = { | |||
id: '', | |||
name: '', | |||
taskType: '', | |||
isExclusive: '', | |||
exclusiveOrg: '', | |||
queue: '', | |||
specs: ['2', '3'], | |||
} | |||
}, | |||
changeIsExclusive() { | |||
this.dataInfo.exclusiveOrg = ''; | |||
}, | |||
changeQueue() { | |||
}, | |||
open() { | |||
this.resetDataInfo(); | |||
if (this.type === 'add') { | |||
// | |||
} else if (this.type === 'edit') { | |||
this.dataInfo = Object.assign(this.dataInfo, { ...this.data }); | |||
} | |||
console.log('open', this.type, this.data); | |||
this.$emit("open"); | |||
}, | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
confirm() { | |||
console.log(this.dataInfo); | |||
}, | |||
cancel() { | |||
this.dialogShow = false; | |||
this.$emit("update:visible", false); | |||
} | |||
}, | |||
mounted() { | |||
this.resetDataInfo(); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.dlg-content { | |||
margin: 20px 0 25px 0; | |||
display: flex; | |||
justify-content: center; | |||
.form { | |||
width: 600px; | |||
.form-row { | |||
display: flex; | |||
min-height: 42px; | |||
margin-bottom: 4px; | |||
.title { | |||
width: 160px; | |||
display: flex; | |||
justify-content: flex-end; | |||
align-items: center; | |||
margin-right: 20px; | |||
color: rgb(136, 136, 136); | |||
font-size: 14px; | |||
&.required { | |||
span { | |||
position: relative; | |||
} | |||
span::after { | |||
position: absolute; | |||
right: -10px; | |||
top: -2px; | |||
vertical-align: top; | |||
content: '*'; | |||
color: #db2828; | |||
} | |||
} | |||
} | |||
.content { | |||
width: 300px; | |||
display: flex; | |||
align-items: center; | |||
/deep/ .el-select { | |||
width: 100%; | |||
} | |||
} | |||
} | |||
} | |||
.btn { | |||
color: rgb(2, 0, 4); | |||
background-color: rgb(194, 199, 204); | |||
border-color: rgb(194, 199, 204); | |||
&.confirm-btn { | |||
color: #fff; | |||
background-color: rgb(56, 158, 13); | |||
border-color: rgb(56, 158, 13); | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,213 @@ | |||
<template> | |||
<div class="base-dlg"> | |||
<BaseDialog :visible.sync="dialogShow" :width="`700px`" :title="`新增资源规格和单价信息`" @open="open" @opened="opened" | |||
@close="close" @closed="closed"> | |||
<div class="dlg-content"> | |||
<div class="form"> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>资源池(队列)</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.queue"></el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>XPU数</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.xpu" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>CPU数</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.cpu" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>内存(GB)</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.mem" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>共享内存(GB)</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.shareMem" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>单价</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.price" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>状态</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.status"></el-select> | |||
</div> | |||
</div> | |||
<div class="form-row" style="margin-top: 20px"> | |||
<div class="title"></div> | |||
<div class="content"> | |||
<el-button type="primary" class="btn confirm-btn" @click="confirm">确定</el-button> | |||
<el-button class="btn" @click="cancel">取消</el-button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</BaseDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import BaseDialog from '~/components/BaseDialog.vue'; | |||
import { getQueueList } from '~/apis/modules/resources'; | |||
export default { | |||
name: "QueueDialog", | |||
props: { | |||
visible: { type: Boolean, default: false }, | |||
title: { type: String, default: '' }, | |||
type: { type: String, defalut: 'add' }, | |||
data: { type: Object, default: () => ({}) }, | |||
}, | |||
components: { | |||
BaseDialog | |||
}, | |||
data() { | |||
return { | |||
dialogShow: false, | |||
dataInfo: { | |||
}, | |||
}; | |||
}, | |||
watch: { | |||
visible: function (val) { | |||
this.dialogShow = val; | |||
}, | |||
}, | |||
methods: { | |||
resetDataInfo() { | |||
this.dataInfo = { | |||
queue: '', | |||
queueList: [], | |||
xpu: '', | |||
cpu: '', | |||
mem: '', | |||
shareMem: '', | |||
price: '', | |||
status: '', | |||
statusList: [], | |||
} | |||
}, | |||
open() { | |||
this.resetDataInfo(); | |||
if (this.type === 'add') { | |||
// | |||
} else if (this.type === 'edit') { | |||
this.dataInfo = Object.assign(this.dataInfo, { ...this.data }); | |||
} | |||
console.log('open', this.type, this.data); | |||
this.$emit("open"); | |||
}, | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
confirm() { | |||
}, | |||
cancel() { | |||
this.dialogShow = false; | |||
this.$emit("update:visible", false); | |||
} | |||
}, | |||
mounted() { | |||
this.resetDataInfo(); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.dlg-content { | |||
margin: 20px 0 25px 0; | |||
display: flex; | |||
justify-content: center; | |||
.form { | |||
width: 600px; | |||
.form-row { | |||
display: flex; | |||
min-height: 42px; | |||
margin-bottom: 4px; | |||
.title { | |||
width: 160px; | |||
display: flex; | |||
justify-content: flex-end; | |||
align-items: center; | |||
margin-right: 20px; | |||
color: rgb(136, 136, 136); | |||
font-size: 14px; | |||
&.required { | |||
span { | |||
position: relative; | |||
} | |||
span::after { | |||
position: absolute; | |||
right: -10px; | |||
top: -2px; | |||
vertical-align: top; | |||
content: '*'; | |||
color: #db2828; | |||
} | |||
} | |||
} | |||
.content { | |||
width: 300px; | |||
display: flex; | |||
align-items: center; | |||
/deep/ .el-select { | |||
width: 100%; | |||
} | |||
} | |||
} | |||
} | |||
.btn { | |||
color: rgb(2, 0, 4); | |||
background-color: rgb(194, 199, 204); | |||
border-color: rgb(194, 199, 204); | |||
&.confirm-btn { | |||
color: #fff; | |||
background-color: rgb(56, 158, 13); | |||
border-color: rgb(56, 158, 13); | |||
} | |||
} | |||
} | |||
</style> |
@@ -3,63 +3,52 @@ | |||
<div class="title"><span>资源池(队列)</span></div> | |||
<div class="tools-bar"> | |||
<div> | |||
<el-select class="select" size="medium" :value="value1"> | |||
<!-- <el-option v-for="item in sceneList" :key="item.k" :label="item.v" :value="item.k" /> --> | |||
<el-select class="select" size="medium" v-model="selCluster" @change="selectChange"> | |||
<el-option v-for="item in clusterList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" :value="value1"> | |||
<!-- <el-option v-for="item in pointActions" :key="item.k" :label="item.v" :value="item.k" /> --> | |||
<el-select class="select" size="medium" v-model="selComputingCenter" @change="selectChange"> | |||
<el-option v-for="item in computingCenterList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selComputingType" @change="selectChange"> | |||
<el-option v-for="item in computingTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selCardType" @change="selectChange"> | |||
<el-option v-for="item in cardTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" :value="value1"></el-select> | |||
<el-select class="select" size="medium" :value="value1"></el-select> | |||
</div> | |||
<div> | |||
<el-button size="medium" icon="el-icon-refresh" @click="showDialog('edit', {x: 1, y: 2, z: 3})">同步智算网络</el-button> | |||
<el-button size="medium" icon="el-icon-refresh" @click="syncComputerNetwork">同步智算网络</el-button> | |||
<el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')">新增资源池</el-button> | |||
</div> | |||
</div> | |||
<div class="table-container"> | |||
<div> | |||
<el-table border :data="tableData" style="width: 100%" v-loading="loading" stripe> | |||
<el-table-column prop="index" label="ID" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="userName" label="资源池(队列)名称" align="center" header-align="center"> | |||
<template #default="scope"> | |||
<a :href="`/${scope.row.userName}`">{{ scope.row.userName }}</a> | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="date" label="所属集群" align="center" header-align="center"> | |||
<el-table-column prop="id" label="ID" align="center" header-align="center" width="100"></el-table-column> | |||
<el-table-column prop="name" label="资源池(队列)名称" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="cluster" label="所属集群" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="computingCenterId" label="智算中心ID" align="center" header-align="center"> | |||
</el-table-column> | |||
<el-table-column prop="scene" label="智算中心ID" align="center" header-align="center"> | |||
</el-table-column> | |||
<el-table-column prop="pointAction" label="智算中心" align="left" header-align="center"> | |||
</el-table-column> | |||
<el-table-column prop="remark" label="计算资源" align="left" header-align="center" min-width="200"> | |||
<template #default="scope"> | |||
<span v-html="scope.row.remark"></span> | |||
<el-table-column prop="computingCenter" label="智算中心" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="computingType" label="计算资源" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="cardType" label="卡类型" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="cardNum" label="卡数 " align="center" header-align="center"> | |||
<template slot-scope="scope"> | |||
{{ scope.row.cardNum }} | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="operator" label="卡类型" align="center" header-align="center"> | |||
</el-table-column> | |||
<el-table-column prop="amount" label="卡数 " align="center" header-align="center"> | |||
<template #default="scope"> | |||
{{ scope.row.amount }} | |||
<el-table-column prop="updateTime" label="最后更新时间" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="remark" label="备注" align="left" header-align="center"> | |||
<template slot-scope="scope"> | |||
{{ scope.row.remark }} | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="blance" label="最后更新时间" align="center" header-align="center"> | |||
<template #default="scope"> | |||
{{ scope.row.blance }} | |||
<el-table-column label="操作" align="center" header-align="center" width="80"> | |||
<template slot-scope="scope"> | |||
<span class="op-btn" @click="showDialog('edit', scope.row)">修改</span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="blance" label="备注" align="center" header-align="center"> | |||
<template #default="scope"> | |||
{{ scope.row.blance }} | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="blance" label="操作" align="center" header-align="center"> | |||
<template #default="scope"> | |||
{{ scope.row.blance }} | |||
</template> | |||
</el-table-column> | |||
<template #empty> | |||
<template slot="empty"> | |||
<span style="font-size: 12px">{{ | |||
loading ? '加载中...' : '暂无数据' | |||
}}</span> | |||
@@ -77,17 +66,27 @@ | |||
</div> | |||
</div> | |||
</div> | |||
<QueueDialog :visible.sync="queueDialogShow" :type="queueDialogType" :data="queueDialogData" @confirm="queueDialogConfirm"></QueueDialog> | |||
<QueueDialog :visible.sync="queueDialogShow" :type="queueDialogType" :data="queueDialogData" | |||
@confirm="queueDialogConfirm"></QueueDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import QueueDialog from '../components/QueueDialog.vue'; | |||
import { getQueueList } from '~/apis/modules/resources'; | |||
export default { | |||
data() { | |||
return { | |||
value1: '1', | |||
searchVal: '', | |||
selCluster: '', | |||
clusterList: [{ k: '', v: '全部集群' }, { k: '1', v: '启智集群' }, { k: '2', v: '智算联盟集群' }], | |||
selComputingCenter: '', | |||
computingCenterList: [{ k: '', v: '全部智算中心' }, { k: '1', v: '云脑I' }, { k: '2', v: '云脑II' }], | |||
selComputingType: '', | |||
computingTypeList: [{ k: '', v: '全部计算资源' }, { k: 'cpu/gpu', v: 'CPU/GPU' }, { k: 'npu', v: 'NPU' }], | |||
selCardType: '', | |||
cardTypeList: [{ k: '', v: '全部卡类型' }, { k: 't4', v: 'T4' }, { k: 'a100', v: 'A100' }, { k: 'v100', v: 'V100' }, { k: 'ascend910', v: 'Ascend910' }], | |||
syncLoading: false, | |||
loading: false, | |||
tableData: [], | |||
pageInfo: { | |||
@@ -101,34 +100,69 @@ export default { | |||
queueDialogData: {}, | |||
}; | |||
}, | |||
components: { | |||
QueueDialog | |||
}, | |||
components: { QueueDialog }, | |||
methods: { | |||
getTableData: function () { | |||
this.tableData = new Array(20).fill(0).map((_, index) => { | |||
return { | |||
index: index, | |||
}; | |||
}) | |||
getTableData() { | |||
const params = { | |||
cluster: this.selCluster, | |||
computingCenter: this.selComputingCenter, | |||
computingType: this.selComputingType, | |||
cardType: this.selCardType, | |||
curpage: this.pageInfo.curpage, | |||
pagesize: this.pageInfo.pageSize, | |||
}; | |||
console.log('params', params); | |||
this.loading = true; | |||
getQueueList(params).then(res => { | |||
this.loading = false; | |||
// const data = res.data. | |||
const data = new Array(20).fill(0).map((_, index) => { | |||
return { | |||
index: index, | |||
id: `id-${index}-${Math.random().toFixed(2)}`, | |||
name: `name-${index}-${Math.random().toFixed(2)}`, | |||
cluster: `cluster-${index}-${Math.random().toFixed(2)}`, | |||
computingCenterId: `computingCenterId-${index}-${Math.random().toFixed(2)}`, | |||
computingCenter: `computingCenter-${index}-${Math.random().toFixed(2)}`, | |||
computingType: `computingType-${index}-${Math.random().toFixed(2)}`, | |||
cardType: `cardType-${index}-${Math.random().toFixed(2)}`, | |||
cardNum: `cardNum-${index}-${Math.random().toFixed(2)}`, | |||
updateTime: `updateTime-${index}-${Math.random().toFixed(2)}`, | |||
remark: `remark-${index}-${Math.random().toFixed(2)}`, | |||
}; | |||
}); | |||
this.tableData = data; | |||
this.pageInfo.total = 99; | |||
}).catch(err => { | |||
console.log(err); | |||
this.loading = false; | |||
}); | |||
}, | |||
syncComputerNetwork() { | |||
// | |||
}, | |||
selectChange() { | |||
this.pageInfo.curpage = 1; | |||
this.getTableData(); | |||
}, | |||
currentChange: function (val) { | |||
currentChange(val) { | |||
this.pageInfo.curpage = val; | |||
// this.getTableData(); | |||
this.getTableData(); | |||
}, | |||
showDialog(type, data) { | |||
this.queueDialogType = type; | |||
this.queueDialogData = data || {}; | |||
this.queueDialogData = data ? { ...data } : {}; | |||
this.queueDialogShow = true; | |||
}, | |||
queueDialogConfirm() { | |||
this.queueDialogShow = false; | |||
this.getTableData(); | |||
} | |||
}, | |||
mounted: function () { | |||
mounted() { | |||
this.getTableData(); | |||
}, | |||
beforeDestroy: function () { | |||
beforeDestroy() { | |||
}, | |||
}; | |||
</script> | |||
@@ -1,21 +1,79 @@ | |||
<template> | |||
<div class="xxx"> | |||
scene | |||
<div> | |||
<div class="title"><span>算力资源应用场景管理</span></div> | |||
<div class="tools-bar"> | |||
<div> | |||
<el-select class="select" size="medium" v-model="selTaskType" @change="selectChange"> | |||
<el-option v-for="item in taskTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selIsExclusive" @change="selectChange"> | |||
<el-option v-for="item in isExclusiveList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selQueue" @change="selectChange"> | |||
<el-option v-for="item in queueList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
<div> | |||
<el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')">新增应用场景</el-button> | |||
</div> | |||
</div> | |||
<div class="table-container"> | |||
<div> | |||
<el-table border :data="tableData" style="width: 100%" v-loading="loading" stripe> | |||
<el-table-column prop="id" label="ID" align="center" header-align="center" width="100"></el-table-column> | |||
<el-table-column prop="name" label="应用场景名称" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="taskType" label="任务类型" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="isExclusive" label="是否专属" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="exclusiveOrg" label="专属组织" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="queue" label="资源池(队列)" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="specs" label="资源规格" align="left" header-align="center"> | |||
<template slot-scope="scope"> | |||
{{ scope.row.specs }} | |||
</template> | |||
</el-table-column> | |||
<el-table-column label="操作" align="center" header-align="center" width="100"> | |||
<template slot-scope="scope"> | |||
<span class="op-btn" @click="showDialog('edit', scope.row)">修改</span> | |||
<span class="op-btn" style="color:red" @click="deleteRow(scope.row)">删除</span> | |||
</template> | |||
</el-table-column> | |||
<template slot="empty"> | |||
<span style="font-size: 12px">{{ | |||
loading ? '加载中...' : '暂无数据' | |||
}}</span> | |||
</template> | |||
</el-table> | |||
</div> | |||
<div class="__r_p_pagination"> | |||
<div style="margin-top: 2rem"> | |||
<div class="center"> | |||
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<SceneDialog :visible.sync="sceneDialogShow" :type="sceneDialogType" :data="sceneDialogData" | |||
@confirm="sceneDialogConfirm"></SceneDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import SceneDialog from '../components/SceneDialog.vue'; | |||
import { getQueueList } from '~/apis/modules/resources'; | |||
export default { | |||
data() { | |||
return { | |||
selTaskType: '', | |||
taskTypeList: [{ k: '', v: '全部任务类型' }, { k: '1', v: '调试任务' }, { k: '2', v: '训练任务' }, { k: '3', v: '评测任务' }, { k: '4', v: '推理任务' }], | |||
selIsExclusive: '', | |||
isExclusiveList: [{ k: '', v: '全部专属和通用' }, { k: '1', v: '专属' }, { k: '2', v: '通用' }], | |||
selQueue: '', | |||
queueList: [{ k: '', v: '全部资源池队列' }, { k: '1', v: '资源池1' }, { k: '2', v: '资源池2' }], | |||
loading: false, | |||
summaryInfo: { | |||
available: 0, | |||
gain: 0, | |||
used: 0, | |||
}, | |||
tabIndex: 0, | |||
tableData: [], | |||
pageInfo: { | |||
curpage: 1, | |||
@@ -23,21 +81,145 @@ export default { | |||
pageSizes: [10], | |||
total: 0, | |||
}, | |||
eventSource: null, | |||
sceneDialogShow: false, | |||
sceneDialogType: 'add', | |||
sceneDialogData: {}, | |||
}; | |||
}, | |||
components: {}, | |||
methods: { | |||
getTableData: function () { | |||
components: { SceneDialog }, | |||
methods: { | |||
getTableData() { | |||
const params = { | |||
taskType: this.selTaskType, | |||
isExclusive: this.selIsExclusive, | |||
queue: this.selQueue, | |||
curpage: this.pageInfo.curpage, | |||
pagesize: this.pageInfo.pageSize, | |||
}; | |||
console.log('params', params); | |||
this.loading = true; | |||
getQueueList(params).then(res => { | |||
this.loading = false; | |||
// const data = res.data. | |||
const data = new Array(20).fill(0).map((_, index) => { | |||
return { | |||
index: index, | |||
id: `id-${index}-${Math.random().toFixed(2)}`, | |||
name: `name-${index}-${Math.random().toFixed(2)}`, | |||
taskType: `taskType-${index}-${Math.random().toFixed(2)}`, | |||
isExclusive: `isExclusive-${index}-${Math.random().toFixed(2)}`, | |||
exclusiveOrg: `exclusiveOrg-${index}-${Math.random().toFixed(2)}`, | |||
queue: `queue-${index}-${Math.random().toFixed(2)}`, | |||
specs: `spec-${index}-${Math.random().toFixed(2)}`, | |||
}; | |||
}); | |||
this.tableData = data; | |||
this.pageInfo.total = 99; | |||
}).catch(err => { | |||
console.log(err); | |||
this.loading = false; | |||
}); | |||
}, | |||
selectChange() { | |||
this.pageInfo.curpage = 1; | |||
this.getTableData(); | |||
}, | |||
currentChange(val) { | |||
this.pageInfo.curpage = val; | |||
this.getTableData(); | |||
}, | |||
deleteRow(row) { | |||
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { | |||
confirmButtonText: '确定', | |||
cancelButtonText: '取消', | |||
type: 'warning', | |||
lockScroll: false, | |||
}).then(() => { | |||
this.$message({ | |||
type: 'success', | |||
message: '删除成功!' | |||
}); | |||
}).catch(() => { | |||
this.$message({ | |||
type: 'info', | |||
message: '已取消删除' | |||
}); | |||
}); | |||
}, | |||
showDialog(type, data) { | |||
this.sceneDialogType = type; | |||
this.sceneDialogData = data ? { ...data } : {}; | |||
this.sceneDialogShow = true; | |||
}, | |||
sceneDialogConfirm() { | |||
this.sceneDialogShow = false; | |||
this.getTableData(); | |||
} | |||
}, | |||
mounted: function () { | |||
mounted() { | |||
this.getTableData(); | |||
}, | |||
beforeDestroy: function () { | |||
beforeDestroy() { | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.title { | |||
height: 30px; | |||
display: flex; | |||
align-items: center; | |||
margin-bottom: 5px; | |||
span { | |||
font-weight: 700; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
} | |||
} | |||
.tools-bar { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin-bottom: 10px; | |||
.select { | |||
margin-right: 10px; | |||
/deep/ .el-input__inner { | |||
border-radius: 0; | |||
} | |||
} | |||
} | |||
.table-container { | |||
margin-bottom: 16px; | |||
/deep/ .el-table__header { | |||
th { | |||
background: rgb(245, 245, 246); | |||
font-size: 12px; | |||
color: rgb(36, 36, 36); | |||
} | |||
} | |||
/deep/ .el-table__body { | |||
td { | |||
font-size: 12px; | |||
} | |||
} | |||
.op-btn { | |||
cursor: pointer; | |||
font-size: 12px; | |||
color: rgb(25, 103, 252); | |||
margin: 0 5px; | |||
} | |||
} | |||
.center { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
</style> |
@@ -1,21 +1,81 @@ | |||
<template> | |||
<div class="xxx"> | |||
specification | |||
<div> | |||
<div class="title"><span>资源规格单价管理</span></div> | |||
<div class="tools-bar"> | |||
<div> | |||
<el-select class="select" size="medium" v-model="selQueue" @change="selectChange"> | |||
<el-option v-for="item in queueList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selStatus" @change="selectChange"> | |||
<el-option v-for="item in statusList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
<div> | |||
<!-- syncComputerNetwork --> | |||
<el-button size="medium" icon="el-icon-refresh" @click="confirmOperate">同步智算网络</el-button> | |||
<el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')">新增资源规格</el-button> | |||
</div> | |||
</div> | |||
<div class="table-container"> | |||
<div> | |||
<el-table border :data="tableData" style="width: 100%" v-loading="loading" stripe> | |||
<el-table-column prop="id" label="ID" align="center" header-align="center" width="100"></el-table-column> | |||
<el-table-column prop="name" label="资源规格" align="left" header-align="center"></el-table-column> | |||
<el-table-column prop="queue" label="资源池(队列)" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="specID" label="智算网络资源规格ID" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="cardNum" label="卡数 " align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="cpu" label="CPU " align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="mem" label="内存(GB) " align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="shareMem" label="共享内存(GB)" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="updateTime" label="最后更新时间" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="price" label="单价(积分/时)" align="center" header-align="center"></el-table-column> | |||
<el-table-column prop="status" label="状态" align="center" header-align="center"> | |||
<template slot-scope="scope"> | |||
{{ scope.row.status }} | |||
</template> | |||
</el-table-column> | |||
<el-table-column label="操作" align="center" header-align="center" width="100"> | |||
<template slot-scope="scope"> | |||
<span class="op-btn" @click="showDialog('edit', scope.row)">定价</span> | |||
<span class="op-btn" @click="showDialog('edit', scope.row)">上架</span> | |||
</template> | |||
</el-table-column> | |||
<template slot="empty"> | |||
<span style="font-size: 12px">{{ | |||
loading ? '加载中...' : '暂无数据' | |||
}}</span> | |||
</template> | |||
</el-table> | |||
</div> | |||
<div class="__r_p_pagination"> | |||
<div style="margin-top: 2rem"> | |||
<div class="center"> | |||
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<SpecificationDialog :visible.sync="specificationDialogShow" :type="specificationDialogType" | |||
:data="specificationDialogData" @confirm="specificationDialogConfirm"></SpecificationDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import SpecificationDialog from '../components/SpecificationDialog.vue'; | |||
import { getQueueList } from '~/apis/modules/resources'; | |||
export default { | |||
data() { | |||
return { | |||
selQueue: '', | |||
queueList: [{ k: '', v: '全部资源池' }, { k: '1', v: '资源池1' }, { k: '2', v: '资源池2' }], | |||
selStatus: '', | |||
statusList: [{ k: '', v: '全部状态' }, { k: '1', v: '待上架' }, { k: '2', v: '已上架' }, { k: '3', v: '已下架' }], | |||
syncLoading: false, | |||
loading: false, | |||
summaryInfo: { | |||
available: 0, | |||
gain: 0, | |||
used: 0, | |||
}, | |||
tabIndex: 0, | |||
tableData: [], | |||
pageInfo: { | |||
curpage: 1, | |||
@@ -23,15 +83,89 @@ export default { | |||
pageSizes: [10], | |||
total: 0, | |||
}, | |||
eventSource: null, | |||
specificationDialogShow: false, | |||
specificationDialogType: 'add', | |||
specificationDialogData: {}, | |||
}; | |||
}, | |||
components: {}, | |||
methods: { | |||
getTableData: function () { | |||
components: { SpecificationDialog }, | |||
methods: { | |||
getTableData() { | |||
const params = { | |||
queue: this.selQueue, | |||
status: this.selStatus, | |||
curpage: this.pageInfo.curpage, | |||
pagesize: this.pageInfo.pageSize, | |||
}; | |||
console.log('params', params); | |||
this.loading = true; | |||
getQueueList(params).then(res => { | |||
this.loading = false; | |||
// const data = res.data. | |||
const data = new Array(20).fill(0).map((_, index) => { | |||
return { | |||
index: index, | |||
id: `id-${index}-${Math.random().toFixed(2)}`, | |||
name: `name-${index}-${Math.random().toFixed(2)}`, | |||
queue: `queue-${index}-${Math.random().toFixed(2)}`, | |||
specID: `specID-${index}-${Math.random().toFixed(2)}`, | |||
cardNum: `cardNum-${index}-${Math.random().toFixed(2)}`, | |||
cpu: `cpu-${index}-${Math.random().toFixed(2)}`, | |||
mem: `mem-${index}-${Math.random().toFixed(2)}`, | |||
shareMem: `shareMem-${index}-${Math.random().toFixed(2)}`, | |||
updateTime: `updateTime-${index}-${Math.random().toFixed(2)}`, | |||
price: `price-${index}-${Math.random().toFixed(2)}`, | |||
status: `status-${index}-${Math.random().toFixed(2)}`, | |||
}; | |||
}); | |||
this.tableData = data; | |||
this.pageInfo.total = 99; | |||
}).catch(err => { | |||
console.log(err); | |||
this.loading = false; | |||
}); | |||
}, | |||
syncComputerNetwork() { | |||
// | |||
}, | |||
selectChange() { | |||
this.pageInfo.curpage = 1; | |||
this.getTableData(); | |||
}, | |||
currentChange(val) { | |||
this.pageInfo.curpage = val; | |||
this.getTableData(); | |||
}, | |||
showDialog(type, data) { | |||
this.specificationDialogType = type; | |||
this.specificationDialogData = data ? { ...data } : {}; | |||
this.specificationDialogShow = true; | |||
}, | |||
specificationDialogConfirm() { | |||
this.specificationDialogShow = false; | |||
this.getTableData(); | |||
}, | |||
confirmOperate() { | |||
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { | |||
confirmButtonText: '确定', | |||
cancelButtonText: '取消', | |||
type: 'warning', | |||
lockScroll: false, | |||
}).then(() => { | |||
this.$message({ | |||
type: 'success', | |||
message: '删除成功!' | |||
}); | |||
}).catch(() => { | |||
this.$message({ | |||
type: 'info', | |||
message: '已取消删除' | |||
}); | |||
}); | |||
} | |||
}, | |||
mounted: function () { | |||
this.getTableData(); | |||
}, | |||
beforeDestroy: function () { | |||
}, | |||
@@ -39,5 +173,61 @@ export default { | |||
</script> | |||
<style scoped lang="less"> | |||
.title { | |||
height: 30px; | |||
display: flex; | |||
align-items: center; | |||
margin-bottom: 5px; | |||
span { | |||
font-weight: 700; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
} | |||
} | |||
.tools-bar { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin-bottom: 10px; | |||
.select { | |||
margin-right: 10px; | |||
/deep/ .el-input__inner { | |||
border-radius: 0; | |||
} | |||
} | |||
} | |||
.table-container { | |||
margin-bottom: 16px; | |||
/deep/ .el-table__header { | |||
th { | |||
background: rgb(245, 245, 246); | |||
font-size: 12px; | |||
color: rgb(36, 36, 36); | |||
} | |||
} | |||
/deep/ .el-table__body { | |||
td { | |||
font-size: 12px; | |||
} | |||
} | |||
.op-btn { | |||
cursor: pointer; | |||
font-size: 12px; | |||
color: rgb(25, 103, 252); | |||
margin: 0 5px; | |||
} | |||
} | |||
.center { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
</style> |