mirror of https://github.com/rancher/dashboard.git
fix conflict
This commit is contained in:
parent
840d1518f1
commit
38dfc71654
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import compact from 'lodash/compact';
|
import compact from 'lodash/compact';
|
||||||
|
import { OFF } from '../../models/harvester/kubevirt.io.virtualmachine';
|
||||||
import { get } from '@shell/utils/object';
|
import { get } from '@shell/utils/object';
|
||||||
import { isIpv4 } from '@shell/utils/string';
|
import { isIpv4 } from '@shell/utils/string';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
||||||
|
|
@ -13,12 +14,16 @@ export default {
|
||||||
components: { CopyToClipboard },
|
components: { CopyToClipboard },
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
row: {
|
row: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
col: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -31,16 +36,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
networkAnnotationIP() {
|
networkAnnotationIP() {
|
||||||
if (this.row.actualState !== 'Running') {
|
if (this.row.actualState !== 'Running') { // TODO: Running
|
||||||
// TODO: Running
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const annotationIp =
|
const annotationIp = get(this.row, `metadata.annotations."${ HCI_ANNOTATIONS.NETWORK_IPS }"`) || '[]';
|
||||||
get(
|
|
||||||
this.row,
|
|
||||||
`metadata.annotations."${ HCI_ANNOTATIONS.NETWORK_IPS }"`
|
|
||||||
) || '[]';
|
|
||||||
|
|
||||||
// Obtain IP from VM annotation, remove the CIDR suffix number if CIDR Exist
|
// Obtain IP from VM annotation, remove the CIDR suffix number if CIDR Exist
|
||||||
try {
|
try {
|
||||||
|
|
@ -82,8 +82,8 @@ export default {
|
||||||
|
|
||||||
showIP() {
|
showIP() {
|
||||||
return this.row.stateDisplay !== OFF;
|
return this.row.stateDisplay !== OFF;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -4,9 +4,9 @@ import { Banner } from '@components/Banner';
|
||||||
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
||||||
import { LONGHORN, METRIC, HCI } from '@shell/config/types';
|
import { LONGHORN, METRIC, HCI } from '@shell/config/types';
|
||||||
import HarvesterCPUUsed from '@shell/components/formatter/HarvesterCPUUsed';
|
import HarvesterCPUUsed from '../../components/formatter/HarvesterCPUUsed';
|
||||||
import HarvesterMemoryUsed from '@shell/components/formatter/HarvesterMemoryUsed';
|
import HarvesterMemoryUsed from '../../components/formatter/HarvesterMemoryUsed';
|
||||||
import HarvesterStorageUsed from '@shell/components/formatter/HarvesterStorageUsed';
|
import HarvesterStorageUsed from '../../components/formatter/HarvesterStorageUsed';
|
||||||
|
|
||||||
const COMPLETE = 'complete';
|
const COMPLETE = 'complete';
|
||||||
const NONE = 'none';
|
const NONE = 'none';
|
||||||
|
|
@ -21,13 +21,13 @@ export default {
|
||||||
Banner,
|
Banner,
|
||||||
HarvesterCPUUsed,
|
HarvesterCPUUsed,
|
||||||
HarvesterMemoryUsed,
|
HarvesterMemoryUsed,
|
||||||
HarvesterStorageUsed
|
HarvesterStorageUsed,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
metrics: {
|
metrics: {
|
||||||
|
|
@ -55,25 +55,18 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
customName() {
|
customName() {
|
||||||
return this.value.metadata?.annotations?.[
|
return this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CUSTOM_NAME];
|
||||||
HCI_ANNOTATIONS.HOST_CUSTOM_NAME
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
consoleUrl() {
|
consoleUrl() {
|
||||||
const consoleUrl = this.value.metadata?.annotations?.[
|
const consoleUrl = this.value.metadata?.annotations?.[HCI_ANNOTATIONS.HOST_CONSOLE_URL];
|
||||||
HCI_ANNOTATIONS.HOST_CONSOLE_URL
|
|
||||||
];
|
|
||||||
let value = consoleUrl;
|
let value = consoleUrl;
|
||||||
|
|
||||||
if (!consoleUrl) {
|
if (!consoleUrl) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!consoleUrl.startsWith('http://') && !consoleUrl.startsWith('https://')) {
|
||||||
!consoleUrl.startsWith('http://') &&
|
|
||||||
!consoleUrl.startsWith('https://')
|
|
||||||
) {
|
|
||||||
value = `http://${ consoleUrl }`;
|
value = `http://${ consoleUrl }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,10 +118,7 @@ export default {
|
||||||
|
|
||||||
storageUsage() {
|
storageUsage() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const longhornNode = this.$store.getters[`${ inStore }/byId`](
|
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.value.id }`);
|
||||||
LONGHORN.NODES,
|
|
||||||
`longhorn-system/${ this.value.id }`
|
|
||||||
);
|
|
||||||
let out = 0;
|
let out = 0;
|
||||||
|
|
||||||
const diskStatus = longhornNode?.status?.diskStatus || {};
|
const diskStatus = longhornNode?.status?.diskStatus || {};
|
||||||
|
|
@ -144,10 +134,7 @@ export default {
|
||||||
|
|
||||||
storageTotal() {
|
storageTotal() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const longhornNode = this.$store.getters[`${ inStore }/byId`](
|
const longhornNode = this.$store.getters[`${ inStore }/byId`](LONGHORN.NODES, `longhorn-system/${ this.value.id }`);
|
||||||
LONGHORN.NODES,
|
|
||||||
`longhorn-system/${ this.value.id }`
|
|
||||||
);
|
|
||||||
let out = 0;
|
let out = 0;
|
||||||
|
|
||||||
const diskStatus = longhornNode?.status?.diskStatus || {};
|
const diskStatus = longhornNode?.status?.diskStatus || {};
|
||||||
|
|
@ -186,15 +173,8 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
nodeRoleState() {
|
nodeRoleState() {
|
||||||
const isExistRoleStatus =
|
const isExistRoleStatus = this.value.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_MASTER] !== undefined || this.value.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_CONTROL_PLANE] !== undefined;
|
||||||
this.value.metadata?.labels?.[HCI_ANNOTATIONS.NODE_ROLE_MASTER] !==
|
const promoteStatus = this.value.metadata?.annotations?.[HCI_ANNOTATIONS.PROMOTE_STATUS] || NONE;
|
||||||
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) {
|
if (!isExistRoleStatus && promoteStatus === COMPLETE) {
|
||||||
return PROMOTE_RESTART;
|
return PROMOTE_RESTART;
|
||||||
|
|
@ -227,7 +207,7 @@ export default {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.NODE_NETWORK);
|
return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.NODE_NETWORK);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -241,7 +221,7 @@ export default {
|
||||||
};
|
};
|
||||||
|
|
||||||
return formatSi(value, formatOptions);
|
return formatSi(value, formatOptions);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -251,25 +231,16 @@ export default {
|
||||||
<h3>{{ t('harvester.host.tabs.overview') }}</h3>
|
<h3>{{ t('harvester.host.tabs.overview') }}</h3>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.customName')" :value="customName" />
|
||||||
:name="t('harvester.host.detail.customName')"
|
|
||||||
:value="customName"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.hostIP')" :value="value.internalIp" />
|
||||||
:name="t('harvester.host.detail.hostIP')"
|
|
||||||
:value="value.internalIp"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.os')" :value="value.status.nodeInfo.osImage" />
|
||||||
:name="t('harvester.host.detail.os')"
|
|
||||||
:value="value.status.nodeInfo.osImage"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<div class="role">
|
<div class="role">
|
||||||
|
|
@ -287,28 +258,17 @@ export default {
|
||||||
|
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.create')" :value="value.metadata.creationTimestamp" />
|
||||||
:name="t('harvester.host.detail.create')"
|
|
||||||
:value="value.metadata.creationTimestamp"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.update')" :value="lastUpdateTime" />
|
||||||
:name="t('harvester.host.detail.update')"
|
|
||||||
:value="lastUpdateTime"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.consoleUrl')" :value="consoleUrl.value">
|
||||||
:name="t('harvester.host.detail.consoleUrl')"
|
<a slot="value" :href="consoleUrl.value" target="_blank">{{ consoleUrl.display }}</a>
|
||||||
:value="consoleUrl.value"
|
|
||||||
>
|
|
||||||
<a slot="value" :href="consoleUrl.value" target="_blank">{{
|
|
||||||
consoleUrl.display
|
|
||||||
}}</a>
|
|
||||||
</LabelValue>
|
</LabelValue>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -321,10 +281,7 @@ export default {
|
||||||
</Banner>
|
</Banner>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.networkType')" :value="networkType" />
|
||||||
:name="t('harvester.host.detail.networkType')"
|
|
||||||
:value="networkType"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
|
|
@ -365,24 +322,15 @@ export default {
|
||||||
<h3>{{ t('harvester.host.detail.more') }}</h3>
|
<h3>{{ t('harvester.host.detail.more') }}</h3>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.uuid')" :value="value.status.nodeInfo.systemUUID" />
|
||||||
:name="t('harvester.host.detail.uuid')"
|
|
||||||
:value="value.status.nodeInfo.systemUUID"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.kernel')" :value="value.status.nodeInfo.kernelVersion" />
|
||||||
:name="t('harvester.host.detail.kernel')"
|
|
||||||
:value="value.status.nodeInfo.kernelVersion"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col span-4">
|
<div class="col span-4">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.host.detail.containerRuntime')" :value="value.status.nodeInfo.containerRuntimeVersion" />
|
||||||
:name="t('harvester.host.detail.containerRuntime')"
|
|
||||||
:value="value.status.nodeInfo.containerRuntimeVersion"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { STATE, AGE, NAME } from '@shell/config/table-headers';
|
import { STATE, AGE, NAME } from '@shell/config/table-headers';
|
||||||
import SortableTable from '@shell/components/SortableTable';
|
import SortableTable from '@shell/components/SortableTable';
|
||||||
import Loading from '@shell/components/Loading';
|
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 { allHash } from '@shell/utils/promise';
|
||||||
import { HCI } from '@shell/config/types';
|
import { HCI } from '@shell/config/types';
|
||||||
import { HOSTNAME } from '@shell/config/labels-annotations';
|
import { HOSTNAME } from '@shell/config/labels-annotations';
|
||||||
|
|
@ -13,22 +13,22 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
SortableTable,
|
SortableTable,
|
||||||
Loading,
|
Loading,
|
||||||
HarvesterVmState
|
HarvesterVmState,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
node: {
|
node: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
const hash = await allHash({
|
const hash = await allHash({
|
||||||
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
|
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
|
||||||
vmis: this.$store.dispatch('harvester/findAll', { type: HCI.VMI }),
|
vmis: this.$store.dispatch('harvester/findAll', { type: HCI.VMI }),
|
||||||
allNodeNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.NODE_NETWORK }),
|
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 = {};
|
const instanceMap = {};
|
||||||
|
|
||||||
|
|
@ -43,10 +43,7 @@ export default {
|
||||||
this.allNodeNetwork = hash.allNodeNetwork;
|
this.allNodeNetwork = hash.allNodeNetwork;
|
||||||
this.allClusterNetwork = hash.allClusterNetwork;
|
this.allClusterNetwork = hash.allClusterNetwork;
|
||||||
this.rows = hash.vms.filter((row) => {
|
this.rows = hash.vms.filter((row) => {
|
||||||
return (
|
return instanceMap[row.metadata?.uid]?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME];
|
||||||
instanceMap[row.metadata?.uid]?.status?.nodeName ===
|
|
||||||
this.node?.metadata?.labels?.[HOSTNAME]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -64,20 +61,20 @@ export default {
|
||||||
STATE,
|
STATE,
|
||||||
NAME,
|
NAME,
|
||||||
{
|
{
|
||||||
name: 'vmCPU',
|
name: 'vmCPU',
|
||||||
labelKey: 'tableHeaders.cpu',
|
labelKey: 'tableHeaders.cpu',
|
||||||
sort: 'vmCPU',
|
sort: 'vmCPU',
|
||||||
search: false,
|
search: false,
|
||||||
value: 'spec.template.spec.domain.cpu.cores',
|
value: 'spec.template.spec.domain.cpu.cores',
|
||||||
width: 120
|
width: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vmRAM',
|
name: 'vmRAM',
|
||||||
labelKey: 'glance.memory',
|
labelKey: 'glance.memory',
|
||||||
sort: 'vmRAM',
|
sort: 'vmRAM',
|
||||||
search: false,
|
search: false,
|
||||||
value: 'spec.template.spec.domain.resources.limits.memory',
|
value: 'spec.template.spec.domain.resources.limits.memory',
|
||||||
width: 120
|
width: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ip',
|
name: 'ip',
|
||||||
|
|
@ -88,10 +85,10 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...AGE,
|
...AGE,
|
||||||
sort: 'metadata.creationTimestamp:desc'
|
sort: 'metadata.creationTimestamp:desc',
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {}
|
methods: {}
|
||||||
|
|
@ -112,15 +109,10 @@ export default {
|
||||||
>
|
>
|
||||||
<template slot="cell:state" slot-scope="scope" class="state-col">
|
<template slot="cell:state" slot-scope="scope" class="state-col">
|
||||||
<div class="state">
|
<div class="state">
|
||||||
<HarvesterVmState
|
<HarvesterVmState class="vmstate" :row="scope.row" :all-node-network="allNodeNetwork" :all-cluster-network="allClusterNetwork" />
|
||||||
class="vmstate"
|
|
||||||
:row="scope.row"
|
|
||||||
:all-node-network="allNodeNetwork"
|
|
||||||
:all-cluster-network="allClusterNetwork"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</SortableTable>
|
</Sortabletable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import HarvesterIpAddress from '@shell/components/formatter/HarvesterIpAddress';
|
import HarvesterIpAddress from '../../../components/formatter/HarvesterIpAddress';
|
||||||
import VMConsoleBar from '../../../components/VMConsoleBar';
|
import VMConsoleBar from '../../../components/VMConsoleBar';
|
||||||
import LabelValue from '@shell/components/LabelValue';
|
import LabelValue from '@shell/components/LabelValue';
|
||||||
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
||||||
|
|
@ -34,8 +34,8 @@ export default {
|
||||||
},
|
},
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -46,8 +46,7 @@ export default {
|
||||||
return UNDEFINED;
|
return UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${ date.getMonth() +
|
return `${ date.getMonth() + 1 }/${ date.getDate() }/${ date.getUTCFullYear() }`;
|
||||||
1 }/${ date.getDate() }/${ date.getUTCFullYear() }`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
node() {
|
node() {
|
||||||
|
|
@ -55,16 +54,13 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
hostname() {
|
hostname() {
|
||||||
return (
|
return this.resource?.spec?.hostname || this.resource?.status?.guestOSInfo?.hostname;
|
||||||
this.resource?.spec?.hostname ||
|
|
||||||
this.resource?.status?.guestOSInfo?.hostname
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
imageName() {
|
imageName() {
|
||||||
const imageList = this.$store.getters['harvester/all'](HCI.IMAGE) || [];
|
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;
|
return this.value.rootImageId === I.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -72,25 +68,21 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
disks() {
|
disks() {
|
||||||
const disks =
|
const disks = this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
|
||||||
this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
|
|
||||||
|
|
||||||
return disks
|
return disks.filter((disk) => {
|
||||||
.filter((disk) => {
|
return !!disk.bootOrder;
|
||||||
return !!disk.bootOrder;
|
}).sort((a, b) => {
|
||||||
})
|
if (a.bootOrder < b.bootOrder) {
|
||||||
.sort((a, b) => {
|
return -1;
|
||||||
if (a.bootOrder < b.bootOrder) {
|
}
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
cdroms() {
|
cdroms() {
|
||||||
const disks =
|
const disks = this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
|
||||||
this.value?.spec?.template?.spec?.domain?.devices?.disks || [];
|
|
||||||
|
|
||||||
return disks.filter((disk) => {
|
return disks.filter((disk) => {
|
||||||
return !!disk.cdrom;
|
return !!disk.cdrom;
|
||||||
|
|
@ -100,9 +92,7 @@ export default {
|
||||||
flavor() {
|
flavor() {
|
||||||
const domain = this.value?.spec?.template?.spec?.domain;
|
const domain = this.value?.spec?.template?.spec?.domain;
|
||||||
|
|
||||||
return `${ domain.cpu?.cores } vCPU , ${
|
return `${ domain.cpu?.cores } vCPU , ${ domain.resources?.limits?.memory } ${ this.t('harvester.virtualMachine.input.memory') }`;
|
||||||
domain.resources?.limits?.memory
|
|
||||||
} ${ this.t('harvester.virtualMachine.input.memory') }`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
kernelRelease() {
|
kernelRelease() {
|
||||||
|
|
@ -118,9 +108,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
machineType() {
|
machineType() {
|
||||||
return (
|
return this.value?.spec?.template?.spec?.domain?.machine?.type || undefined;
|
||||||
this.value?.spec?.template?.spec?.domain?.machine?.type || undefined
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -143,15 +131,11 @@ export default {
|
||||||
<div class="overview-basics">
|
<div class="overview-basics">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.name')" :value="value.nameDisplay">
|
||||||
:name="t('harvester.virtualMachine.detail.details.name')"
|
|
||||||
:value="value.nameDisplay"
|
|
||||||
>
|
|
||||||
<template #value>
|
<template #value>
|
||||||
<div class="smart-row">
|
<div class="smart-row">
|
||||||
<div class="console">
|
<div class="console">
|
||||||
{{ value.nameDisplay }}
|
{{ value.nameDisplay }} <VMConsoleBar :resource="value" class="consoleBut" />
|
||||||
<VMConsoleBar :resource="value" class="consoleBut" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -165,35 +149,26 @@ export default {
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.hostname')" :value="hostname">
|
||||||
:name="t('harvester.virtualMachine.detail.details.hostname')"
|
|
||||||
:value="hostname"
|
|
||||||
>
|
|
||||||
<template #value>
|
<template #value>
|
||||||
<div v-if="!isDown">
|
<div v-if="!isDown">
|
||||||
{{
|
{{ hostname || t("harvester.virtualMachine.detail.GuestAgentNotInstalled") }}
|
||||||
hostname ||
|
|
||||||
t('harvester.virtualMachine.detail.GuestAgentNotInstalled')
|
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ t('harvester.virtualMachine.detail.details.down') }}
|
{{ t("harvester.virtualMachine.detail.details.down") }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</LabelValue>
|
</LabelValue>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.node')" :value="node">
|
||||||
:name="t('harvester.virtualMachine.detail.details.node')"
|
|
||||||
:value="node"
|
|
||||||
>
|
|
||||||
<template #value>
|
<template #value>
|
||||||
<div v-if="!isDown">
|
<div v-if="!isDown">
|
||||||
{{ node }}
|
{{ node }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
{{ t('harvester.virtualMachine.detail.details.down') }}
|
{{ t("harvester.virtualMachine.detail.details.down") }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</LabelValue>
|
</LabelValue>
|
||||||
|
|
@ -202,9 +177,7 @@ export default {
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.ipAddress')">
|
||||||
:name="t('harvester.virtualMachine.detail.details.ipAddress')"
|
|
||||||
>
|
|
||||||
<template #value>
|
<template #value>
|
||||||
<HarvesterIpAddress v-model="value.id" :row="value" />
|
<HarvesterIpAddress v-model="value.id" :row="value" />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -212,10 +185,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.created')" :value="creationTimestamp" />
|
||||||
:name="t('harvester.virtualMachine.detail.details.created')"
|
|
||||||
:value="creationTimestamp"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -225,37 +195,27 @@ export default {
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<InputOrDisplay
|
<InputOrDisplay :name="t('harvester.virtualMachine.detail.details.bootOrder')" :value="disks" :mode="mode">
|
||||||
:name="t('harvester.virtualMachine.detail.details.bootOrder')"
|
|
||||||
:value="disks"
|
|
||||||
:mode="mode"
|
|
||||||
>
|
|
||||||
<template #value>
|
<template #value>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="disk in disks" :key="disk.bootOrder">
|
<li v-for="(disk) in disks" :key="disk.bootOrder">
|
||||||
{{ disk.bootOrder }}. {{ disk.name }} ({{
|
{{ disk.bootOrder }}. {{ disk.name }} ({{ getDeviceType(disk) }})
|
||||||
getDeviceType(disk)
|
|
||||||
}})
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
</InputOrDisplay>
|
</InputOrDisplay>
|
||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<InputOrDisplay
|
<InputOrDisplay :name="t('harvester.virtualMachine.detail.details.CDROMs')" :value="cdroms" :mode="mode">
|
||||||
:name="t('harvester.virtualMachine.detail.details.CDROMs')"
|
|
||||||
:value="cdroms"
|
|
||||||
:mode="mode"
|
|
||||||
>
|
|
||||||
<template #value>
|
<template #value>
|
||||||
<div>
|
<div>
|
||||||
<ul v-if="cdroms.length > 0">
|
<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 }}
|
{{ rom.name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ t('harvester.virtualMachine.detail.notAvailable') }}
|
{{ t("harvester.virtualMachine.detail.notAvailable") }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -264,74 +224,56 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.operatingSystem')" :value="operatingSystem || t('harvester.virtualMachine.detail.GuestAgentNotInstalled')" />
|
||||||
:name="t('harvester.virtualMachine.detail.details.operatingSystem')"
|
|
||||||
:value="
|
|
||||||
operatingSystem ||
|
|
||||||
t('harvester.virtualMachine.detail.GuestAgentNotInstalled')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.flavor')" :value="flavor" />
|
||||||
:name="t('harvester.virtualMachine.detail.details.flavor')"
|
|
||||||
:value="flavor"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.detail.details.kernelRelease')" :value="kernelRelease || t('harvester.virtualMachine.detail.GuestAgentNotInstalled')" />
|
||||||
:name="t('harvester.virtualMachine.detail.details.kernelRelease')"
|
|
||||||
:value="
|
|
||||||
kernelRelease ||
|
|
||||||
t('harvester.virtualMachine.detail.GuestAgentNotInstalled')
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue :name="t('harvester.virtualMachine.input.MachineType')" :value="machineType" />
|
||||||
:name="t('harvester.virtualMachine.input.MachineType')"
|
|
||||||
:value="machineType"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.consoleBut {
|
.consoleBut {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -20px;
|
top: -20px;
|
||||||
left: 38px;
|
left: 38px;
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
.overview-basics {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: row;
|
grid-template-columns: 100%;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-row-gap: 15px;
|
||||||
|
|
||||||
.console {
|
.badge-state {
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smart-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
.console {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__ssh-key {
|
||||||
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__name {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__ssh-key {
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,10 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterHotplugModal',
|
name: 'HotplugModal',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
AsyncButton,
|
AsyncButton, Card, LabeledInput, LabeledSelect, Banner
|
||||||
Card,
|
|
||||||
LabeledInput,
|
|
||||||
LabeledSelect,
|
|
||||||
Banner
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -37,7 +33,7 @@ export default {
|
||||||
diskName: '',
|
diskName: '',
|
||||||
volumeName: '',
|
volumeName: '',
|
||||||
errors: [],
|
errors: [],
|
||||||
allPVCs: []
|
allPVCs: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -45,11 +41,7 @@ export default {
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
...mapGetters({ t: 'i18n/t' }),
|
||||||
|
|
||||||
PVCs() {
|
PVCs() {
|
||||||
return (
|
return this.allPVCs.filter(P => this.actionResource.metadata.namespace === P.metadata.namespace) || [];
|
||||||
this.allPVCs.filter(
|
|
||||||
P => this.actionResource.metadata.namespace === P.metadata.namespace
|
|
||||||
) || []
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actionResource() {
|
actionResource() {
|
||||||
|
|
@ -58,21 +50,23 @@ export default {
|
||||||
|
|
||||||
volumeOption() {
|
volumeOption() {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
this.PVCs.filter((pvc) => {
|
this.PVCs
|
||||||
if (!!pvc.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID]) {
|
.filter( (pvc) => {
|
||||||
return false;
|
if (!!pvc.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID]) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return !pvc.attachVM;
|
return !pvc.attachVM;
|
||||||
}).map((pvc) => {
|
})
|
||||||
return {
|
.map((pvc) => {
|
||||||
label: pvc.metadata.name,
|
return {
|
||||||
value: pvc.metadata.name
|
label: pvc.metadata.name,
|
||||||
};
|
value: pvc.metadata.name
|
||||||
}),
|
};
|
||||||
|
}),
|
||||||
'label'
|
'label'
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -85,25 +79,13 @@ export default {
|
||||||
async save(buttonCb) {
|
async save(buttonCb) {
|
||||||
if (this.actionResource) {
|
if (this.actionResource) {
|
||||||
try {
|
try {
|
||||||
const res = await this.actionResource.doAction(
|
const res = await this.actionResource.doAction('addVolume', { volumeSourceName: this.volumeName, diskName: this.diskName }, {}, false);
|
||||||
'addVolume',
|
|
||||||
{ volumeSourceName: this.volumeName, diskName: this.diskName },
|
|
||||||
{},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res._status === 200 || res._status === 204) {
|
if (res._status === 200 || res._status === 204) {
|
||||||
this.$store.dispatch(
|
this.$store.dispatch('growl/success', {
|
||||||
'growl/success',
|
title: this.t('harvester.notification.title.succeed'),
|
||||||
{
|
message: this.t('harvester.modal.hotplug.success', { diskName: this.diskName, vm: this.actionResource.nameDisplay })
|
||||||
title: this.t('harvester.notification.title.succeed'),
|
}, { root: true });
|
||||||
message: this.t('harvester.modal.hotplug.success', {
|
|
||||||
diskName: this.diskName,
|
|
||||||
vm: this.actionResource.nameDisplay
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ root: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
buttonCb(true);
|
buttonCb(true);
|
||||||
|
|
@ -121,21 +103,21 @@ export default {
|
||||||
buttonCb(false);
|
buttonCb(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Card ref="modal" name="modal" :show-highlight-border="false">
|
<Card ref="modal" name="modal" :show-highlight-border="false">
|
||||||
<h4
|
<h4 slot="title" class="text-default-text" v-html="t('harvester.modal.hotplug.title')" />
|
||||||
slot="title"
|
|
||||||
class="text-default-text"
|
|
||||||
v-html="t('harvester.modal.hotplug.title')"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<LabeledInput v-model="diskName" :label="t('generic.name')" required />
|
<LabeledInput
|
||||||
|
v-model="diskName"
|
||||||
|
:label="t('generic.name')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model="volumeName"
|
v-model="volumeName"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import LabelValue from '@shell/components/LabelValue';
|
||||||
import Select from '@shell/components/form/Select';
|
import Select from '@shell/components/form/Select';
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
import { OS } from '../mixins/harvester-vm';
|
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 { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
||||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ export default {
|
||||||
LabeledInput,
|
LabeledInput,
|
||||||
NameNsDescription,
|
NameNsDescription,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
LabelValue
|
LabelValue,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView],
|
||||||
|
|
@ -38,12 +38,12 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
if (!this.value.spec) {
|
if ( !this.value.spec ) {
|
||||||
this.$set(this.value, 'spec', { sourceType: DOWNLOAD });
|
this.$set(this.value, 'spec', { sourceType: DOWNLOAD });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,12 +52,12 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: this.value.spec.url,
|
url: this.value.spec.url,
|
||||||
files: [],
|
files: [],
|
||||||
resource: '',
|
resource: '',
|
||||||
headers: {},
|
headers: {},
|
||||||
fileUrl: '',
|
fileUrl: '',
|
||||||
file: ''
|
file: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -67,9 +67,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
imageName() {
|
imageName() {
|
||||||
return (
|
return this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-';
|
||||||
this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-'
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isCreateEdit() {
|
isCreateEdit() {
|
||||||
|
|
@ -85,10 +83,7 @@ export default {
|
||||||
'value.spec.url'(neu) {
|
'value.spec.url'(neu) {
|
||||||
const url = neu.trim();
|
const url = neu.trim();
|
||||||
const suffixName = url.split('/').pop();
|
const suffixName = url.split('/').pop();
|
||||||
const fileSuffix = suffixName
|
const fileSuffix = suffixName.split('.').pop().toLowerCase();
|
||||||
.split('.')
|
|
||||||
.pop()
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
this.value.spec.url = url;
|
this.value.spec.url = url;
|
||||||
if (VM_IMAGE_FILE_FORMAT.includes(fileSuffix)) {
|
if (VM_IMAGE_FILE_FORMAT.includes(fileSuffix)) {
|
||||||
|
|
@ -106,7 +101,7 @@ export default {
|
||||||
if (!this.value.spec.displayName) {
|
if (!this.value.spec.displayName) {
|
||||||
this.$refs.nd.changeNameAndNamespace({
|
this.$refs.nd.changeNameAndNamespace({
|
||||||
text: suffixName,
|
text: suffixName,
|
||||||
selected: this.value.metadata.namespace
|
selected: this.value.metadata.namespace,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +125,7 @@ export default {
|
||||||
if (this.$refs?.file?.value) {
|
if (this.$refs?.file?.value) {
|
||||||
this.$refs.file.value = null;
|
this.$refs.file.value = null;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -143,8 +138,7 @@ export default {
|
||||||
|
|
||||||
const file = this.file;
|
const file = this.file;
|
||||||
|
|
||||||
this.value.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] =
|
this.value.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] = file?.name;
|
||||||
file?.name;
|
|
||||||
|
|
||||||
const res = await this.value.save();
|
const res = await this.value.save();
|
||||||
|
|
||||||
|
|
@ -169,7 +163,7 @@ export default {
|
||||||
if (!this.value.spec.displayName) {
|
if (!this.value.spec.displayName) {
|
||||||
this.$refs.nd.changeNameAndNamespace({
|
this.$refs.nd.changeNameAndNamespace({
|
||||||
text: file?.name,
|
text: file?.name,
|
||||||
selected: this.value.metadata.namespace
|
selected: this.value.metadata.namespace,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -181,10 +175,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
internalAnnotations(option) {
|
internalAnnotations(option) {
|
||||||
const optionKeys = [
|
const optionKeys = [HCI_ANNOTATIONS.OS_TYPE, HCI_ANNOTATIONS.IMAGE_SUFFIX];
|
||||||
HCI_ANNOTATIONS.OS_TYPE,
|
|
||||||
HCI_ANNOTATIONS.IMAGE_SUFFIX
|
|
||||||
];
|
|
||||||
|
|
||||||
return optionKeys.find(O => O === option.key);
|
return optionKeys.find(O => O === option.key);
|
||||||
},
|
},
|
||||||
|
|
@ -193,16 +184,13 @@ export default {
|
||||||
if (keyName === HCI_ANNOTATIONS.OS_TYPE) {
|
if (keyName === HCI_ANNOTATIONS.OS_TYPE) {
|
||||||
return OS;
|
return OS;
|
||||||
} else if (keyName === HCI_ANNOTATIONS.IMAGE_SUFFIX) {
|
} else if (keyName === HCI_ANNOTATIONS.IMAGE_SUFFIX) {
|
||||||
return [
|
return [{
|
||||||
{
|
label: 'ISO',
|
||||||
label: 'ISO',
|
value: 'iso'
|
||||||
value: 'iso'
|
}, {
|
||||||
},
|
label: 'raw/qcow2',
|
||||||
{
|
value: rawORqcow2
|
||||||
label: 'raw/qcow2',
|
}];
|
||||||
value: rawORqcow2
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -217,16 +205,16 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return OS.find((os) => {
|
return OS.find( (os) => {
|
||||||
if (os.match) {
|
if (os.match) {
|
||||||
return os.match.find(matchValue => url.toLowerCase().includes(matchValue)
|
return os.match.find(matchValue => url.toLowerCase().includes(matchValue)) ? os.value : false;
|
||||||
) ? os.value : false;
|
|
||||||
} else {
|
} else {
|
||||||
return url.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
|
return url.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -249,20 +237,18 @@ export default {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Tabbed v-bind="$attrs" class="mt-15" :side-tabs="true">
|
<Tabbed v-bind="$attrs" class="mt-15" :side-tabs="true">
|
||||||
<Tab
|
<Tab name="basic" :label="t('harvester.image.tabs.basics')" :weight="3" class="bordered-table">
|
||||||
name="basic"
|
|
||||||
:label="t('harvester.image.tabs.basics')"
|
|
||||||
:weight="3"
|
|
||||||
class="bordered-table"
|
|
||||||
>
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
v-if="isCreate"
|
v-if="isCreate"
|
||||||
v-model="value.spec.sourceType"
|
v-model="value.spec.sourceType"
|
||||||
name="model"
|
name="model"
|
||||||
:options="['download', 'upload']"
|
:options="[
|
||||||
|
'download',
|
||||||
|
'upload',
|
||||||
|
]"
|
||||||
:labels="[
|
:labels="[
|
||||||
t('harvester.image.sourceType.download'),
|
t('harvester.image.sourceType.download'),
|
||||||
t('harvester.image.sourceType.upload')
|
t('harvester.image.sourceType.upload'),
|
||||||
]"
|
]"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
@ -308,13 +294,19 @@ export default {
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div v-if="uploadFileName" class="fileName mt-5">
|
<div
|
||||||
|
v-if="uploadFileName"
|
||||||
|
class="fileName mt-5"
|
||||||
|
>
|
||||||
<span class="icon icon-file" />
|
<span class="icon icon-file" />
|
||||||
{{ uploadFileName }}
|
{{ uploadFileName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="col span-12">
|
<div
|
||||||
|
v-else
|
||||||
|
class="col span-12"
|
||||||
|
>
|
||||||
<LabelValue
|
<LabelValue
|
||||||
:name="t('harvester.image.fileName')"
|
:name="t('harvester.image.fileName')"
|
||||||
:value="imageName"
|
:value="imageName"
|
||||||
|
|
@ -323,12 +315,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab name="labels" :label="t('labels.labels.title')" :weight="2" class="bordered-table">
|
||||||
name="labels"
|
|
||||||
:label="t('labels.labels.title')"
|
|
||||||
:weight="2"
|
|
||||||
class="bordered-table"
|
|
||||||
>
|
|
||||||
<KeyValue
|
<KeyValue
|
||||||
key="labels"
|
key="labels"
|
||||||
ref="labels"
|
ref="labels"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import ConsoleBar from '../components/VMConsoleBar';
|
import ConsoleBar from '../components/VMConsoleBar';
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
import ResourceTable from '@shell/components/ResourceTable';
|
||||||
import LinkDetail from '@shell/components/formatter/LinkDetail';
|
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 { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
|
||||||
import { HCI, NODE, POD } from '@shell/config/types';
|
import { HCI, NODE, POD } from '@shell/config/types';
|
||||||
|
|
@ -23,15 +23,15 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
schema: {
|
schema: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
const _hash = {
|
const _hash = {
|
||||||
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
|
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
|
||||||
pod: this.$store.dispatch('harvester/findAll', { type: POD }),
|
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)) {
|
if (this.$store.getters['harvester/schemaFor'](NODE)) {
|
||||||
|
|
@ -69,7 +69,7 @@ export default {
|
||||||
STATE,
|
STATE,
|
||||||
{
|
{
|
||||||
...NAME,
|
...NAME,
|
||||||
width: 300
|
width: 300,
|
||||||
},
|
},
|
||||||
NAMESPACE,
|
NAMESPACE,
|
||||||
{
|
{
|
||||||
|
|
@ -78,7 +78,7 @@ export default {
|
||||||
sort: ['spec.template.spec.domain.cpu.cores'],
|
sort: ['spec.template.spec.domain.cpu.cores'],
|
||||||
value: 'spec.template.spec.domain.cpu.cores',
|
value: 'spec.template.spec.domain.cpu.cores',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
dashIfEmpty: true
|
dashIfEmpty: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Memory',
|
name: 'Memory',
|
||||||
|
|
@ -89,14 +89,10 @@ export default {
|
||||||
formatter: 'Si',
|
formatter: 'Si',
|
||||||
formatterOpts: {
|
formatterOpts: {
|
||||||
opts: {
|
opts: {
|
||||||
increment: 1024,
|
increment: 1024, addSuffix: true, maxExponent: 3, minExponent: 3, suffix: 'i',
|
||||||
addSuffix: true,
|
|
||||||
maxExponent: 3,
|
|
||||||
minExponent: 3,
|
|
||||||
suffix: 'i'
|
|
||||||
},
|
},
|
||||||
needParseSi: true
|
needParseSi: true
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ip',
|
name: 'ip',
|
||||||
|
|
@ -115,15 +111,13 @@ export default {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...AGE,
|
...AGE,
|
||||||
sort: 'metadata.creationTimestamp:desc'
|
sort: 'metadata.creationTimestamp:desc',
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
rows() {
|
rows() {
|
||||||
const matchVMIs = this.allVMIs.filter(
|
const matchVMIs = this.allVMIs.filter(VMI => !this.allVMs.find(VM => VM.id === VMI.id));
|
||||||
VMI => !this.allVMs.find(VM => VM.id === VMI.id)
|
|
||||||
);
|
|
||||||
|
|
||||||
return [...this.allVMs, ...matchVMIs];
|
return [...this.allVMs, ...matchVMIs];
|
||||||
}
|
}
|
||||||
|
|
@ -154,22 +148,13 @@ export default {
|
||||||
>
|
>
|
||||||
<template slot="cell:state" slot-scope="scope" class="state-col">
|
<template slot="cell:state" slot-scope="scope" class="state-col">
|
||||||
<div class="state">
|
<div class="state">
|
||||||
<HarvesterVmState
|
<HarvesterVmState class="vmstate" :row="scope.row" :all-node-network="allNodeNetworks" :all-cluster-network="allClusterNetworks" />
|
||||||
class="vmstate"
|
|
||||||
:row="scope.row"
|
|
||||||
:all-node-network="allNodeNetworks"
|
|
||||||
:all-cluster-network="allClusterNetworks"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template slot="cell:name" slot-scope="scope">
|
<template slot="cell:name" slot-scope="scope">
|
||||||
<div class="name-console">
|
<div class="name-console">
|
||||||
<LinkDetail
|
<LinkDetail v-if="scope.row.type !== HCI.VMI" v-model="scope.row.metadata.name" :row="scope.row" />
|
||||||
v-if="scope.row.type !== HCI.VMI"
|
|
||||||
v-model="scope.row.metadata.name"
|
|
||||||
:row="scope.row"
|
|
||||||
/>
|
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ scope.row.metadata.name }}
|
{{ scope.row.metadata.name }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -197,12 +182,17 @@ export default {
|
||||||
|
|
||||||
span {
|
span {
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
width: 160px;
|
width:160px;
|
||||||
overflow: hidden;
|
overflow:hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
-o-text-overflow: ellipsis;
|
-o-text-overflow:ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around
|
||||||
|
>>>>>>> fix conflict
|
||||||
}
|
}
|
||||||
</style>
|
</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)
|
// Plugin metadata (plugin package.json)
|
||||||
public _metadata: any = {};
|
public _metadata: any = {};
|
||||||
|
public _validators: { [key: string]: Function } = {};
|
||||||
|
|
||||||
// Is this a built-in plugin (bundled with the application)
|
// Is this a built-in plugin (bundled with the application)
|
||||||
public builtin = false;
|
public builtin = false;
|
||||||
|
|
@ -43,6 +44,14 @@ export class Plugin implements IPlugin {
|
||||||
this.name = this._metadata.name || this.id;
|
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
|
// Track which products the plugin creates
|
||||||
DSL(store: any, productName: string) {
|
DSL(store: any, productName: string) {
|
||||||
const storeDSL = STORE_DSL(store, productName);
|
const storeDSL = STORE_DSL(store, productName);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
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) => {
|
const contextMap = contextFolders.reduce((map, obj) => {
|
||||||
map[obj] = true;
|
map[obj] = true;
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -409,25 +409,6 @@ export const getters = {
|
||||||
if (state.isSingleProduct !== undefined) {
|
if (state.isSingleProduct !== undefined) {
|
||||||
return state.isSingleProduct;
|
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;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -798,7 +798,13 @@ export const getters = {
|
||||||
|
|
||||||
allTypes(state, getters, rootState, rootGetters) {
|
allTypes(state, getters, rootState, rootGetters) {
|
||||||
return (product, mode = ALL) => {
|
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 schemas = rootGetters[`${ module }/all`](SCHEMA);
|
||||||
const counts = rootGetters[`${ module }/all`](COUNT)?.[0]?.counts || {};
|
const counts = rootGetters[`${ module }/all`](COUNT)?.[0]?.counts || {};
|
||||||
const isDev = rootGetters['prefs/get'](DEV);
|
const isDev = rootGetters['prefs/get'](DEV);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
import { flowOutput } from '@shell/utils/validators/flow-output';
|
import { flowOutput } from '@shell/utils/validators/flow-output';
|
||||||
import { logdna } from '@shell/utils/validators/logging-outputs';
|
import { logdna } from '@shell/utils/validators/logging-outputs';
|
||||||
import {
|
import { clusterIp, externalName, servicePort } from '@shell/utils/validators/service';
|
||||||
clusterIp,
|
import { ruleGroups, groupsAreValid } from '@shell/utils/validators/prometheusrule';
|
||||||
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 { interval, matching } from '@shell/utils/validators/monitoring-route';
|
||||||
import { containerImages } from '@shell/utils/validators/container-images';
|
import { containerImages } from '@shell/utils/validators/container-images';
|
||||||
import { cronSchedule } from '@shell/utils/validators/cron-schedule';
|
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 { clusterName } from '@shell/utils/validators/cluster-name';
|
||||||
import { isHttps, backupTarget } from '@shell/utils/validators/setting';
|
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
|
* Custom validation functions beyond normal scalr types
|
||||||
* Validator must export a function name should match the validator name on the customValidationRules rule
|
* 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
|
* Exported function is used as a lookup key in resource-class:validationErrors:customValidationRules loop
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
clusterName,
|
clusterName,
|
||||||
clusterIp,
|
clusterIp,
|
||||||
|
|
@ -38,5 +36,10 @@ export default {
|
||||||
podAffinity,
|
podAffinity,
|
||||||
roleTemplateRules,
|
roleTemplateRules,
|
||||||
isHttps,
|
isHttps,
|
||||||
backupTarget
|
backupTarget,
|
||||||
|
imageUrl,
|
||||||
|
dataVolumeSize,
|
||||||
|
vmNetworks,
|
||||||
|
vmDisks,
|
||||||
|
fileRequired,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { formatSi, parseSi } from '@shell/utils/units';
|
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'];
|
const t = getters['i18n/t'];
|
||||||
|
|
||||||
if (!storage || storage === '') {
|
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 { SOURCE_TYPE } from '@shell/config/harvester-map';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
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 isVMTemplate = validatorArgs.includes('isVMTemplate');
|
||||||
const data = isVMTemplate ? this.value.spec.vm : value;
|
const data = isVMTemplate ? this.value.spec.vm : value;
|
||||||
|
|
||||||
|
|
@ -109,7 +176,11 @@ export default function vmDisks(spec, getters, errors, validatorArgs, displayKey
|
||||||
return errors;
|
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;
|
let outValue = null;
|
||||||
|
|
||||||
if (V.persistentVolumeClaim) {
|
if (V.persistentVolumeClaim) {
|
||||||
Loading…
Reference in New Issue