mirror of https://github.com/rancher/dashboard.git
fix conflict
This commit is contained in:
parent
840d1518f1
commit
38dfc71654
|
|
@ -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>
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 === '') {
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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) {
|
||||
Loading…
Reference in New Issue