mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-02-04 06:51:44 +00:00
feat: add vGPU MIGConfiguration page
Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
5d996c42dc
commit
ce5928108e
@ -457,6 +457,7 @@ export function init($plugin, store) {
|
|||||||
HCI.PCI_DEVICE,
|
HCI.PCI_DEVICE,
|
||||||
HCI.SR_IOVGPU_DEVICE,
|
HCI.SR_IOVGPU_DEVICE,
|
||||||
HCI.VGPU_DEVICE,
|
HCI.VGPU_DEVICE,
|
||||||
|
HCI.MIG_CONFIGURATION,
|
||||||
HCI.USB_DEVICE,
|
HCI.USB_DEVICE,
|
||||||
HCI.ADD_ONS,
|
HCI.ADD_ONS,
|
||||||
HCI.SECRET,
|
HCI.SECRET,
|
||||||
@ -849,6 +850,26 @@ export function init($plugin, store) {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
virtualType({
|
||||||
|
labelKey: 'harvester.migconfiguration.label',
|
||||||
|
group: 'advanced',
|
||||||
|
weight: 12,
|
||||||
|
name: HCI.MIG_CONFIGURATION,
|
||||||
|
namespaced: false,
|
||||||
|
route: {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
|
params: { resource: HCI.MIG_CONFIGURATION }
|
||||||
|
},
|
||||||
|
exact: false,
|
||||||
|
ifHaveType: HCI.MIG_CONFIGURATION,
|
||||||
|
});
|
||||||
|
|
||||||
|
configureType(HCI.MIG_CONFIGURATION, {
|
||||||
|
isCreatable: false,
|
||||||
|
hiddenNamespaceGroupButton: true,
|
||||||
|
canYaml: false,
|
||||||
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
labelKey: 'harvester.usb.label',
|
labelKey: 'harvester.usb.label',
|
||||||
group: 'advanced',
|
group: 'advanced',
|
||||||
|
|||||||
129
pkg/harvester/edit/devices.harvesterhci.io.migconfiguration.vue
Normal file
129
pkg/harvester/edit/devices.harvesterhci.io.migconfiguration.vue
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<script>
|
||||||
|
import Tabbed from '@shell/components/Tabbed';
|
||||||
|
import Tab from '@shell/components/Tabbed/Tab';
|
||||||
|
import CruResource from '@shell/components/CruResource';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
||||||
|
import LabelValue from '@shell/components/LabelValue';
|
||||||
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HarvesterEditMIGConfiguration',
|
||||||
|
|
||||||
|
// emits: ['update:value'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Tab,
|
||||||
|
Tabbed,
|
||||||
|
CruResource,
|
||||||
|
LabeledInput,
|
||||||
|
NameNsDescription,
|
||||||
|
LabelValue
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [CreateEditView],
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const { profileSpec } = this.value.spec;
|
||||||
|
|
||||||
|
return { profileSpec: profileSpec || [] };
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
if (this.registerBeforeHook) {
|
||||||
|
this.registerBeforeHook(this.updateBeforeSave);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
updateBeforeSave() {
|
||||||
|
// MIGConfiguration CRD don't have any namespace field,
|
||||||
|
// so we need to remove the namespace field before saving
|
||||||
|
delete this.value.metadata.namespace;
|
||||||
|
// enable the MIGConfiguration when saving
|
||||||
|
this.value.spec.enabled = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
labelTitle(profile) {
|
||||||
|
return `${ profile.name }`;
|
||||||
|
},
|
||||||
|
|
||||||
|
available(profile) {
|
||||||
|
const count = this.value.status?.profileStatus?.find((p) => p.id === profile.id)?.available;
|
||||||
|
|
||||||
|
return count || 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRequested(neu, profile) {
|
||||||
|
if (neu === null || neu === '') return;
|
||||||
|
const newValue = Number(neu);
|
||||||
|
|
||||||
|
if (newValue < 0) {
|
||||||
|
profile.requested = 0;
|
||||||
|
} else if (newValue > this.available(profile)) {
|
||||||
|
profile.requested = this.available(profile);
|
||||||
|
} else {
|
||||||
|
profile.requested = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CruResource
|
||||||
|
:done-route="doneRoute"
|
||||||
|
:resource="value"
|
||||||
|
:mode="mode"
|
||||||
|
:errors="errors"
|
||||||
|
:apply-hooks="applyHooks"
|
||||||
|
finish-button-mode="enable"
|
||||||
|
@finish="save"
|
||||||
|
@error="e=>errors=e"
|
||||||
|
>
|
||||||
|
<NameNsDescription
|
||||||
|
:value="value"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
<Tabbed
|
||||||
|
v-bind="$attrs"
|
||||||
|
class="mt-15"
|
||||||
|
:side-tabs="true"
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
name="Profile Spec"
|
||||||
|
:label="t('harvester.migconfiguration.profileSpec')"
|
||||||
|
:weight="1"
|
||||||
|
class="bordered-table"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(profile, index) in profileSpec"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<LabelValue
|
||||||
|
:value="labelTitle(profile)"
|
||||||
|
class="mb-10"
|
||||||
|
/>
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="profile.requested"
|
||||||
|
:min="0"
|
||||||
|
:max="available(profile)"
|
||||||
|
type="number"
|
||||||
|
class="mb-20"
|
||||||
|
:label="`${t('harvester.migconfiguration.requested')} (available : ${available(profile)})`"
|
||||||
|
@update:value="updateRequested($event, profile)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Tab>
|
||||||
|
</Tabbed>
|
||||||
|
</CruResource>
|
||||||
|
</template>
|
||||||
@ -272,6 +272,7 @@ harvester:
|
|||||||
cronExpression: Cron Expression
|
cronExpression: Cron Expression
|
||||||
retain: Retain
|
retain: Retain
|
||||||
scheduleType: Type
|
scheduleType: Type
|
||||||
|
profileCount: Profile Count
|
||||||
maxFailure: Max Failure
|
maxFailure: Max Failure
|
||||||
sourceVm: Source Virtual Machine
|
sourceVm: Source Virtual Machine
|
||||||
vmSchedule: Virtual Machine Schedule
|
vmSchedule: Virtual Machine Schedule
|
||||||
@ -1664,6 +1665,12 @@ harvester:
|
|||||||
middle: here
|
middle: here
|
||||||
suffix: to enable it to manage your SR-IOV GPU devices.
|
suffix: to enable it to manage your SR-IOV GPU devices.
|
||||||
|
|
||||||
|
migconfiguration:
|
||||||
|
label: vGPU MIG Configurations
|
||||||
|
infoBanner: To configure the MIG configuration, please disable it first and re-enable after editing the configuration.
|
||||||
|
profileSpec: Profile Specs
|
||||||
|
requested: Requested
|
||||||
|
|
||||||
vgpu:
|
vgpu:
|
||||||
label: vGPU Devices
|
label: vGPU Devices
|
||||||
noPermission: Please contact system administrator to add Harvester add-ons first.
|
noPermission: Please contact system administrator to add Harvester add-ons first.
|
||||||
@ -1937,6 +1944,12 @@ typeLabel:
|
|||||||
other { vGPU Devices }
|
other { vGPU Devices }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
devices.harvesterhci.io.migconfiguration: |-
|
||||||
|
{count, plural,
|
||||||
|
one { vGPU MIG Configuration }
|
||||||
|
other { vGPU MIG Configurations }
|
||||||
|
}
|
||||||
|
|
||||||
harvesterhci.io.secret: |-
|
harvesterhci.io.secret: |-
|
||||||
{count, plural,
|
{count, plural,
|
||||||
one { Secret }
|
one { Secret }
|
||||||
|
|||||||
105
pkg/harvester/list/devices.harvesterhci.io.migconfiguration.vue
Normal file
105
pkg/harvester/list/devices.harvesterhci.io.migconfiguration.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<script>
|
||||||
|
import { STATE, SIMPLE_NAME } from '@shell/config/table-headers';
|
||||||
|
import { allHash } from '@shell/utils/promise';
|
||||||
|
import Banner from '@components/Banner/Banner.vue';
|
||||||
|
import Loading from '@shell/components/Loading';
|
||||||
|
import ResourceTable from '@shell/components/ResourceTable';
|
||||||
|
import { HCI } from '../types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ListMIGConfigurations',
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Banner,
|
||||||
|
Loading,
|
||||||
|
ResourceTable,
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetch() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
this.schema = this.$store.getters[`${ inStore }/schemaFor`](HCI.MIG_CONFIGURATION);
|
||||||
|
|
||||||
|
if (this.hasSchema) {
|
||||||
|
try {
|
||||||
|
const hash = await allHash({ migconfigs: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.MIG_CONFIGURATION }) });
|
||||||
|
|
||||||
|
this.rows = hash.migconfigs;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
// const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
schema: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasSchema() {
|
||||||
|
return !!this.schema;
|
||||||
|
},
|
||||||
|
|
||||||
|
rowsData() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const rows = this.$store.getters[`${ inStore }/all`](HCI.MIG_CONFIGURATION) || [];
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
},
|
||||||
|
|
||||||
|
headers() {
|
||||||
|
const cols = [
|
||||||
|
STATE,
|
||||||
|
SIMPLE_NAME,
|
||||||
|
{
|
||||||
|
name: 'address',
|
||||||
|
label: 'Address',
|
||||||
|
value: 'spec.gpuAddress',
|
||||||
|
sort: ['spec.gpuAddress']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Profile Count',
|
||||||
|
label: 'Profile Count',
|
||||||
|
labelKey: 'harvester.tableHeaders.profileCount',
|
||||||
|
value: 'spec.profileSpec.length',
|
||||||
|
sort: ['spec.profileSpec.length'],
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: 'Status',
|
||||||
|
labelKey: 'tableHeaders.status',
|
||||||
|
sort: ['status.status'],
|
||||||
|
value: 'status.status',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return cols;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Loading v-if="$fetchState.pending" />
|
||||||
|
<div v-else-if="hasSchema">
|
||||||
|
<Banner
|
||||||
|
color="info"
|
||||||
|
:label="t('harvester.migconfiguration.infoBanner')"
|
||||||
|
/>
|
||||||
|
<ResourceTable
|
||||||
|
v-bind="$attrs"
|
||||||
|
:groupable="false"
|
||||||
|
:namespaced="false"
|
||||||
|
:headers="headers"
|
||||||
|
:schema="schema"
|
||||||
|
:rows="rowsData"
|
||||||
|
key-field="_key"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
100
pkg/harvester/models/devices.harvesterhci.io.migconfiguration.js
Normal file
100
pkg/harvester/models/devices.harvesterhci.io.migconfiguration.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
||||||
|
import { escapeHtml } from '@shell/utils/string';
|
||||||
|
import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
|
||||||
|
// import { NODE } from '@shell/config/types';
|
||||||
|
// import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
|
// import { HCI } from '../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing vGPU MIGConfiguration resource.
|
||||||
|
* @extends SteveModal
|
||||||
|
*/
|
||||||
|
export default class MIGCONFIGURATION extends SteveModel {
|
||||||
|
get _availableActions() {
|
||||||
|
let out = super._availableActions;
|
||||||
|
|
||||||
|
out = out.map((action) => {
|
||||||
|
if (action.action === 'goToEditYaml') {
|
||||||
|
return { ...action, enabled: true };
|
||||||
|
} else if (action.action === 'goToEdit') {
|
||||||
|
return { ...action, enabled: !this.spec.enabled };
|
||||||
|
} else {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
out.push(
|
||||||
|
{
|
||||||
|
action: 'enableConfig',
|
||||||
|
enabled: !this.isEnabled,
|
||||||
|
icon: 'icon icon-fw icon-dot',
|
||||||
|
label: 'Enable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: 'disableConfig',
|
||||||
|
enabled: this.isEnabled,
|
||||||
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
|
label: 'Disable',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canYaml() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get canDelete() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get actualState() {
|
||||||
|
return this.isEnabled ? 'Enabled' : 'Disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
get stateDisplay() {
|
||||||
|
return this.actualState;
|
||||||
|
}
|
||||||
|
|
||||||
|
get stateColor() {
|
||||||
|
const state = this.actualState;
|
||||||
|
|
||||||
|
return colorForState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isEnabled() {
|
||||||
|
return this.spec.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
async enableConfig() {
|
||||||
|
try {
|
||||||
|
this.spec.enabled = true;
|
||||||
|
await this.save();
|
||||||
|
} catch (err) {
|
||||||
|
this.$dispatch('growl/fromError', {
|
||||||
|
title: this.t('generic.notification.title.error', { name: escapeHtml(this.name) }),
|
||||||
|
err,
|
||||||
|
}, { root: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async disableConfig() {
|
||||||
|
const { enabled: currentEnabled } = this.spec;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.spec.enabled = false;
|
||||||
|
await this.save();
|
||||||
|
} catch (err) {
|
||||||
|
this.spec.enabled = currentEnabled;
|
||||||
|
this.$dispatch('growl/fromError', {
|
||||||
|
title: this.t('generic.notification.title.error', { name: escapeHtml(this.name) }),
|
||||||
|
err,
|
||||||
|
}, { root: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// cleanForSave(data, _forNew) {
|
||||||
|
// console.log("🚀 ~ MIGCONFIGURATION ~ cleanForSave ~ data:", data)
|
||||||
|
// return data;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@ -44,6 +44,7 @@ export const HCI = {
|
|||||||
SR_IOVGPU_DEVICE: 'devices.harvesterhci.io.sriovgpudevice',
|
SR_IOVGPU_DEVICE: 'devices.harvesterhci.io.sriovgpudevice',
|
||||||
USB_DEVICE: 'devices.harvesterhci.io.usbdevice',
|
USB_DEVICE: 'devices.harvesterhci.io.usbdevice',
|
||||||
USB_CLAIM: 'devices.harvesterhci.io.usbdeviceclaim',
|
USB_CLAIM: 'devices.harvesterhci.io.usbdeviceclaim',
|
||||||
|
MIG_CONFIGURATION: 'devices.harvesterhci.io.migconfiguration',
|
||||||
VLAN_CONFIG: 'network.harvesterhci.io.vlanconfig',
|
VLAN_CONFIG: 'network.harvesterhci.io.vlanconfig',
|
||||||
VLAN_STATUS: 'network.harvesterhci.io.vlanstatus',
|
VLAN_STATUS: 'network.harvesterhci.io.vlanstatus',
|
||||||
ADD_ONS: 'harvesterhci.io.addon',
|
ADD_ONS: 'harvesterhci.io.addon',
|
||||||
@ -53,7 +54,7 @@ export const HCI = {
|
|||||||
LB: 'loadbalancer.harvesterhci.io.loadbalancer',
|
LB: 'loadbalancer.harvesterhci.io.loadbalancer',
|
||||||
IP_POOL: 'loadbalancer.harvesterhci.io.ippool',
|
IP_POOL: 'loadbalancer.harvesterhci.io.ippool',
|
||||||
HARVESTER_CONFIG: 'rke-machine-config.cattle.io.harvesterconfig',
|
HARVESTER_CONFIG: 'rke-machine-config.cattle.io.harvesterconfig',
|
||||||
LVM_VOLUME_GROUP: 'harvesterhci.io.lvmvolumegroup'
|
LVM_VOLUME_GROUP: 'harvesterhci.io.lvmvolumegroup',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VOLUME_SNAPSHOT = 'snapshot.storage.k8s.io.volumesnapshot';
|
export const VOLUME_SNAPSHOT = 'snapshot.storage.k8s.io.volumesnapshot';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user