feat: support for HotPlugNICs from Kubevirt (#582)

* refactor: rename hotplug volume
* feat: add hotplug NIC
* feat: add hot unplug
* refactor: rename NIC
* feat: get latest status
* feat: disable not ready options
* feat: filter out system networks
* refactor: update wordings

---------

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
This commit is contained in:
Yiya Chen
2025-11-11 11:43:46 +08:00
committed by GitHub
parent 6735826e15
commit f9bff21e84
10 changed files with 471 additions and 115 deletions
@@ -1,6 +1,5 @@
<script>
import InfoBox from '@shell/components/InfoBox';
import { NETWORK_ATTACHMENT } from '@shell/config/types';
import { sortBy } from '@shell/utils/sort';
import { clone } from '@shell/utils/object';
@@ -14,6 +13,13 @@ export default {
components: { InfoBox, Base },
props: {
vm: {
type: Object,
default: () => {
return {};
}
},
mode: {
type: String,
default: 'create'
@@ -32,10 +38,15 @@ export default {
}
},
async fetch() {
await this.fetchHotunplugData();
},
data() {
return {
rows: this.addKeyId(clone(this.value)),
nameIdx: 1
nameIdx: 1,
rows: this.addKeyId(clone(this.value)),
hotunpluggableNics: new Set(),
};
},
@@ -64,15 +75,57 @@ export default {
return out;
},
canCheckHotunplug() {
return !!this.vm?.actions?.findHotunpluggableNics;
},
vmState() {
return this.vm?.stateDisplay;
}
},
watch: {
value(neu) {
this.rows = neu;
this.rows = this.mergeHotplugData(clone(neu));
},
vmState(newState, oldState) {
if (newState !== oldState) {
this.fetchHotunplugData();
}
}
},
methods: {
async fetchHotunplugData() {
if (!this.canCheckHotunplug) {
this.rows = this.mergeHotplugData(clone(this.value));
return;
}
try {
const resp = await this.vm.doAction('findHotunpluggableNics');
this.hotunpluggableNics = new Set(resp?.interfaces || []);
} catch (e) {
// eslint-disable-next-line no-console
console.error('Failed to fetch hot-unpluggable NICs:', e);
this.hotunpluggableNics = new Set();
}
this.rows = this.mergeHotplugData(clone(this.value));
},
mergeHotplugData(networks) {
return (networks || []).map((network) => ({
...network,
isHotunpluggable: this.hotunpluggableNics.has(network.name),
rowKeyId: network.rowKeyId || randomStr(10)
}));
},
add(type) {
const name = this.generateName();
@@ -118,7 +171,11 @@ export default {
update() {
this.$emit('update:value', this.rows);
}
},
unplugNIC(network) {
this.vm.unplugNIC(network.name);
},
}
};
</script>
@@ -129,17 +186,28 @@ export default {
v-for="(row, i) in rows"
:key="i"
>
<button
v-if="!isView"
type="button"
class="role-link remove-vol"
@click="remove(row)"
>
<i class="icon icon-x" />
</button>
<h3> {{ t('harvester.virtualMachine.network.title') }} </h3>
<div class="box-title mb-10">
<h3>
{{ t('harvester.virtualMachine.network.title') }}
</h3>
<button
v-if="!isView"
type="button"
class="role-link btn btn-sm remove"
@click="remove(row)"
>
<i class="icon icon-x" />
</button>
<button
v-if="vm.hotplugNicFeatureEnabled && row.isHotunpluggable && isView"
type="button"
class="role-link btn btn-sm remove"
:disabled="!canCheckHotunplug"
@click="unplugNIC(row)"
>
{{ t('harvester.virtualMachine.hotUnplug.detachNIC.actionLabel') }}
</button>
</div>
<Base
v-model:value="rows[i]"
:rows="rows"
@@ -162,16 +230,13 @@ export default {
</template>
<style lang='scss' scoped>
.infoBox{
position: relative;
}
.box-title{
display: flex;
justify-content: space-between;
align-items: center;
.remove-vol {
position: absolute;
top: 10px;
right: 16px;
padding:0px;
max-height: 28px;
min-height: 28px;
h3 {
margin-bottom: 0;
}
}
</style>