mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-01 03:36:04 +08:00
Add pkg/harvester components + shell portings - 1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
This commit is contained in:
+283
@@ -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>
|
||||
+75
@@ -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>
|
||||
+114
@@ -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>
|
||||
+79
@@ -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>
|
||||
@@ -0,0 +1,271 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Tabbed from '@shell/components/Tabbed';
|
||||
import Tab from '@shell/components/Tabbed/Tab';
|
||||
import { EVENT, SERVICE, POD } from '@shell/config/types';
|
||||
import { HCI } from '../../types';
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import VM_MIXIN from '../../mixins/harvester-vm';
|
||||
import DashboardMetrics from '@shell/components/DashboardMetrics';
|
||||
import { allHash, setPromiseResult } from '@shell/utils/promise';
|
||||
import { allDashboardsExist } from '@shell/utils/grafana';
|
||||
|
||||
import CloudConfig from '../../edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
|
||||
import Volume from '../../edit/kubevirt.io.virtualmachine/VirtualMachineVolume';
|
||||
import Network from '../../edit/kubevirt.io.virtualmachine/VirtualMachineNetwork';
|
||||
import NodeScheduling from '@shell/components/form/NodeScheduling';
|
||||
import PodAffinity from '@shell/components/form/PodAffinity';
|
||||
import AccessCredentials from '../../edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials';
|
||||
import Events from './VirtualMachineTabs/VirtualMachineEvents';
|
||||
import Migration from './VirtualMachineTabs/VirtualMachineMigration';
|
||||
import OverviewBasics from './VirtualMachineTabs/VirtualMachineBasics';
|
||||
import OverviewKeypairs from './VirtualMachineTabs/VirtualMachineKeypairs';
|
||||
import KeyValue from '@shell/components/form/KeyValue';
|
||||
import Labels from '@shell/components/form/Labels';
|
||||
|
||||
const VM_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/harvester-vm-detail-1/vm-info-detail?orgId=1';
|
||||
|
||||
export default {
|
||||
name: 'VMIDetailsPage',
|
||||
|
||||
components: {
|
||||
Tab,
|
||||
Tabbed,
|
||||
Events,
|
||||
OverviewBasics,
|
||||
Volume,
|
||||
Network,
|
||||
OverviewKeypairs,
|
||||
CloudConfig,
|
||||
Migration,
|
||||
DashboardMetrics,
|
||||
AccessCredentials,
|
||||
NodeScheduling,
|
||||
PodAffinity,
|
||||
KeyValue,
|
||||
Labels,
|
||||
},
|
||||
|
||||
mixins: [CreateEditView, VM_MIXIN],
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
switchToCloud: false,
|
||||
VM_METRICS_DETAIL_URL,
|
||||
showVmMetrics: false,
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const hash = {
|
||||
pods: this.$store.dispatch(`${ inStore }/findAll`, { type: POD }),
|
||||
services: this.$store.dispatch(`${ inStore }/findAll`, { type: SERVICE }),
|
||||
events: this.$store.dispatch(`${ inStore }/findAll`, { type: EVENT }),
|
||||
allSSHs: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.SSH }),
|
||||
vmis: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMI }),
|
||||
restore: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESTORE }),
|
||||
};
|
||||
|
||||
await allHash(hash);
|
||||
|
||||
setPromiseResult(
|
||||
allDashboardsExist(this.$store, this.currentCluster.id, [VM_METRICS_DETAIL_URL], 'harvester'),
|
||||
this,
|
||||
'showVmMetrics',
|
||||
'Determine vm metrics'
|
||||
);
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters(['currentCluster']),
|
||||
|
||||
vmi() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
const vmiList = this.$store.getters[`${ inStore }/all`](HCI.VMI) || [];
|
||||
const vmi = vmiList.find( (VMI) => {
|
||||
return VMI?.metadata?.ownerReferences?.[0]?.uid === this.value?.metadata?.uid;
|
||||
});
|
||||
|
||||
return vmi;
|
||||
},
|
||||
|
||||
allEvents() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
return this.$store.getters[`${ inStore }/all`](EVENT);
|
||||
},
|
||||
|
||||
events() {
|
||||
return this.allEvents.filter((e) => {
|
||||
const { name, creationTimestamp } = this.value?.metadata || {};
|
||||
const podName = this.value.podResource?.metadata?.name;
|
||||
const pvcName = this.value.persistentVolumeClaimName || [];
|
||||
|
||||
const involvedName = e?.involvedObject?.name;
|
||||
|
||||
const matchPVC = pvcName.find(name => name === involvedName);
|
||||
|
||||
return (involvedName === name || involvedName === podName || matchPVC) && e.firstTimestamp >= creationTimestamp;
|
||||
}).sort((a, b) => {
|
||||
if (a.lastTimestamp > b.lastTimestamp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
});
|
||||
},
|
||||
|
||||
graphVars() {
|
||||
return {
|
||||
namespace: this.value.namespace,
|
||||
vm: this.value.name
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onTabChanged({ tab }) {
|
||||
if (tab.name === 'cloudConfig') {
|
||||
this.$refs.yamlEditor?.refresh();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
handler(neu) {
|
||||
const diskRows = this.getDiskRows(neu);
|
||||
|
||||
this.$set(this, 'diskRows', diskRows);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Tabbed v-bind="$attrs" class="mt-15" :side-tabs="true" @changed="onTabChanged">
|
||||
<Tab name="basics" :label="t('harvester.virtualMachine.detail.tabs.basics')" class="bordered-table" :weight="7">
|
||||
<OverviewBasics v-model="value" :vmi="vmi" mode="view" />
|
||||
</Tab>
|
||||
|
||||
<Tab name="disks" :label="t('harvester.tab.volume')" class="bordered-table" :weight="6">
|
||||
<Volume
|
||||
v-model="diskRows"
|
||||
mode="view"
|
||||
:namespace="value.metadata.namespace"
|
||||
:vm="value"
|
||||
:resource-type="value.type"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab name="networks" :label="t('harvester.virtualMachine.detail.tabs.networks')" class="bordered-table" :weight="5">
|
||||
<Network v-model="networkRows" mode="view" />
|
||||
</Tab>
|
||||
|
||||
<Tab name="keypairs" :label="t('harvester.virtualMachine.detail.tabs.keypairs')" class="bordered-table" :weight="3">
|
||||
<OverviewKeypairs v-model="value" />
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
v-if="showVmMetrics"
|
||||
name="vm-metrics"
|
||||
:label="t('harvester.virtualMachine.detail.tabs.metrics')"
|
||||
:weight="2.5"
|
||||
>
|
||||
<template #default="props">
|
||||
<DashboardMetrics
|
||||
v-if="props.active"
|
||||
:detail-url="VM_METRICS_DETAIL_URL"
|
||||
graph-height="550px"
|
||||
:has-summary-and-detail="false"
|
||||
:vars="graphVars"
|
||||
/>
|
||||
</template>
|
||||
</Tab>
|
||||
|
||||
<Tab name="nodeScheduling" :label="t('workload.container.titles.nodeScheduling')" :weight="2.4">
|
||||
<template #default="{active}">
|
||||
<NodeScheduling v-if="spec" :key="active" :mode="mode" :value="spec.template.spec" :nodes="nodesIdOptions" />
|
||||
</template>
|
||||
</Tab>
|
||||
|
||||
<Tab :label="t('harvester.tab.vmScheduling')" name="vmScheduling" :weight="2.3">
|
||||
<template #default="{active}">
|
||||
<PodAffinity
|
||||
v-if="spec"
|
||||
:key="active"
|
||||
:mode="mode"
|
||||
:value="spec.template.spec"
|
||||
:nodes="nodes"
|
||||
:all-namespaces-option-available="true"
|
||||
:namespaces="filteredNamespaces"
|
||||
:overwrite-labels="affinityLabels"
|
||||
/>
|
||||
</template>
|
||||
</Tab>
|
||||
|
||||
<Tab :label="t('harvester.tab.accessCredentials')" class="bordered-table" name="accessCredentials" :weight="2.2">
|
||||
<AccessCredentials mode="view" :value="accessCredentials" :resource="value" />
|
||||
</Tab>
|
||||
|
||||
<Tab name="cloudConfig" :label="t('harvester.virtualMachine.detail.tabs.cloudConfig')" class="bordered-table" :weight="2">
|
||||
<CloudConfig
|
||||
ref="yamlEditor"
|
||||
mode="view"
|
||||
:user-script="userScript"
|
||||
:network-script="networkScript"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab name="event" :label="t('harvester.virtualMachine.detail.tabs.events')" :weight="1">
|
||||
<Events :resource="vmi" :events="events" />
|
||||
</Tab>
|
||||
|
||||
<Tab name="migration" :label="t('harvester.virtualMachine.detail.tabs.migration')">
|
||||
<Migration v-model="value" :vmi-resource="vmi" />
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
name="instanceLabel"
|
||||
:label="t('harvester.tab.instanceLabel')"
|
||||
:weight="-99"
|
||||
>
|
||||
<Labels
|
||||
:default-container-class="'labels-and-annotations-container'"
|
||||
:value="value"
|
||||
:mode="mode"
|
||||
:display-side-by-side="false"
|
||||
:show-annotations="false"
|
||||
:show-label-title="false"
|
||||
>
|
||||
<template #labels="{toggler}">
|
||||
<KeyValue
|
||||
key="labels"
|
||||
:value="value.instanceLabels"
|
||||
:protected-keys="value.systemLabels || []"
|
||||
:toggle-filter="toggler"
|
||||
:add-label="t('labels.addLabel')"
|
||||
:mode="mode"
|
||||
:read-allowed="false"
|
||||
:value-can-be-empty="true"
|
||||
@input="value.setInstanceLabels($event)"
|
||||
/>
|
||||
</template>
|
||||
</Labels>
|
||||
</Tab>
|
||||
</Tabbed>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user