Add pkg/harvester components + shell portings - 1

Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
Francesco Torchia
2024-10-23 17:00:46 +02:00
parent f8408469f7
commit 4f2688f6ab
265 changed files with 42316 additions and 82 deletions
@@ -0,0 +1,283 @@
<script>
import HarvesterIpAddress from '../../../formatters/HarvesterIpAddress';
import VMConsoleBar from '../../../components/VMConsoleBar';
import LabelValue from '@shell/components/LabelValue';
import InputOrDisplay from '@shell/components/InputOrDisplay';
import { HCI } from '../../../types';
import CreateEditView from '@shell/mixins/create-edit-view';
const UNDEFINED = 'n/a';
export default {
name: 'VMDetailsBasics',
components: {
VMConsoleBar,
HarvesterIpAddress,
LabelValue,
InputOrDisplay
},
mixins: [CreateEditView],
props: {
value: {
type: Object,
required: true
},
vmi: {
type: Object,
required: true,
default: () => {
return {};
}
},
mode: {
type: String,
required: true,
},
},
computed: {
creationTimestamp() {
const date = new Date(this.value?.metadata?.creationTimestamp);
if (!date.getMonth) {
return UNDEFINED;
}
return `${ date.getMonth() + 1 }/${ date.getDate() }/${ date.getUTCFullYear() }`;
},
node() {
const node = this.vmi?.status?.nodeName || UNDEFINED;
return this.isDown ? this.t('harvester.virtualMachine.detail.details.down') : node;
},
hostname() {
const hostName = this.vmi?.spec?.hostname || this.vmi?.status?.guestOSInfo?.hostname || this.t('harvester.virtualMachine.detail.GuestAgentNotInstalled');
return this.isDown ? this.t('harvester.virtualMachine.detail.details.down') : hostName;
},
imageName() {
const imageList = this.$store.getters['harvester/all'](HCI.IMAGE) || [];
const image = imageList.find( (I) => {
return this.value.rootImageId === I.id;
});
return image?.spec?.displayName || 'N/A';
},
disks() {
const disks = this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
return disks.filter((disk) => {
return !!disk.bootOrder;
}).sort((a, b) => {
if (a.bootOrder < b.bootOrder) {
return -1;
}
return 1;
});
},
cdroms() {
const disks = this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
return disks.filter((disk) => {
return !!disk.cdrom;
});
},
flavor() {
const domain = this.value?.spec?.template?.spec?.domain;
return `${ domain.cpu?.cores } vCPU , ${ domain.resources?.limits?.memory } ${ this.t('harvester.virtualMachine.input.memory') }`;
},
kernelRelease() {
const kernelRelease = this.vmi?.status?.guestOSInfo?.kernelRelease || this.t('harvester.virtualMachine.detail.GuestAgentNotInstalled');
return this.isDown ? this.t('harvester.virtualMachine.detail.details.down') : kernelRelease;
},
operatingSystem() {
const operatingSystem = this.vmi?.status?.guestOSInfo?.prettyName || this.t('harvester.virtualMachine.detail.GuestAgentNotInstalled');
return this.isDown ? this.t('harvester.virtualMachine.detail.details.down') : operatingSystem;
},
isDown() {
return this.isEmpty(this.vmi);
},
machineType() {
return this.value?.spec?.template?.spec?.domain?.machine?.type || undefined;
}
},
methods: {
getDeviceType(o) {
if (o.disk) {
return 'Disk';
} else {
return 'CD-ROM';
}
},
isEmpty(o) {
return o !== undefined && Object.keys(o).length === 0;
}
}
};
</script>
<template>
<div>
<VMConsoleBar :resource="value" class="consoleBut" />
<div class="overview-basics">
<div class="row">
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.name')" :value="value.nameDisplay">
<template #value>
<div class="smart-row">
<div class="console">
{{ value.nameDisplay }}
</div>
</div>
</template>
</LabelValue>
</div>
<div class="col span-6">
<LabelValue :name="t('harvester.fields.image')" :value="imageName" />
</div>
</div>
<div class="row">
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.hostname')" :value="hostname">
<template #value>
<div>
{{ hostname }}
</div>
</template>
</LabelValue>
</div>
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.node')" :value="node">
<template #value>
<div>
{{ node }}
</div>
</template>
</LabelValue>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.ipAddress')">
<template #value>
<HarvesterIpAddress v-model="value.id" :row="value" />
</template>
</LabelValue>
</div>
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.created')" :value="creationTimestamp" />
</div>
</div>
<hr class="section-divider" />
<h2>{{ t('harvester.virtualMachine.detail.tabs.configurations') }}</h2>
<div class="row">
<div class="col span-6">
<InputOrDisplay :name="t('harvester.virtualMachine.detail.details.bootOrder')" :value="disks" :mode="mode">
<template #value>
<ul>
<li v-for="(disk) in disks" :key="disk.bootOrder">
{{ disk.bootOrder }}. {{ disk.name }} ({{ getDeviceType(disk) }})
</li>
</ul>
</template>
</InputOrDisplay>
</div>
<div class="col span-6">
<InputOrDisplay :name="t('harvester.virtualMachine.detail.details.CDROMs')" :value="cdroms" :mode="mode">
<template #value>
<div>
<ul v-if="cdroms.length > 0">
<li v-for="(rom) in cdroms" :key="rom.name">
{{ rom.name }}
</li>
</ul>
<span v-else>
{{ t("harvester.virtualMachine.detail.notAvailable") }}
</span>
</div>
</template>
</InputOrDisplay>
</div>
</div>
<div class="row">
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.operatingSystem')" :value="operatingSystem" />
</div>
<LabelValue :name="t('harvester.virtualMachine.detail.details.flavor')" :value="flavor" />
</div>
<div class="row">
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.kernelRelease')" :value="kernelRelease" />
</div>
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.input.MachineType')" :value="machineType" />
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.consoleBut {
display: flex;
justify-content: flex-end;
}
.overview-basics {
display: grid;
grid-template-columns: 100%;
grid-template-rows: auto;
grid-row-gap: 15px;
.badge-state {
padding: 2px 5px;
font-size: 12px;
margin-right: 3px;
}
.smart-row {
display: flex;
flex-direction: row;
.console {
display: flex;
overflow: hidden;
}
}
&__name {
flex: 1;
}
&__ssh-key {
min-width: 150px;
}
}
</style>
@@ -0,0 +1,75 @@
<script>
import { REASON } from '@shell/config/table-headers';
import SortableTable from '@shell/components/SortableTable';
export default {
name: 'VirtualMachineEvents',
components: { SortableTable },
props: {
events: {
type: Array,
required: true,
},
},
data() {
const reason = {
...REASON,
canBeVariable: true,
width: 180
};
const eventHeaders = [
reason,
{
name: 'resource',
label: 'Resource',
labelKey: 'clusterIndexPage.sections.events.resource.label',
value: 'displayInvolvedObject',
sort: ['involvedObject.kind', 'involvedObject.name'],
canBeVariable: true,
},
{
align: 'right',
name: 'date',
label: 'Date',
labelKey: 'clusterIndexPage.sections.events.date.label',
value: 'lastTimestamp',
sort: 'lastTimestamp:desc',
formatter: 'LiveDate',
formatterOpts: { addSuffix: true },
width: 125,
defaultSort: true,
},
];
return { eventHeaders };
},
};
</script>
<template>
<SortableTable
:rows="events"
:headers="eventHeaders"
key-field="id"
:search="false"
:table-actions="false"
:row-actions="false"
:paging="true"
:rows-per-page="10"
default-sort-by="date"
>
<template #cell:resource="{row, value}">
<div class="text-info">
{{ value }}
</div>
<div v-if="row.message">
{{ row.displayMessage }}
</div>
</template>
</SortableTable>
</template>
@@ -0,0 +1,114 @@
<script>
import isString from 'lodash/isString';
import { HCI } from '../../../types';
import { allHash } from '@shell/utils/promise';
import impl from '../../../mixins/harvester-vm/impl';
export default {
mixins: [impl],
props: {
value: {
type: Object,
required: true
}
},
async fetch() {
const hash = await allHash({ allSSHs: this.$store.dispatch('harvester/findAll', { type: HCI.SSH }) });
this.allSSHs = hash.allSSHs;
},
data() {
return {
allSSHs: [],
sshKeys: []
};
},
methods: {
toggleShow(idx) {
const ssh = this.sshKeys[idx];
this.$set(this.sshKeys, idx, {
...ssh,
showKey: !ssh.showKey
});
},
getKeys() {
return this.mergeAllSSHs(this.value?.spec);
},
isShow(id = '') {
const ssh = this.sshKeys.find(O => O?.data?.id === id) || {};
return ssh.showKey || false;
}
},
watch: {
allSSHs(neu) {
const out = this.getKeys().map((ssh) => {
return {
id: ssh.id,
publicKey: isString(ssh.data) ? ssh.data : ssh.data?.spec?.publicKey,
showKey: this.isShow(ssh.id)
};
});
this.$set(this, 'sshKeys', out);
}
}
};
</script>
<template>
<div class="overview-sshKeys">
<div v-for="(ssh, index) in sshKeys" :key="index" class="row overview-sshKeys__item">
<div class="col span-4">
{{ ssh.id }}
</div>
<div class="col span-7 offset-1">
<div v-if="ssh.showKey" class="key-display">
{{ ssh.publicKey }}
<button class="btn btn-sm role-link hide-bar" @click="toggleShow(index)">
<i class="icon icon-x"></i>
</button>
</div>
<button v-else class="btn btn-sm role-link" @click="toggleShow(index)">
*******<i class="icons icon-show"></i>
</button>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.overview-sshKeys {
text-align: left;
max-height: 700px;
overflow: auto;
&__item {
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
.key-display {
position: relative;
padding-right: 30px;
word-break: break-word;
.hide-bar {
position: absolute;
top: -10px;
right: 10px;
}
}
}
</style>
@@ -0,0 +1,79 @@
<script>
import LabelValue from '@shell/components/LabelValue';
export default {
name: 'VirtualMachineMigration',
components: { LabelValue },
props: {
value: {
type: Object,
required: true,
},
vmiResource: {
type: Object,
required: true,
default: () => {
return {};
}
}
},
data() {
return { localResource: this.vmiResource };
},
computed: {
migrationState() {
return this.localResource?.status?.migrationState;
},
sourceNode() {
return this.migrationState?.sourceNode || 'N/A';
},
targetNode() {
return this.migrationState?.targetNode || 'N/A';
},
started() {
return this.migrationState?.startTimestamp || 'N/A';
},
ended() {
return this.migrationState?.endTimestamp || 'N/A';
},
message() {
return 'N/A';
}
},
watch: {
vmiResource: {
handler(neu) {
this.localResource = neu;
},
deep: true
}
}
};
</script>
<template>
<div>
<div class="row mb-20">
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.sourceNode')" :value="sourceNode" />
</div>
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.targetNode')" :value="targetNode" />
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.started')" :value="started" />
</div>
<div class="col span-6">
<LabelValue :name="t('harvester.virtualMachine.detail.details.ended')" :value="ended" />
</div>
</div>
</div>
</template>