mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2025-12-15 14:11:46 +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.SR_IOVGPU_DEVICE,
|
||||
HCI.VGPU_DEVICE,
|
||||
HCI.MIG_CONFIGURATION,
|
||||
HCI.USB_DEVICE,
|
||||
HCI.ADD_ONS,
|
||||
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({
|
||||
labelKey: 'harvester.usb.label',
|
||||
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
|
||||
retain: Retain
|
||||
scheduleType: Type
|
||||
profileCount: Profile Count
|
||||
maxFailure: Max Failure
|
||||
sourceVm: Source Virtual Machine
|
||||
vmSchedule: Virtual Machine Schedule
|
||||
@ -1664,6 +1665,12 @@ harvester:
|
||||
middle: here
|
||||
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:
|
||||
label: vGPU Devices
|
||||
noPermission: Please contact system administrator to add Harvester add-ons first.
|
||||
@ -1937,6 +1944,12 @@ typeLabel:
|
||||
other { vGPU Devices }
|
||||
}
|
||||
|
||||
devices.harvesterhci.io.migconfiguration: |-
|
||||
{count, plural,
|
||||
one { vGPU MIG Configuration }
|
||||
other { vGPU MIG Configurations }
|
||||
}
|
||||
|
||||
harvesterhci.io.secret: |-
|
||||
{count, plural,
|
||||
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',
|
||||
USB_DEVICE: 'devices.harvesterhci.io.usbdevice',
|
||||
USB_CLAIM: 'devices.harvesterhci.io.usbdeviceclaim',
|
||||
MIG_CONFIGURATION: 'devices.harvesterhci.io.migconfiguration',
|
||||
VLAN_CONFIG: 'network.harvesterhci.io.vlanconfig',
|
||||
VLAN_STATUS: 'network.harvesterhci.io.vlanstatus',
|
||||
ADD_ONS: 'harvesterhci.io.addon',
|
||||
@ -53,7 +54,7 @@ export const HCI = {
|
||||
LB: 'loadbalancer.harvesterhci.io.loadbalancer',
|
||||
IP_POOL: 'loadbalancer.harvesterhci.io.ippool',
|
||||
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';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user