blob: b5c2af591c2da4609e4f330e57f0e22f79d204b5 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
<template>
<div>
<a-button
type="primary"
@click="createVgpuProfile"
>
{{ $t('label.add.vgpu.profile') }}
</a-button>
<list-view
:tabLoading="tabLoading"
:columns="columns"
:items="items"
:columnKeys="columnKeys"
:selectedColumns="selectedColumnKeys"
ref="listview"
@update-selected-columns="updateSelectedColumns"
@refresh="this.fetchData"
>
<template #actionButtons="{ record }">
<a-space>
<!-- Edit Action -->
<a-tooltip :title="$t('label.edit')">
<a-button
type="primary"
size="small"
shape="circle"
@click="editVgpuProfile(record)"
>
<template #icon><edit-outlined /></template>
</a-button>
</a-tooltip>
<!-- Delete Action -->
<a-popconfirm
:title="$t('message.confirm.delete.vgpu.profile')"
@confirm="deleteVgpuProfile(record)"
okText="Yes"
cancelText="No"
>
<a-tooltip :title="$t('label.delete')">
<a-button
type="primary"
danger
size="small"
shape="circle"
>
<template #icon><delete-outlined /></template>
</a-button>
</a-tooltip>
</a-popconfirm>
</a-space>
</template>
</list-view>
<!-- Create/Update VGPU Profile Modal -->
<a-modal
:visible="showModal"
:title="isEditing ? $t('label.update.vgpu.profile') : $t('label.add.vgpu.profile')"
:closable="true"
:maskClosable="false"
:footer="null"
@cancel="closeModal"
centered
width="450px"
>
<a-spin
:spinning="actionLoading"
v-ctrl-enter="handleSubmit"
>
<a-form
ref="vgpuForm"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
>
<a-form-item
v-for="field in formFields"
:key="field.key"
:name="field.key"
:ref="field.key"
:label="field.label"
>
<!-- Input field -->
<a-input
v-if="field.type === 'input'"
v-model:value="form[field.key]"
:placeholder="field.placeholder"
:v-focus="field.key === 'name'"
/>
<!-- Number input field -->
<a-input-number
v-else-if="field.type === 'number'"
style="width: 100%"
v-model:value="form[field.key]"
:min="field.min"
:placeholder="field.placeholder"
/>
</a-form-item>
<div
:span="24"
class="action-button"
>
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button
type="primary"
ref="submit"
@click="handleSubmit"
>{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-spin>
</a-modal>
</div>
</template>
<script>
import { reactive, toRaw } from 'vue'
import { getAPI, postAPI } from '@/api'
import { genericCompare } from '@/utils/sort.js'
import ListView from '@/components/view/ListView'
import { EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
export default {
name: 'VgpuProfilesTab',
components: {
ListView,
EditOutlined,
DeleteOutlined
},
props: {
resource: {
type: Object,
required: true
},
loading: {
type: Boolean,
required: true
}
},
data () {
return {
tabLoading: false,
columnKeys: ['name', 'videoram', 'maxheads', 'resolution', 'maxvgpuperphysicalgpu'],
selectedColumnKeys: [],
columns: [],
cols: [],
items: [],
showModal: false,
isEditing: false,
selectedVgpuProfile: null,
actionLoading: false,
form: {},
rules: {},
formFields: [],
createApiParams: {},
updateApiParams: {}
}
},
computed: {
isAdmin () {
return this.$store.getters.userInfo.roletype === 'Admin'
}
},
created () {
this.fetchApiParams()
this.selectedColumnKeys = this.columnKeys
this.updateColumns()
this.fetchData()
},
methods: {
fetchApiParams () {
this.createApiParams = this.$getApiParams('createVgpuProfile') || {}
this.updateApiParams = this.$getApiParams('updateVgpuProfile') || {}
},
generateFormFields (isEdit = false) {
const apiParams = isEdit ? this.updateApiParams : this.createApiParams
const fields = []
const fieldOrder = ['name', 'description', 'videoram', 'maxheads', 'maxresolutionx', 'maxresolutiony', 'maxvgpuperphysicalgpu']
fieldOrder.forEach(paramName => {
if (paramName === 'gpucardid' && !isEdit) return // Skip gpucardid for create as it's auto-populated
const param = apiParams[paramName]
if (!param && !isEdit) return // For create, only include params that exist in API
const field = {
key: paramName,
label: this.$t(`label.${paramName}`),
required: param?.required || (paramName === 'name' || paramName === 'maxvgpuperphysicalgpu'),
placeholder: param?.description || this.$t(`label.${paramName}`),
type: this.getFieldType(paramName, param)
}
fields.push(field)
})
this.formFields = fields
},
getFieldType (paramName, param) {
const numberFields = ['maxvgpuperphysicalgpu']
const selectFields = []
if (numberFields.includes(paramName)) {
return 'number'
} else if (selectFields.includes(paramName)) {
return 'select'
} else {
return 'input'
}
},
initForm (isEdit = false, record = null) {
const formData = {
name: '',
description: '',
maxvgpuperphysicalgpu: 1
}
if (isEdit && record) {
formData.name = record.name || ''
formData.description = record.description || ''
formData.maxvgpuperphysicalgpu = record.maxvgpuperphysicalgpu || 1
formData.videoram = record.videoram || 0
formData.maxheads = record.maxheads || 0
formData.maxresolutionx = record.maxresolutionx || 0
formData.maxresolutiony = record.maxresolutiony || 0
}
this.form = reactive(formData)
const validationRules = {
name: [{ required: true, message: this.$t('message.error.required.input') }],
maxvgpuperphysicalgpu: [{ required: true, message: this.$t('message.error.required.input') }]
}
this.rules = reactive(validationRules)
},
createVgpuProfile () {
this.isEditing = false
this.selectedVgpuProfile = null
this.generateFormFields(false)
this.initForm(false)
this.showModal = true
},
editVgpuProfile (record) {
this.isEditing = true
this.selectedVgpuProfile = record
this.generateFormFields(true)
this.initForm(true, record)
this.showModal = true
},
closeModal () {
this.showModal = false
this.isEditing = false
this.selectedVgpuProfile = null
this.actionLoading = false
},
filterOption (input, option) {
return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
},
handleSubmit (e) {
if (e) {
e.preventDefault()
}
if (this.actionLoading) return
this.$refs.vgpuForm.validate().then(() => {
this.actionLoading = true
const formRaw = toRaw(this.form)
if (this.isEditing) {
this.updateVgpuProfile(formRaw)
} else {
this.createVgpuProfileSubmit(formRaw)
}
}).catch((error) => {
this.$message.error(error)
})
},
createVgpuProfileSubmit (formRaw) {
const params = {
name: formRaw.name,
gpucardid: this.resource.id,
maxvgpuperphysicalgpu: formRaw.maxvgpuperphysicalgpu
}
if (formRaw.description) {
params.description = formRaw.description
}
postAPI('createVgpuProfile', params).then(response => {
this.$message.success(this.$t('message.success.create.vgpu.profile'))
this.closeModal()
this.fetchData()
}).catch(error => {
console.error('Error creating vGPU profile:', error)
this.$notifyError(error)
}).finally(() => {
this.actionLoading = false
})
},
updateVgpuProfile (formRaw) {
const params = {
id: this.selectedVgpuProfile.id
}
// Add only fields that have values
if (formRaw.name) {
params.name = formRaw.name
}
if (formRaw.description) {
params.description = formRaw.description
}
if (formRaw.maxvgpuperphysicalgpu) {
params.maxvgpuperphysicalgpu = formRaw.maxvgpuperphysicalgpu
}
postAPI('updateVgpuProfile', params).then(() => {
this.$message.success(this.$t('message.success.update.vgpu.profile'))
this.closeModal()
this.fetchData()
}).catch(error => {
console.error('Error updating vGPU profile:', error)
this.$notifyError(error)
}).finally(() => {
this.actionLoading = false
})
},
deleteVgpuProfile (record) {
postAPI('deleteVgpuProfile', {
id: record.id
}).then(() => {
this.$message.success(this.$t('message.success.delete.vgpu.profile'))
this.fetchData()
}).catch(error => {
console.error('Error deleting vGPU profile:', error)
this.$notifyError(error)
})
},
fetchData () {
this.fetchVgpuProfiles()
},
updateSelectedColumns (key) {
if (this.selectedColumnKeys.includes(key)) {
this.selectedColumnKeys = this.selectedColumnKeys.filter(x => x !== key)
} else {
this.selectedColumnKeys.push(key)
}
this.updateColumns()
},
updateColumns () {
this.columns = []
for (var columnKey of this.columnKeys) {
if (!this.selectedColumnKeys.includes(columnKey)) continue
this.columns.push({
key: columnKey,
title: columnKey === 'name' ? this.$t('label.vgpu.profile') : this.$t('label.' + String(columnKey).toLowerCase()),
dataIndex: columnKey,
sorter: (a, b) => { return genericCompare(a[columnKey] || '', b[columnKey] || '') }
})
}
// Add actions column for admin users
if (this.isAdmin) {
this.columns.push({
key: 'vgpuActions',
title: this.$t('label.actions'),
width: 120,
fixed: 'right'
})
}
if (this.columns.length > 0) {
this.columns[this.columns.length - 1].customFilterDropdown = true
}
},
fetchVgpuProfiles () {
getAPI('listVgpuProfiles', {
gpucardid: this.resource.id
}).then(res => {
this.items = res?.listvgpuprofilesresponse?.vgpuprofile || []
})
}
}
}
</script>