fix conflict

This commit is contained in:
Nancy Butler 2022-07-11 15:49:19 -07:00
parent 840d1518f1
commit 38dfc71654
28 changed files with 805 additions and 1334 deletions

View File

@ -1,5 +1,6 @@
<script>
import compact from 'lodash/compact';
import { OFF } from '../../models/harvester/kubevirt.io.virtualmachine';
import { get } from '@shell/utils/object';
import { isIpv4 } from '@shell/utils/string';
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
@ -19,6 +20,10 @@ export default {
row: {
type: Object,
required: true
},
col: {
type: Object,
default: () => {}
}
},
@ -31,16 +36,11 @@ export default {
},
networkAnnotationIP() {
if (this.row.actualState !== 'Running') {
// TODO: Running
if (this.row.actualState !== 'Running') { // TODO: Running
return [];
}
const annotationIp =
get(
this.row,
`metadata.annotations."${ HCI_ANNOTATIONS.NETWORK_IPS }"`
) || '[]';
const annotationIp = get(this.row, `metadata.annotations."${ HCI_ANNOTATIONS.NETWORK_IPS }"`) || '[]';
// Obtain IP from VM annotation, remove the CIDR suffix number if CIDR Exist
try {
@ -82,8 +82,8 @@ export default {
showIP() {
return this.row.stateDisplay !== OFF;
}
}
},
},
};
</script>

View File

@ -4,9 +4,9 @@ import { Banner } from '@components/Banner';
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
import { LONGHORN, METRIC, HCI } from '@shell/config/types';
import HarvesterCPUUsed from '@shell/components/formatter/HarvesterCPUUsed';
import HarvesterMemoryUsed from '@shell/components/formatter/HarvesterMemoryUsed';
import HarvesterStorageUsed from '@shell/components/formatter/HarvesterStorageUsed';
import HarvesterCPUUsed from '../../components/formatter/HarvesterCPUUsed';
import HarvesterMemoryUsed from '../../components/formatter/HarvesterMemoryUsed';
import HarvesterStorageUsed from '../../components/formatter/HarvesterStorageUsed';
const COMPLETE = 'complete';
const NONE = 'none';
@ -21,13 +21,13 @@ export default {
Banner,
HarvesterCPUUsed,
HarvesterMemoryUsed,
HarvesterStorageUsed
HarvesterStorageUsed,
},
props: {
value: {
type: Object,
required: true
required: true,
},
metrics: {
@ -55,25 +55,18 @@ export default {
computed: {
customName() {
return this.value.metadata?.annotations?.[
HCI_ANNOTATIONS.HOST_CUSTOM_NAME
];
return this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CUSTOM_NAME];
},
consoleUrl() {
const consoleUrl = this.value.metadata?.annotations?.[
HCI_ANNOTATIONS.HOST_CONSOLE_URL
];
const consoleUrl = this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL];
let value = consoleUrl;
if (!consoleUrl) {
return '';
}
if (
!consoleUrl.startsWith('http://') &&
!consoleUrl.startsWith('https://')
) {
if (!consoleUrl.startsWith('http://') && !consoleUrl.startsWith('https://')) {
value = `http://${ consoleUrl }`;
}
@ -125,10 +118,7 @@ export default {
storageUsage() {
const inStore = this.$store.getters['currentProduct'].inStore;
const longhornNode = this.$store.getters[`${ inStore }/byId`](
LONGHORN.NODES,
`longhorn-system/${ this.value.id }`
);
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.value.id }`);
let out = 0;
const diskStatus = longhornNode?.status?.diskStatus || {};
@ -144,10 +134,7 @@ export default {
storageTotal() {
const inStore = this.$store.getters['currentProduct'].inStore;
const longhornNode = this.$store.getters[`${ inStore }/byId`](
LONGHORN.NODES,
`longhorn-system/${ this.value.id }`
);
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.value.id }`);
let out = 0;
const diskStatus = longhornNode?.status?.diskStatus || {};
@ -186,15 +173,8 @@ export default {
},
nodeRoleState() {
const isExistRoleStatus =
this.value.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_MASTER] !==
undefined ||
this.value.metadata?.labels?.[
HCI_ANNOTATIONS.NODE_ROLE_CONTROL_PLANE
] !== undefined;
const promoteStatus =
this.value.metadata?.annotations?.[HCI_ANNOTATIONS.PROMOTE_STATUS] ||
NONE;
const isExistRoleStatus = this.value.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_MASTER] !== undefined || this.value.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_CONTROL_PLANE] !== undefined;
const promoteStatus = this.value.metadata?.annotations?.[HCI_ANNOTATIONS.PROMOTE_STATUS] || NONE;
if (!isExistRoleStatus && promoteStatus === COMPLETE) {
return PROMOTE_RESTART;
@ -227,7 +207,7 @@ export default {
const inStore = this.$store.getters['currentProduct'].inStore;
return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.NODE_NETWORK);
}
},
},
methods: {
@ -241,7 +221,7 @@ export default {
};
return formatSi(value, formatOptions);
}
},
}
};
</script>
@ -251,25 +231,16 @@ export default {
<h3>{{ t('harvester.host.tabs.overview') }}</h3>
<div class="row mb-20">
<div class="col span-6">
<LabelValue
:name="t('harvester.host.detail.customName')"
:value="customName"
/>
<LabelValue :name="t('harvester.host.detail.customName')" :value="customName" />
</div>
<div class="col span-6">
<LabelValue
:name="t('harvester.host.detail.hostIP')"
:value="value.internalIp"
/>
<LabelValue :name="t('harvester.host.detail.hostIP')" :value="value.internalIp" />
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<LabelValue
:name="t('harvester.host.detail.os')"
:value="value.status.nodeInfo.osImage"
/>
<LabelValue :name="t('harvester.host.detail.os')" :value="value.status.nodeInfo.osImage" />
</div>
<div class="col span-6">
<div class="role">
@ -287,28 +258,17 @@ export default {
<div class="row mb-20">
<div class="col span-6">
<LabelValue
:name="t('harvester.host.detail.create')"
:value="value.metadata.creationTimestamp"
/>
<LabelValue :name="t('harvester.host.detail.create')" :value="value.metadata.creationTimestamp" />
</div>
<div class="col span-6">
<LabelValue
:name="t('harvester.host.detail.update')"
:value="lastUpdateTime"
/>
<LabelValue :name="t('harvester.host.detail.update')" :value="lastUpdateTime" />
</div>
</div>
<div class="row mb-20">
<div class="col span-6">
<LabelValue
:name="t('harvester.host.detail.consoleUrl')"
:value="consoleUrl.value"
>
<a slot="value" :href="consoleUrl.value" target="_blank">{{
consoleUrl.display
}}</a>
<LabelValue :name="t('harvester.host.detail.consoleUrl')" :value="consoleUrl.value">
<a slot="value" :href="consoleUrl.value" target="_blank">{{ consoleUrl.display }}</a>
</LabelValue>
</div>
</div>
@ -321,10 +281,7 @@ export default {
</Banner>
<div class="row mb-20">
<div class="col span-6">
<LabelValue
:name="t('harvester.host.detail.networkType')"
:value="networkType"
/>
<LabelValue :name="t('harvester.host.detail.networkType')" :value="networkType" />
</div>
<div class="col span-6">
@ -365,24 +322,15 @@ export default {
<h3>{{ t('harvester.host.detail.more') }}</h3>
<div class="row mb-20">
<div class="col span-4">
<LabelValue
:name="t('harvester.host.detail.uuid')"
:value="value.status.nodeInfo.systemUUID"
/>
<LabelValue :name="t('harvester.host.detail.uuid')" :value="value.status.nodeInfo.systemUUID" />
</div>
<div class="col span-4">
<LabelValue
:name="t('harvester.host.detail.kernel')"
:value="value.status.nodeInfo.kernelVersion"
/>
<LabelValue :name="t('harvester.host.detail.kernel')" :value="value.status.nodeInfo.kernelVersion" />
</div>
<div class="col span-4">
<LabelValue
:name="t('harvester.host.detail.containerRuntime')"
:value="value.status.nodeInfo.containerRuntimeVersion"
/>
<LabelValue :name="t('harvester.host.detail.containerRuntime')" :value="value.status.nodeInfo.containerRuntimeVersion" />
</div>
</div>
</div>

View File

@ -2,7 +2,7 @@
import { STATE, AGE, NAME } from '@shell/config/table-headers';
import SortableTable from '@shell/components/SortableTable';
import Loading from '@shell/components/Loading';
import HarvesterVmState from '@shell/components/formatter/HarvesterVmState';
import HarvesterVmState from '../../components/formatter/HarvesterVmState';
import { allHash } from '@shell/utils/promise';
import { HCI } from '@shell/config/types';
import { HOSTNAME } from '@shell/config/labels-annotations';
@ -13,14 +13,14 @@ export default {
components: {
SortableTable,
Loading,
HarvesterVmState
HarvesterVmState,
},
props: {
node: {
type: Object,
required: true
}
required: true,
},
},
async fetch() {
@ -28,7 +28,7 @@ export default {
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
vmis: this.$store.dispatch('harvester/findAll', { type: HCI.VMI }),
allNodeNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.NODE_NETWORK }),
allClusterNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.CLUSTER_NETWORK })
allClusterNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.CLUSTER_NETWORK }),
});
const instanceMap = {};
@ -43,10 +43,7 @@ export default {
this.allNodeNetwork = hash.allNodeNetwork;
this.allClusterNetwork = hash.allClusterNetwork;
this.rows = hash.vms.filter((row) => {
return (
instanceMap[row.metadata?.uid]?.status?.nodeName ===
this.node?.metadata?.labels?.[HOSTNAME]
);
return instanceMap[row.metadata?.uid]?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME];
});
},
@ -88,10 +85,10 @@ export default {
},
{
...AGE,
sort: 'metadata.creationTimestamp:desc'
sort: 'metadata.creationTimestamp:desc',
}
];
}
},
},
methods: {}
@ -112,15 +109,10 @@ export default {
>
<template slot="cell:state" slot-scope="scope" class="state-col">
<div class="state">
<HarvesterVmState
class="vmstate"
:row="scope.row"
:all-node-network="allNodeNetwork"
:all-cluster-network="allClusterNetwork"
/>
<HarvesterVmState class="vmstate" :row="scope.row" :all-node-network="allNodeNetwork" :all-cluster-network="allClusterNetwork" />
</div>
</template>
</SortableTable>
</Sortabletable>
</div>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script>
import HarvesterIpAddress from '@shell/components/formatter/HarvesterIpAddress';
import HarvesterIpAddress from '../../../components/formatter/HarvesterIpAddress';
import VMConsoleBar from '../../../components/VMConsoleBar';
import LabelValue from '@shell/components/LabelValue';
import InputOrDisplay from '@shell/components/InputOrDisplay';
@ -34,8 +34,8 @@ export default {
},
mode: {
type: String,
required: true
}
required: true,
},
},
computed: {
@ -46,8 +46,7 @@ export default {
return UNDEFINED;
}
return `${ date.getMonth() +
1 }/${ date.getDate() }/${ date.getUTCFullYear() }`;
return `${ date.getMonth() + 1 }/${ date.getDate() }/${ date.getUTCFullYear() }`;
},
node() {
@ -55,16 +54,13 @@ export default {
},
hostname() {
return (
this.resource?.spec?.hostname ||
this.resource?.status?.guestOSInfo?.hostname
);
return this.resource?.spec?.hostname || this.resource?.status?.guestOSInfo?.hostname;
},
imageName() {
const imageList = this.$store.getters['harvester/all'](HCI.IMAGE) || [];
const image = imageList.find((I) => {
const image = imageList.find( (I) => {
return this.value.rootImageId === I.id;
});
@ -72,14 +68,11 @@ export default {
},
disks() {
const disks =
this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
const disks = this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
return disks
.filter((disk) => {
return disks.filter((disk) => {
return !!disk.bootOrder;
})
.sort((a, b) => {
}).sort((a, b) => {
if (a.bootOrder < b.bootOrder) {
return -1;
}
@ -89,8 +82,7 @@ export default {
},
cdroms() {
const disks =
this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
const disks = this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
return disks.filter((disk) => {
return !!disk.cdrom;
@ -100,9 +92,7 @@ export default {
flavor() {
const domain = this.value?.spec?.template?.spec?.domain;
return `${ domain.cpu?.cores } vCPU , ${
domain.resources?.limits?.memory
} ${ this.t('harvester.virtualMachine.input.memory') }`;
return `${ domain.cpu?.cores } vCPU , ${ domain.resources?.limits?.memory } ${ this.t('harvester.virtualMachine.input.memory') }`;
},
kernelRelease() {
@ -118,9 +108,7 @@ export default {
},
machineType() {
return (
this.value?.spec?.template?.spec?.domain?.machine?.type || undefined
);
return this.value?.spec?.template?.spec?.domain?.machine?.type || undefined;
}
},
@ -143,15 +131,11 @@ export default {
<div class="overview-basics">
<div class="row">
<div class="col span-6">
<LabelValue
:name="t('harvester.virtualMachine.detail.details.name')"
:value="value.nameDisplay"
>
<LabelValue :name="t('harvester.virtualMachine.detail.details.name')" :value="value.nameDisplay">
<template #value>
<div class="smart-row">
<div class="console">
{{ value.nameDisplay }}
<VMConsoleBar :resource="value" class="consoleBut" />
{{ value.nameDisplay }} <VMConsoleBar :resource="value" class="consoleBut" />
</div>
</div>
</template>
@ -165,35 +149,26 @@ export default {
<div class="row">
<div class="col span-6">
<LabelValue
:name="t('harvester.virtualMachine.detail.details.hostname')"
:value="hostname"
>
<LabelValue :name="t('harvester.virtualMachine.detail.details.hostname')" :value="hostname">
<template #value>
<div v-if="!isDown">
{{
hostname ||
t('harvester.virtualMachine.detail.GuestAgentNotInstalled')
}}
{{ hostname || t("harvester.virtualMachine.detail.GuestAgentNotInstalled") }}
</div>
<div v-else>
{{ t('harvester.virtualMachine.detail.details.down') }}
{{ t("harvester.virtualMachine.detail.details.down") }}
</div>
</template>
</LabelValue>
</div>
<div class="col span-6">
<LabelValue
:name="t('harvester.virtualMachine.detail.details.node')"
:value="node"
>
<LabelValue :name="t('harvester.virtualMachine.detail.details.node')" :value="node">
<template #value>
<div v-if="!isDown">
{{ node }}
</div>
<div v-else>
{{ t('harvester.virtualMachine.detail.details.down') }}
{{ t("harvester.virtualMachine.detail.details.down") }}
</div>
</template>
</LabelValue>
@ -202,9 +177,7 @@ export default {
<div class="row">
<div class="col span-6">
<LabelValue
:name="t('harvester.virtualMachine.detail.details.ipAddress')"
>
<LabelValue :name="t('harvester.virtualMachine.detail.details.ipAddress')">
<template #value>
<HarvesterIpAddress v-model="value.id" :row="value" />
</template>
@ -212,10 +185,7 @@ export default {
</div>
<div class="col span-6">
<LabelValue
:name="t('harvester.virtualMachine.detail.details.created')"
:value="creationTimestamp"
/>
<LabelValue :name="t('harvester.virtualMachine.detail.details.created')" :value="creationTimestamp" />
</div>
</div>
@ -225,37 +195,27 @@ export default {
<div class="row">
<div class="col span-6">
<InputOrDisplay
:name="t('harvester.virtualMachine.detail.details.bootOrder')"
:value="disks"
:mode="mode"
>
<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 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"
>
<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">
<li v-for="(rom) in cdroms" :key="rom.name">
{{ rom.name }}
</li>
</ul>
<span v-else>
{{ t('harvester.virtualMachine.detail.notAvailable') }}
{{ t("harvester.virtualMachine.detail.notAvailable") }}
</span>
</div>
</template>
@ -264,48 +224,30 @@ export default {
</div>
<div class="row">
<div class="col span-6">
<LabelValue
:name="t('harvester.virtualMachine.detail.details.operatingSystem')"
:value="
operatingSystem ||
t('harvester.virtualMachine.detail.GuestAgentNotInstalled')
"
/>
<LabelValue :name="t('harvester.virtualMachine.detail.details.operatingSystem')" :value="operatingSystem || t('harvester.virtualMachine.detail.GuestAgentNotInstalled')" />
</div>
<LabelValue
:name="t('harvester.virtualMachine.detail.details.flavor')"
:value="flavor"
/>
<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 ||
t('harvester.virtualMachine.detail.GuestAgentNotInstalled')
"
/>
<LabelValue :name="t('harvester.virtualMachine.detail.details.kernelRelease')" :value="kernelRelease || t('harvester.virtualMachine.detail.GuestAgentNotInstalled')" />
</div>
<div class="col span-6">
<LabelValue
:name="t('harvester.virtualMachine.input.MachineType')"
:value="machineType"
/>
<LabelValue :name="t('harvester.virtualMachine.input.MachineType')" :value="machineType" />
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.consoleBut {
.consoleBut {
position: relative;
top: -20px;
left: 38px;
}
}
.overview-basics {
.overview-basics {
display: grid;
grid-template-columns: 100%;
grid-template-rows: auto;
@ -333,5 +275,5 @@ export default {
&__ssh-key {
min-width: 150px;
}
}
}
</style>

View File

@ -11,14 +11,10 @@ import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
export default {
name: 'HarvesterHotplugModal',
name: 'HotplugModal',
components: {
AsyncButton,
Card,
LabeledInput,
LabeledSelect,
Banner
AsyncButton, Card, LabeledInput, LabeledSelect, Banner
},
props: {
@ -37,7 +33,7 @@ export default {
diskName: '',
volumeName: '',
errors: [],
allPVCs: []
allPVCs: [],
};
},
@ -45,11 +41,7 @@ export default {
...mapGetters({ t: 'i18n/t' }),
PVCs() {
return (
this.allPVCs.filter(
P => this.actionResource.metadata.namespace === P.metadata.namespace
) || []
);
return this.allPVCs.filter(P => this.actionResource.metadata.namespace === P.metadata.namespace) || [];
},
actionResource() {
@ -58,13 +50,15 @@ export default {
volumeOption() {
return sortBy(
this.PVCs.filter((pvc) => {
this.PVCs
.filter( (pvc) => {
if (!!pvc.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID]) {
return false;
}
return !pvc.attachVM;
}).map((pvc) => {
})
.map((pvc) => {
return {
label: pvc.metadata.name,
value: pvc.metadata.name
@ -72,7 +66,7 @@ export default {
}),
'label'
);
}
},
},
methods: {
@ -85,25 +79,13 @@ export default {
async save(buttonCb) {
if (this.actionResource) {
try {
const res = await this.actionResource.doAction(
'addVolume',
{ volumeSourceName: this.volumeName, diskName: this.diskName },
{},
false
);
const res = await this.actionResource.doAction('addVolume', { volumeSourceName: this.volumeName, diskName: this.diskName }, {}, false);
if (res._status === 200 || res._status === 204) {
this.$store.dispatch(
'growl/success',
{
this.$store.dispatch('growl/success', {
title: this.t('harvester.notification.title.succeed'),
message: this.t('harvester.modal.hotplug.success', {
diskName: this.diskName,
vm: this.actionResource.nameDisplay
})
},
{ root: true }
);
message: this.t('harvester.modal.hotplug.success', { diskName: this.diskName, vm: this.actionResource.nameDisplay })
}, { root: true });
this.close();
buttonCb(true);
@ -121,21 +103,21 @@ export default {
buttonCb(false);
}
}
}
},
}
};
</script>
<template>
<Card ref="modal" name="modal" :show-highlight-border="false">
<h4
slot="title"
class="text-default-text"
v-html="t('harvester.modal.hotplug.title')"
/>
<h4 slot="title" class="text-default-text" v-html="t('harvester.modal.hotplug.title')" />
<template #body>
<LabeledInput v-model="diskName" :label="t('generic.name')" required />
<LabeledInput
v-model="diskName"
:label="t('generic.name')"
required
/>
<LabeledSelect
v-model="volumeName"

View File

@ -10,7 +10,7 @@ import LabelValue from '@shell/components/LabelValue';
import Select from '@shell/components/form/Select';
import CreateEditView from '@shell/mixins/create-edit-view';
import { OS } from '../mixins/harvester-vm';
import { VM_IMAGE_FILE_FORMAT } from '../validators/imageUrl';
import { VM_IMAGE_FILE_FORMAT } from '@shell/utils/validators/vm-image';
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
import { exceptionToErrorsArray } from '@shell/utils/error';
@ -30,7 +30,7 @@ export default {
LabeledInput,
NameNsDescription,
RadioGroup,
LabelValue
LabelValue,
},
mixins: [CreateEditView],
@ -38,12 +38,12 @@ export default {
props: {
value: {
type: Object,
required: true
}
required: true,
},
},
data() {
if (!this.value.spec) {
if ( !this.value.spec ) {
this.$set(this.value, 'spec', { sourceType: DOWNLOAD });
}
@ -57,7 +57,7 @@ export default {
resource: '',
headers: {},
fileUrl: '',
file: ''
file: '',
};
},
@ -67,9 +67,7 @@ export default {
},
imageName() {
return (
this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-'
);
return this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-';
},
isCreateEdit() {
@ -85,10 +83,7 @@ export default {
'value.spec.url'(neu) {
const url = neu.trim();
const suffixName = url.split('/').pop();
const fileSuffix = suffixName
.split('.')
.pop()
.toLowerCase();
const fileSuffix = suffixName.split('.').pop().toLowerCase();
this.value.spec.url = url;
if (VM_IMAGE_FILE_FORMAT.includes(fileSuffix)) {
@ -106,7 +101,7 @@ export default {
if (!this.value.spec.displayName) {
this.$refs.nd.changeNameAndNamespace({
text: suffixName,
selected: this.value.metadata.namespace
selected: this.value.metadata.namespace,
});
}
}
@ -130,7 +125,7 @@ export default {
if (this.$refs?.file?.value) {
this.$refs.file.value = null;
}
}
},
},
methods: {
@ -143,8 +138,7 @@ export default {
const file = this.file;
this.value.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] =
file?.name;
this.value.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] = file?.name;
const res = await this.value.save();
@ -169,7 +163,7 @@ export default {
if (!this.value.spec.displayName) {
this.$refs.nd.changeNameAndNamespace({
text: file?.name,
selected: this.value.metadata.namespace
selected: this.value.metadata.namespace,
});
}
},
@ -181,10 +175,7 @@ export default {
},
internalAnnotations(option) {
const optionKeys = [
HCI_ANNOTATIONS.OS_TYPE,
HCI_ANNOTATIONS.IMAGE_SUFFIX
];
const optionKeys = [HCI_ANNOTATIONS.OS_TYPE, HCI_ANNOTATIONS.IMAGE_SUFFIX];
return optionKeys.find(O => O === option.key);
},
@ -193,16 +184,13 @@ export default {
if (keyName === HCI_ANNOTATIONS.OS_TYPE) {
return OS;
} else if (keyName === HCI_ANNOTATIONS.IMAGE_SUFFIX) {
return [
{
return [{
label: 'ISO',
value: 'iso'
},
{
}, {
label: 'raw/qcow2',
value: rawORqcow2
}
];
}];
}
return [];
@ -217,16 +205,16 @@ export default {
return;
}
return OS.find((os) => {
return OS.find( (os) => {
if (os.match) {
return os.match.find(matchValue => url.toLowerCase().includes(matchValue)
) ? os.value : false;
return os.match.find(matchValue => url.toLowerCase().includes(matchValue)) ? os.value : false;
} else {
return url.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
}
});
}
}
},
};
</script>
@ -249,20 +237,18 @@ export default {
/>
<Tabbed v-bind="$attrs" class="mt-15" :side-tabs="true">
<Tab
name="basic"
:label="t('harvester.image.tabs.basics')"
:weight="3"
class="bordered-table"
>
<Tab name="basic" :label="t('harvester.image.tabs.basics')" :weight="3" class="bordered-table">
<RadioGroup
v-if="isCreate"
v-model="value.spec.sourceType"
name="model"
:options="['download', 'upload']"
:options="[
'download',
'upload',
]"
:labels="[
t('harvester.image.sourceType.download'),
t('harvester.image.sourceType.upload')
t('harvester.image.sourceType.upload'),
]"
:mode="mode"
/>
@ -308,13 +294,19 @@ export default {
/>
</button>
<div v-if="uploadFileName" class="fileName mt-5">
<div
v-if="uploadFileName"
class="fileName mt-5"
>
<span class="icon icon-file" />
{{ uploadFileName }}
</div>
</div>
</div>
<div v-else class="col span-12">
<div
v-else
class="col span-12"
>
<LabelValue
:name="t('harvester.image.fileName')"
:value="imageName"
@ -323,12 +315,7 @@ export default {
</div>
</Tab>
<Tab
name="labels"
:label="t('labels.labels.title')"
:weight="2"
class="bordered-table"
>
<Tab name="labels" :label="t('labels.labels.title')" :weight="2" class="bordered-table">
<KeyValue
key="labels"
ref="labels"

View File

@ -2,7 +2,7 @@
import ConsoleBar from '../components/VMConsoleBar';
import ResourceTable from '@shell/components/ResourceTable';
import LinkDetail from '@shell/components/formatter/LinkDetail';
import HarvesterVmState from '@shell/components/formatter/HarvesterVmState';
import HarvesterVmState from '../components/formatter/HarvesterVmState';
import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
import { HCI, NODE, POD } from '@shell/config/types';
@ -23,15 +23,15 @@ export default {
props: {
schema: {
type: Object,
required: true
}
required: true,
},
},
async fetch() {
const _hash = {
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
pod: this.$store.dispatch('harvester/findAll', { type: POD }),
restore: this.$store.dispatch('harvester/findAll', { type: HCI.RESTORE })
restore: this.$store.dispatch('harvester/findAll', { type: HCI.RESTORE }),
};
if (this.$store.getters['harvester/schemaFor'](NODE)) {
@ -69,7 +69,7 @@ export default {
STATE,
{
...NAME,
width: 300
width: 300,
},
NAMESPACE,
{
@ -78,7 +78,7 @@ export default {
sort: ['spec.template.spec.domain.cpu.cores'],
value: 'spec.template.spec.domain.cpu.cores',
align: 'center',
dashIfEmpty: true
dashIfEmpty: true,
},
{
name: 'Memory',
@ -89,14 +89,10 @@ export default {
formatter: 'Si',
formatterOpts: {
opts: {
increment: 1024,
addSuffix: true,
maxExponent: 3,
minExponent: 3,
suffix: 'i'
increment: 1024, addSuffix: true, maxExponent: 3, minExponent: 3, suffix: 'i',
},
needParseSi: true
}
},
},
{
name: 'ip',
@ -115,15 +111,13 @@ export default {
},
{
...AGE,
sort: 'metadata.creationTimestamp:desc'
sort: 'metadata.creationTimestamp:desc',
}
];
},
rows() {
const matchVMIs = this.allVMIs.filter(
VMI => !this.allVMs.find(VM => VM.id === VMI.id)
);
const matchVMIs = this.allVMIs.filter(VMI => !this.allVMs.find(VM => VM.id === VMI.id));
return [...this.allVMs, ...matchVMIs];
}
@ -154,22 +148,13 @@ export default {
>
<template slot="cell:state" slot-scope="scope" class="state-col">
<div class="state">
<HarvesterVmState
class="vmstate"
:row="scope.row"
:all-node-network="allNodeNetworks"
:all-cluster-network="allClusterNetworks"
/>
<HarvesterVmState class="vmstate" :row="scope.row" :all-node-network="allNodeNetworks" :all-cluster-network="allClusterNetworks" />
</div>
</template>
<template slot="cell:name" slot-scope="scope">
<div class="name-console">
<LinkDetail
v-if="scope.row.type !== HCI.VMI"
v-model="scope.row.metadata.name"
:row="scope.row"
/>
<LinkDetail v-if="scope.row.type !== HCI.VMI" v-model="scope.row.metadata.name" :row="scope.row" />
<span v-else>
{{ scope.row.metadata.name }}
</span>
@ -197,12 +182,17 @@ export default {
span {
line-height: 26px;
width: 160px;
overflow: hidden;
width:160px;
overflow:hidden;
white-space: nowrap;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-o-text-overflow:ellipsis;
}
<<<<<<< HEAD
=======
display: flex;
justify-content: space-around
>>>>>>> fix conflict
}
</style>

View File

@ -1,11 +0,0 @@
import { HCI } from '@shell/config/labels-annotations';
export default function fileRequired(annotations = {}, getters, errors, validatorArgs, type) {
const t = getters['i18n/t'];
if (!annotations[HCI.IMAGE_NAME]) {
errors.push(t('validation.required', { key: t('harvester.image.fileName') }));
}
return errors;
}

View File

@ -1,24 +0,0 @@
export const VM_IMAGE_FILE_FORMAT = ['qcow', 'qcow2', 'raw', 'img', 'iso'];
export default function imageUrl(url, getters, errors, validatorArgs, type) {
const t = getters['i18n/t'];
if (!url || url === '') {
return errors;
}
const suffixName = url.split('/').pop();
const fileSuffix = suffixName
.split('.')
.pop()
.toLowerCase();
if (!VM_IMAGE_FILE_FORMAT.includes(fileSuffix)) {
const tipString =
type === 'file' ? 'harvester.validation.image.ruleFileTip' : 'harvester.validation.image.ruleTip';
errors.push(t(tipString));
}
return errors;
}

View File

@ -1,70 +0,0 @@
export default function vmNetworks(spec, getters, errors, validatorArgs) {
const { domain: { devices: { interfaces } }, networks } = spec;
const allNames = new Set();
interfaces.map( (I, index) => {
allNames.add(I.name);
const N = networks.find( N => I.name === N.name);
const prefix = (I.name || N.name) || index + 1;
if (I.name.length > 20) {
const message = getters['i18n/t']('harvester.validation.custom.tooLongName', { max: 20 });
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
if (!I.name || !N.name) {
const message = getters['i18n/t']('harvester.validation.vm.name');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
if (N.multus) {
if (!N.multus.networkName) {
const message = getters['i18n/t']('harvester.validation.vm.network.name');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
}
if (I.macAddress && !isValidMac(I.macAddress) && !N.pod) {
const message = getters['i18n/t']('harvester.validation.vm.network.macFormat');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
const portsName = new Set();
const portsNumber = new Set();
if (I.masquerade && I.ports) {
const ports = I?.ports || [];
ports.forEach((P) => {
portsName.add(P.name);
portsNumber.add(P.port);
});
if (portsName.size !== I.ports.length) {
const message = getters['i18n/t']('harvester.validation.vm.network.duplicatedPortName');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
if (portsNumber.size !== I.ports.length) {
const message = getters['i18n/t']('harvester.validation.vm.network.duplicatedPortNumber');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
}
});
if (allNames.size !== interfaces.length) {
errors.push(getters['i18n/t']('harvester.validation.vm.network.duplicatedName'));
}
return errors;
}
function isValidMac(value) {
return /^[A-Fa-f0-9]{2}(-[A-Fa-f0-9]{2}){5}$|^[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){5}$/.test(value);
}

View File

@ -22,6 +22,7 @@ export class Plugin implements IPlugin {
// Plugin metadata (plugin package.json)
public _metadata: any = {};
public _validators: { [key: string]: Function } = {};
// Is this a built-in plugin (bundled with the application)
public builtin = false;
@ -43,6 +44,14 @@ export class Plugin implements IPlugin {
this.name = this._metadata.name || this.id;
}
get validators() {
return this._validators;
}
set validators(vals:{ [key: string]: Function }) {
this._validators = vals;
}
// Track which products the plugin creates
DSL(store: any, productName: string) {
const storeDSL = STORE_DSL(store, productName);

View File

@ -1,6 +1,6 @@
const fs = require('fs');
const path = require('path');
const contextFolders = ['chart', 'cloud-credential', 'content', 'detail', 'edit', 'list', 'machine-config', 'models', 'promptRemove', 'l10n', 'windowComponents', 'dialog', 'validators'];
const contextFolders = ['chart', 'cloud-credential', 'content', 'detail', 'edit', 'list', 'machine-config', 'models', 'promptRemove', 'l10n', 'windowComponents', 'dialog'];
const contextMap = contextFolders.reduce((map, obj) => {
map[obj] = true;

File diff suppressed because it is too large Load Diff

View File

@ -409,25 +409,6 @@ export const getters = {
if (state.isSingleProduct !== undefined) {
return state.isSingleProduct;
}
// TODO not this
if (rootGetters.isSingleVirtualCluster) {
// return {
// logo: require('~shell/assets/images/providers/harvester.svg'),
// productNameKey: 'product.harvester',
// version: rootGetters['harvester/byId'](HCI.SETTING, 'server-version')?.value,
// afterLoginRoute: {
// name: 'c-cluster-product',
// params: { product: VIRTUAL },
// },
// logoRoute: {
// name: 'c-cluster-product-resource',
// params: {
// product: VIRTUAL,
// resource: HCI.DASHBOARD,
// }
// },
// };
}
return false;
},

View File

@ -798,7 +798,13 @@ export const getters = {
allTypes(state, getters, rootState, rootGetters) {
return (product, mode = ALL) => {
const module = findBy(state.products, 'name', product).inStore;
let module;
try {
module = findBy(state.products, 'name', product).inStore;
} catch {
debugger;
}
const schemas = rootGetters[`${ module }/all`](SCHEMA);
const counts = rootGetters[`${ module }/all`](COUNT)?.[0]?.counts || {};
const isDev = rootGetters['prefs/get'](DEV);

View File

@ -1,14 +1,7 @@
import { flowOutput } from '@shell/utils/validators/flow-output';
import { logdna } from '@shell/utils/validators/logging-outputs';
import {
clusterIp,
externalName,
servicePort
} from '@shell/utils/validators/service';
import {
ruleGroups,
groupsAreValid
} from '@shell/utils/validators/prometheusrule';
import { clusterIp, externalName, servicePort } from '@shell/utils/validators/service';
import { ruleGroups, groupsAreValid } from '@shell/utils/validators/prometheusrule';
import { interval, matching } from '@shell/utils/validators/monitoring-route';
import { containerImages } from '@shell/utils/validators/container-images';
import { cronSchedule } from '@shell/utils/validators/cron-schedule';
@ -17,11 +10,16 @@ import { roleTemplateRules } from '@shell/utils/validators/role-template';
import { clusterName } from '@shell/utils/validators/cluster-name';
import { isHttps, backupTarget } from '@shell/utils/validators/setting';
import { imageUrl, fileRequired } from '@shell/utils/validators/vm-image';
import { vmNetworks, vmDisks } from '@shell/utils/validators/vm';
import { dataVolumeSize } from '@shell/utils/validators/vm-datavolumes';
/**
* Custom validation functions beyond normal scalr types
* Validator must export a function name should match the validator name on the customValidationRules rule
* Exported function is used as a lookup key in resource-class:validationErrors:customValidationRules loop
*/
* Custom validation functions beyond normal scalr types
* Validator must export a function name should match the validator name on the customValidationRules rule
* Exported function is used as a lookup key in resource-class:validationErrors:customValidationRules loop
*/
export default {
clusterName,
clusterIp,
@ -38,5 +36,10 @@ export default {
podAffinity,
roleTemplateRules,
isHttps,
backupTarget
backupTarget,
imageUrl,
dataVolumeSize,
vmNetworks,
vmDisks,
fileRequired,
};

View File

@ -1,6 +1,6 @@
import { formatSi, parseSi } from '@shell/utils/units';
export default function dataVolumeSize(storage, getters, errors, validatorArgs) {
export function dataVolumeSize(storage, getters, errors, validatorArgs) {
const t = getters['i18n/t'];
if (!storage || storage === '') {

View File

@ -0,0 +1,32 @@
import { HCI } from '@shell/config/labels-annotations';
export const VM_IMAGE_FILE_FORMAT = ['qcow', 'qcow2', 'raw', 'img', 'iso'];
export function imageUrl(url, getters, errors, validatorArgs, type) {
const t = getters['i18n/t'];
if (!url || url === '') {
return errors;
}
const suffixName = url.split('/').pop();
const fileSuffix = suffixName.split('.').pop().toLowerCase();
if (!VM_IMAGE_FILE_FORMAT.includes(fileSuffix)) {
const tipString = type === 'file' ? 'harvester.validation.image.ruleFileTip' : 'harvester.validation.image.ruleTip';
errors.push(t(tipString));
}
return errors;
}
export function fileRequired(annotations = {}, getters, errors, validatorArgs, type) {
const t = getters['i18n/t'];
if (!annotations[HCI.IMAGE_NAME]) {
errors.push(t('validation.required', { key: t('harvester.image.fileName') }));
}
return errors;
}

View File

@ -2,7 +2,74 @@ import { PVC } from '@shell/config/types';
import { SOURCE_TYPE } from '@shell/config/harvester-map';
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
export default function vmDisks(spec, getters, errors, validatorArgs, displayKey, value) {
export function vmNetworks(spec, getters, errors, validatorArgs) {
const { domain: { devices: { interfaces } }, networks } = spec;
const allNames = new Set();
interfaces.map( (I, index) => {
allNames.add(I.name);
const N = networks.find( N => I.name === N.name);
const prefix = (I.name || N.name) || index + 1;
if (I.name.length > 20) {
const message = getters['i18n/t']('harvester.validation.custom.tooLongName', { max: 20 });
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
if (!I.name || !N.name) {
const message = getters['i18n/t']('harvester.validation.vm.name');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
if (N.multus) {
if (!N.multus.networkName) {
const message = getters['i18n/t']('harvester.validation.vm.network.name');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
}
if (I.macAddress && !isValidMac(I.macAddress) && !N.pod) {
const message = getters['i18n/t']('harvester.validation.vm.network.macFormat');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
const portsName = new Set();
const portsNumber = new Set();
if (I.masquerade && I.ports) {
const ports = I?.ports || [];
ports.forEach((P) => {
portsName.add(P.name);
portsNumber.add(P.port);
});
if (portsName.size !== I.ports.length) {
const message = getters['i18n/t']('harvester.validation.vm.network.duplicatedPortName');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
if (portsNumber.size !== I.ports.length) {
const message = getters['i18n/t']('harvester.validation.vm.network.duplicatedPortNumber');
errors.push(getters['i18n/t']('harvester.validation.vm.network.error', { prefix, message }));
}
}
});
if (allNames.size !== interfaces.length) {
errors.push(getters['i18n/t']('harvester.validation.vm.network.duplicatedName'));
}
return errors;
}
export function vmDisks(spec, getters, errors, validatorArgs, displayKey, value) {
const isVMTemplate = validatorArgs.includes('isVMTemplate');
const data = isVMTemplate ? this.value.spec.vm : value;
@ -109,7 +176,11 @@ export default function vmDisks(spec, getters, errors, validatorArgs, displayKey
return errors;
}
function getVolumeType(V, DVTS) {
export function isValidMac(value) {
return /^[A-Fa-f0-9]{2}(-[A-Fa-f0-9]{2}){5}$|^[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){5}$/.test(value);
}
export function getVolumeType(V, DVTS) {
let outValue = null;
if (V.persistentVolumeClaim) {