mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-05 05:36:05 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c8033dfdd | ||
|
|
e9a9035cb8 | ||
|
|
1ff6137a9d | ||
|
|
311de06483 | ||
|
|
37ddf83f42 | ||
|
|
76bd9195f9 | ||
|
|
50d66a521d | ||
|
|
c81d2a87fd | ||
|
|
2e996d95da | ||
|
|
bdfa44186d | ||
|
|
ab2aa79037 | ||
|
|
cd86909b73 | ||
|
|
c73aca870f | ||
|
|
447be1f75d | ||
|
|
d958f443bf | ||
|
|
c43c3915b7 | ||
|
|
2e3547d0a2 | ||
|
|
1cbb6a6d54 | ||
|
|
3a3f8f60dc | ||
|
|
f7d2f1c586 | ||
|
|
4d4e3699a8 | ||
|
|
9423cf4c11 | ||
|
|
73b6193bf7 | ||
|
|
21ff6b0307 | ||
|
|
b887e5ee01 | ||
|
|
bf9b5866a7 | ||
|
|
b97017e076 | ||
|
|
3740c9c424 | ||
|
|
af8e8b2eeb | ||
|
|
7906033287 | ||
|
|
5584c86789 | ||
|
|
1dcb528717 | ||
|
|
36fc21ccbc | ||
|
|
8803b79524 | ||
|
|
3f80a71303 | ||
|
|
13e4154335 | ||
|
|
a80b87ce4f | ||
|
|
809b9925fa | ||
|
|
7788161d94 | ||
|
|
90b8a821a1 | ||
|
|
87ff014bd8 | ||
|
|
20328b06e6 | ||
|
|
bbc415a2b4 | ||
|
|
c12d4b5fba | ||
|
|
dc91be11f9 | ||
|
|
e5b72499b5 | ||
|
|
e66037b007 | ||
|
|
c676cad057 | ||
|
|
254abd4648 | ||
|
|
1df1d3534a | ||
|
|
ff1e6e6e7f | ||
|
|
622242e4a5 | ||
|
|
d8ea9be174 | ||
|
|
f8a479cfcf | ||
|
|
6d5e584113 | ||
|
|
a9eb04195e | ||
|
|
0441c1f6e3 | ||
|
|
dde58269d5 | ||
|
|
b8111f0ad7 | ||
|
|
6d627f82e9 | ||
|
|
cfc7a76fe7 | ||
|
|
71d3067354 | ||
|
|
9ecc372009 |
+4
-13
@@ -39,20 +39,11 @@
|
|||||||
"reviewers": ["a110605", "houhoucoop"]
|
"reviewers": ["a110605", "houhoucoop"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchUpdateTypes": ["patch"],
|
"matchUpdateTypes": ["patch", "digest"],
|
||||||
"automerge": false,
|
"automerge": true,
|
||||||
"minimumReleaseAge": "7 days",
|
"minimumReleaseAge": "7 days",
|
||||||
"groupName": "patch dependencies",
|
"groupName": "patch digest dependencies",
|
||||||
"labels": ["patch-update"],
|
"labels": ["patch-update", "automerge"]
|
||||||
"reviewers": ["a110605", "houhoucoop"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchUpdateTypes": ["digest", "pinDigest"],
|
|
||||||
"automerge": false,
|
|
||||||
"groupName": "digest dependencies",
|
|
||||||
"labels": ["digest-update"],
|
|
||||||
"schedule": ["on the first day of the month"],
|
|
||||||
"reviewers": ["a110605", "houhoucoop"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
- name: Setup Helm
|
- name: Setup Helm
|
||||||
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
||||||
with:
|
with:
|
||||||
version: v3.8.0
|
version: v3.22.0
|
||||||
|
|
||||||
- name: Setup Nodejs with yarn install
|
- name: Setup Nodejs with yarn install
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
- name: Setup Helm
|
- name: Setup Helm
|
||||||
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
|
||||||
with:
|
with:
|
||||||
version: v3.8.0
|
version: v3.22.0
|
||||||
|
|
||||||
- name: Build Helm charts
|
- name: Build Helm charts
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -43,12 +43,10 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- setup-release-tag
|
- setup-release-tag
|
||||||
- check-version
|
- check-version
|
||||||
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@3b26a36bad555e5e2b8634b24823be29732f287c # master
|
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@9bf23dff6650116673346447bcf0b088e0edb27f # creators-pkg-v3.0.12
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
|
||||||
contents: write
|
contents: write
|
||||||
deployments: write
|
pull-requests: write
|
||||||
pages: write
|
|
||||||
with:
|
with:
|
||||||
target_branch: gh-pages
|
target_branch: gh-pages
|
||||||
tagged_release: '${{ needs.setup-release-tag.outputs.release_tag }}'
|
tagged_release: '${{ needs.setup-release-tag.outputs.release_tag }}'
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ module.exports = {
|
|||||||
'subject-full-stop': [2, 'never', '.'],
|
'subject-full-stop': [2, 'never', '.'],
|
||||||
'subject-max-length': [0, 'never'],
|
'subject-max-length': [0, 'never'],
|
||||||
'body-leading-blank': [2, 'always'],
|
'body-leading-blank': [2, 'always'],
|
||||||
'body-max-line-length': [0, 'always', 100],
|
'body-max-line-length': [2, 'always', 100],
|
||||||
'footer-leading-blank': [2, 'always'],
|
'footer-leading-blank': [2, 'always'],
|
||||||
'footer-max-line-length': [2, 'always', 100],
|
'footer-max-line-length': [2, 'always', 100],
|
||||||
},
|
},
|
||||||
|
|||||||
+9
-9
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester-ui-extension",
|
"name": "harvester-ui-extension",
|
||||||
"version": "1.9.0-dev",
|
"version": "1.8.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.0.0"
|
"node": ">=24.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-transform-class-static-block": "7.28.6",
|
"@babel/plugin-transform-class-static-block": "7.29.7",
|
||||||
"@rancher/shell": "3.0.12-rc.3",
|
"@rancher/shell": "3.0.12-rc.1",
|
||||||
"@vue-flow/background": "^1.3.0",
|
"@vue-flow/background": "^1.3.0",
|
||||||
"@vue-flow/controls": "^1.1.1",
|
"@vue-flow/controls": "^1.1.1",
|
||||||
"@vue-flow/core": "^1.33.5",
|
"@vue-flow/core": "^1.33.5",
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
"color": "4.2.3",
|
"color": "4.2.3",
|
||||||
"ip": "2.0.1",
|
"ip": "2.0.1",
|
||||||
"node-polyfill-webpack-plugin": "^3.0.0",
|
"node-polyfill-webpack-plugin": "^3.0.0",
|
||||||
"elkjs": "^0.11.0",
|
"elkjs": "^0.12.0",
|
||||||
"vue-draggable-next": "^2.2.1",
|
"vue-draggable-next": "^2.2.1",
|
||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "25.9.1",
|
"@types/node": "25.9.6",
|
||||||
"cronstrue": "2.59.0",
|
"cronstrue": "2.59.0",
|
||||||
"d3-color": "3.1.0",
|
"d3-color": "3.1.0",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
@@ -29,13 +29,13 @@
|
|||||||
"glob": "7.2.3",
|
"glob": "7.2.3",
|
||||||
"glob-parent": "6.0.2",
|
"glob-parent": "6.0.2",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"@types/lodash": "4.17.24",
|
"@types/lodash": "4.17.25",
|
||||||
"merge": "2.1.1",
|
"merge": "2.1.1",
|
||||||
"node-forge": "1.4.0",
|
"node-forge": "1.4.0",
|
||||||
"nth-check": "2.1.1",
|
"nth-check": "2.1.1",
|
||||||
"qs": "6.15.2",
|
"qs": "6.16.0",
|
||||||
"roarr": "7.21.5",
|
"roarr": "7.21.7",
|
||||||
"semver": "7.8.1",
|
"semver": "7.8.5",
|
||||||
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export default {
|
|||||||
return this.$store.getters['currentCluster'].isLocal;
|
return this.$store.getters['currentCluster'].isLocal;
|
||||||
},
|
},
|
||||||
canEditClusterMembers() {
|
canEditClusterMembers() {
|
||||||
return this.schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
return this.normanClusterRTBSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,14 +58,11 @@ export default {
|
|||||||
|
|
||||||
const url = `https://${ host }${ prefix }/${ PRODUCT_NAME }/c/${ params.cluster }/console/${ uid }/${ type }`;
|
const url = `https://${ host }${ prefix }/${ PRODUCT_NAME }/c/${ params.cluster }/console/${ uid }/${ type }`;
|
||||||
|
|
||||||
// Defer so v-select can finish closing the dropdown before the popup steals focus
|
|
||||||
this.$nextTick(() => {
|
|
||||||
window.open(
|
window.open(
|
||||||
url,
|
url,
|
||||||
'_blank',
|
'_blank',
|
||||||
`toolbars=0,width=${ screen.width - 200 },height=${ screen.height - 200 },left=0,top=0,noreferrer`
|
`toolbars=0,width=${ screen.width - 200 },height=${ screen.height - 200 },left=0,top=0,noreferrer`
|
||||||
);
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isEmpty(o) {
|
isEmpty(o) {
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { RadioGroup } from '@components/Form/Radio';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import { NAMESPACE } from '@shell/config/types';
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterClusterPodSecurityStandard',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
RadioGroup,
|
|
||||||
LabeledSelect,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
await this.$store.dispatch(`${ inStore }/findAll`, { type: NAMESPACE });
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
let enabled = false;
|
|
||||||
let whitelistedNamespaces = [];
|
|
||||||
let privilegedNamespaces = [];
|
|
||||||
let restrictedNamespaces = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(this.value.value || this.value.default || '{}');
|
|
||||||
|
|
||||||
enabled = !!parsed.enabled;
|
|
||||||
whitelistedNamespaces = parsed.whitelistedNamespacesList ? parsed.whitelistedNamespacesList.split(',') : [];
|
|
||||||
privilegedNamespaces = parsed.privilegedNamespacesList ? parsed.privilegedNamespacesList.split(',') : [];
|
|
||||||
restrictedNamespaces = parsed.restrictedNamespacesList ? parsed.restrictedNamespacesList.split(',') : [];
|
|
||||||
} catch (e) {
|
|
||||||
enabled = false;
|
|
||||||
whitelistedNamespaces = [];
|
|
||||||
privilegedNamespaces = [];
|
|
||||||
restrictedNamespaces = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
enabled,
|
|
||||||
whitelistedNamespaces,
|
|
||||||
privilegedNamespaces,
|
|
||||||
restrictedNamespaces,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
enabledOptions() {
|
|
||||||
return [
|
|
||||||
{ label: this.t('generic.enabled'), value: true },
|
|
||||||
{ label: this.t('generic.disabled'), value: false },
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
allNamespaces() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
return this.$store.getters[`${ inStore }/all`](NAMESPACE).filter((ns) => !ns.isSystem).map((ns) => ns.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
whitelistedOptions() {
|
|
||||||
const excluded = new Set([...this.privilegedNamespaces, ...this.restrictedNamespaces]);
|
|
||||||
|
|
||||||
return this.allNamespaces.filter((ns) => !excluded.has(ns));
|
|
||||||
},
|
|
||||||
|
|
||||||
privilegedOptions() {
|
|
||||||
const excluded = new Set([...this.whitelistedNamespaces, ...this.restrictedNamespaces]);
|
|
||||||
|
|
||||||
return this.allNamespaces.filter((ns) => !excluded.has(ns));
|
|
||||||
},
|
|
||||||
|
|
||||||
restrictedOptions() {
|
|
||||||
const excluded = new Set([...this.whitelistedNamespaces, ...this.privilegedNamespaces]);
|
|
||||||
|
|
||||||
return this.allNamespaces.filter((ns) => !excluded.has(ns));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
useDefault() {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(this.value.default || '{}');
|
|
||||||
|
|
||||||
this.enabled = !!parsed.enabled;
|
|
||||||
this.whitelistedNamespaces = parsed.whitelistedNamespacesList ? parsed.whitelistedNamespacesList.split(',') : [];
|
|
||||||
this.privilegedNamespaces = parsed.privilegedNamespacesList ? parsed.privilegedNamespacesList.split(',') : [];
|
|
||||||
this.restrictedNamespaces = parsed.restrictedNamespacesList ? parsed.restrictedNamespacesList.split(',') : [];
|
|
||||||
} catch (e) {
|
|
||||||
this.enabled = false;
|
|
||||||
this.whitelistedNamespaces = [];
|
|
||||||
this.privilegedNamespaces = [];
|
|
||||||
this.restrictedNamespaces = [];
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
|
|
||||||
onUpdateEnabled() {
|
|
||||||
if (!this.enabled) {
|
|
||||||
this.whitelistedNamespaces = [];
|
|
||||||
this.privilegedNamespaces = [];
|
|
||||||
this.restrictedNamespaces = [];
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateWhitelisted(selected) {
|
|
||||||
this.whitelistedNamespaces = selected;
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePrivileged(selected) {
|
|
||||||
this.privilegedNamespaces = selected;
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateRestricted(selected) {
|
|
||||||
this.restrictedNamespaces = selected;
|
|
||||||
this.save();
|
|
||||||
},
|
|
||||||
|
|
||||||
save() {
|
|
||||||
this.value.value = JSON.stringify({
|
|
||||||
enabled: this.enabled,
|
|
||||||
whitelistedNamespacesList: this.whitelistedNamespaces.join(','),
|
|
||||||
privilegedNamespacesList: this.privilegedNamespaces.join(','),
|
|
||||||
restrictedNamespacesList: this.restrictedNamespaces.join(','),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<RadioGroup
|
|
||||||
v-model:value="enabled"
|
|
||||||
name="enabled"
|
|
||||||
:options="enabledOptions"
|
|
||||||
@update:value="onUpdateEnabled"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-if="enabled">
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="whitelistedNamespaces"
|
|
||||||
:label="t('harvester.setting.clusterPodSecurityStandard.whitelistedNamespaces.label')"
|
|
||||||
:options="whitelistedOptions"
|
|
||||||
:multiple="true"
|
|
||||||
:mode="mode"
|
|
||||||
@update:value="updateWhitelisted"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="privilegedNamespaces"
|
|
||||||
:label="t('harvester.setting.clusterPodSecurityStandard.privilegedNamespaces.label')"
|
|
||||||
:options="privilegedOptions"
|
|
||||||
:multiple="true"
|
|
||||||
:mode="mode"
|
|
||||||
@update:value="updatePrivileged"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="restrictedNamespaces"
|
|
||||||
:label="t('harvester.setting.clusterPodSecurityStandard.restrictedNamespaces.label')"
|
|
||||||
:options="restrictedOptions"
|
|
||||||
:multiple="true"
|
|
||||||
:mode="mode"
|
|
||||||
@update:value="updateRestricted"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -65,14 +65,7 @@ const FEATURE_FLAGS = {
|
|||||||
'vGPUAsPCIDevice',
|
'vGPUAsPCIDevice',
|
||||||
'instanceManagerResourcesSetting',
|
'instanceManagerResourcesSetting',
|
||||||
'rwxNetworkSetting',
|
'rwxNetworkSetting',
|
||||||
'createPVCWithDataVolume',
|
'createPVCWithDataVolume'
|
||||||
'clusterPodSecurityStandardSetting',
|
|
||||||
],
|
|
||||||
'v1.8.1': [],
|
|
||||||
'v1.9.0': [
|
|
||||||
'supportFilesystem',
|
|
||||||
'disableResourcePooling',
|
|
||||||
'expandOnlineEncryptedVolume'
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -237,7 +237,6 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimport',
|
labelKey: 'harvester.addons.vmImport.labels.vmimport',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
ifHaveType: HCI.VMIMPORT,
|
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT }
|
params: { resource: HCI.VMIMPORT }
|
||||||
@@ -267,7 +266,6 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceVMWare',
|
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceVMWare',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
ifHaveType: HCI.VMIMPORT_SOURCE_V,
|
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT_SOURCE_V }
|
params: { resource: HCI.VMIMPORT_SOURCE_V }
|
||||||
@@ -297,7 +295,6 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOpenStack',
|
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOpenStack',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
ifHaveType: HCI.VMIMPORT_SOURCE_O,
|
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT_SOURCE_O }
|
params: { resource: HCI.VMIMPORT_SOURCE_O }
|
||||||
@@ -326,7 +323,6 @@ export function init($plugin, store) {
|
|||||||
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOVA',
|
labelKey: 'harvester.addons.vmImport.labels.vmimportSourceOVA',
|
||||||
group: 'vmimport',
|
group: 'vmimport',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
ifHaveType: HCI.VMIMPORT_SOURCE_OVA,
|
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.VMIMPORT_SOURCE_OVA }
|
params: { resource: HCI.VMIMPORT_SOURCE_OVA }
|
||||||
@@ -488,7 +484,6 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
ifHaveType: LOGGING.CLUSTER_FLOW,
|
|
||||||
labelKey: 'harvester.logging.clusterFlow.label',
|
labelKey: 'harvester.logging.clusterFlow.label',
|
||||||
name: HCI.CLUSTER_FLOW,
|
name: HCI.CLUSTER_FLOW,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@@ -512,7 +507,6 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
ifHaveType: LOGGING.CLUSTER_OUTPUT,
|
|
||||||
labelKey: 'harvester.logging.clusterOutput.label',
|
labelKey: 'harvester.logging.clusterOutput.label',
|
||||||
name: HCI.CLUSTER_OUTPUT,
|
name: HCI.CLUSTER_OUTPUT,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@@ -536,7 +530,6 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
ifHaveType: LOGGING.FLOW,
|
|
||||||
labelKey: 'harvester.logging.flow.label',
|
labelKey: 'harvester.logging.flow.label',
|
||||||
name: HCI.FLOW,
|
name: HCI.FLOW,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@@ -560,7 +553,6 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
ifHaveType: LOGGING.OUTPUT,
|
|
||||||
labelKey: 'harvester.logging.output.label',
|
labelKey: 'harvester.logging.output.label',
|
||||||
name: HCI.OUTPUT,
|
name: HCI.OUTPUT,
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@@ -694,7 +686,7 @@ export function init($plugin, store) {
|
|||||||
},
|
},
|
||||||
resource: NETWORK_ATTACHMENT,
|
resource: NETWORK_ATTACHMENT,
|
||||||
resourceDetail: HCI.NETWORK_ATTACHMENT,
|
resourceDetail: HCI.NETWORK_ATTACHMENT,
|
||||||
resourceEdit: HCI.NETWORK_ATTACHMENT,
|
resourceEdit: HCI.NETWORK_ATTACHMENT
|
||||||
});
|
});
|
||||||
|
|
||||||
virtualType({
|
virtualType({
|
||||||
|
|||||||
@@ -88,8 +88,6 @@ export const CSI_SECRETS = {
|
|||||||
CSI_NODE_PUBLISH_SECRET_NAMESPACE: 'csi.storage.k8s.io/node-publish-secret-namespace',
|
CSI_NODE_PUBLISH_SECRET_NAMESPACE: 'csi.storage.k8s.io/node-publish-secret-namespace',
|
||||||
CSI_NODE_STAGE_SECRET_NAME: 'csi.storage.k8s.io/node-stage-secret-name',
|
CSI_NODE_STAGE_SECRET_NAME: 'csi.storage.k8s.io/node-stage-secret-name',
|
||||||
CSI_NODE_STAGE_SECRET_NAMESPACE: 'csi.storage.k8s.io/node-stage-secret-namespace',
|
CSI_NODE_STAGE_SECRET_NAMESPACE: 'csi.storage.k8s.io/node-stage-secret-namespace',
|
||||||
CSI_NODE_EXPAND_SECRET_NAME: 'csi.storage.k8s.io/node-expand-secret-name',
|
|
||||||
CSI_NODE_EXPAND_SECRET_NAMESPACE: 'csi.storage.k8s.io/node-expand-secret-namespace'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Some harvester CRD type is not equal to model file name, define the mapping here
|
// Some harvester CRD type is not equal to model file name, define the mapping here
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export const HCI = {
|
|||||||
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
||||||
VM_NAME: 'harvesterhci.io/vmName',
|
VM_NAME: 'harvesterhci.io/vmName',
|
||||||
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
||||||
VM_DISPLAY_NAME: 'harvesterhci.io/vmDisplayName',
|
|
||||||
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
|
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
|
||||||
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
|
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
|
||||||
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
|
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ export const HCI_SETTING = {
|
|||||||
RANCHER_CLUSTER: 'rancher-cluster',
|
RANCHER_CLUSTER: 'rancher-cluster',
|
||||||
MAX_HOTPLUG_RATIO: 'max-hotplug-ratio',
|
MAX_HOTPLUG_RATIO: 'max-hotplug-ratio',
|
||||||
KUBEVIRT_MIGRATION: 'kubevirt-migration',
|
KUBEVIRT_MIGRATION: 'kubevirt-migration',
|
||||||
INSTANCE_MANAGER_RESOURCES: 'instance-manager-resources',
|
INSTANCE_MANAGER_RESOURCES: 'instance-manager-resources'
|
||||||
CLUSTER_POD_SECURITY_STANDARD: 'cluster-pod-security-standard'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HCI_ALLOWED_SETTINGS = {
|
export const HCI_ALLOWED_SETTINGS = {
|
||||||
@@ -131,9 +130,6 @@ export const HCI_ALLOWED_SETTINGS = {
|
|||||||
},
|
},
|
||||||
[HCI_SETTING.INSTANCE_MANAGER_RESOURCES]: {
|
[HCI_SETTING.INSTANCE_MANAGER_RESOURCES]: {
|
||||||
kind: 'json', from: 'import', featureFlag: 'instanceManagerResourcesSetting'
|
kind: 'json', from: 'import', featureFlag: 'instanceManagerResourcesSetting'
|
||||||
},
|
|
||||||
[HCI_SETTING.CLUSTER_POD_SECURITY_STANDARD]: {
|
|
||||||
kind: 'json', from: 'import', canReset: true, featureFlag: 'clusterPodSecurityStandardSetting'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,3 @@ export const CDI_POPULATOR_KIND = {
|
|||||||
VOLUME_IMPORT_SOURCE: 'VolumeImportSource',
|
VOLUME_IMPORT_SOURCE: 'VolumeImportSource',
|
||||||
VOLUME_CLONE_SOURCE: 'VolumeCloneSource',
|
VOLUME_CLONE_SOURCE: 'VolumeCloneSource',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FILESYSTEM_SOURCE_TYPE = {
|
|
||||||
CONFIGMAP: 'configmap',
|
|
||||||
SECRET: 'secret',
|
|
||||||
SERVICEACCOUNT: 'serviceaccount',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -27,11 +27,15 @@ export default {
|
|||||||
await allHash({
|
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 }),
|
||||||
vmims: this.$store.dispatch('harvester/findAll', { type: HCI.VMIM }),
|
allClusterNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.CLUSTER_NETWORK }),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
allClusterNetwork() {
|
||||||
|
return this.$store.getters['harvester/all'](HCI.CLUSTER_NETWORK);
|
||||||
|
},
|
||||||
|
|
||||||
rows() {
|
rows() {
|
||||||
const vms = this.$store.getters['harvester/all'](HCI.VM);
|
const vms = this.$store.getters['harvester/all'](HCI.VM);
|
||||||
|
|
||||||
@@ -104,6 +108,7 @@ export default {
|
|||||||
<HarvesterVmState
|
<HarvesterVmState
|
||||||
class="vmstate"
|
class="vmstate"
|
||||||
:row="scope.row"
|
:row="scope.row"
|
||||||
|
:all-cluster-network="allClusterNetwork"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { Banner } from '@components/Banner';
|
|
||||||
import { Card } from '@components/Card';
|
import { Card } from '@components/Card';
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
|
||||||
import AsyncButton from '@shell/components/AsyncButton';
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
import { escapeHtml } from '@shell/utils/string';
|
import { escapeHtml } from '@shell/utils/string';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
@@ -15,9 +13,7 @@ export default {
|
|||||||
|
|
||||||
components: {
|
components: {
|
||||||
AsyncButton,
|
AsyncButton,
|
||||||
Banner,
|
|
||||||
Card,
|
Card,
|
||||||
Checkbox,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -28,16 +24,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { disableResourcePooling: false };
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: { ...mapGetters({ t: 'i18n/t' }) },
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
disableResourcePoolingEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('disableResourcePooling');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
@@ -64,8 +54,7 @@ export default {
|
|||||||
spec: {
|
spec: {
|
||||||
address: actionResource.status.address,
|
address: actionResource.status.address,
|
||||||
nodeName: actionResource.status.nodeName,
|
nodeName: actionResource.status.nodeName,
|
||||||
userName,
|
userName
|
||||||
disableResourcePooling: this.disableResourcePooling,
|
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
@@ -96,19 +85,7 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<p class="mb-20">
|
|
||||||
{{ t('harvester.pci.enablePassthroughWarning') }}
|
{{ t('harvester.pci.enablePassthroughWarning') }}
|
||||||
</p>
|
|
||||||
<template v-if="disableResourcePoolingEnabled">
|
|
||||||
<Checkbox
|
|
||||||
v-model:value="disableResourcePooling"
|
|
||||||
label-key="harvester.pci.disableResourcePooling"
|
|
||||||
/>
|
|
||||||
<Banner
|
|
||||||
color="info"
|
|
||||||
:label="t('harvester.pci.disableResourcePoolingDescription')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
|||||||
@@ -1,154 +0,0 @@
|
|||||||
<script>
|
|
||||||
import merge from 'lodash/merge';
|
|
||||||
import jsyaml from 'js-yaml';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
import { Card } from '@components/Card';
|
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
||||||
import AsyncButton from '@shell/components/AsyncButton';
|
|
||||||
import { escapeHtml } from '@shell/utils/string';
|
|
||||||
|
|
||||||
const DEFAULT_VALUE = { image: { repository: 'rancher/harvester-nvidia-driver-toolkit' } };
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterEnableNvidiaDriverToolkit',
|
|
||||||
|
|
||||||
emits: ['close'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
AsyncButton,
|
|
||||||
Card,
|
|
||||||
LabeledInput,
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
resources: {
|
|
||||||
type: Array,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
const addon = this.resources[0];
|
|
||||||
let valuesContentJson;
|
|
||||||
|
|
||||||
try {
|
|
||||||
valuesContentJson = merge({}, DEFAULT_VALUE, jsyaml.load(addon.spec.valuesContent));
|
|
||||||
} catch (e) {
|
|
||||||
valuesContentJson = { ...DEFAULT_VALUE };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valuesContentJson };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
buttonDisabled() {
|
|
||||||
const { image, driverLocation } = this.valuesContentJson;
|
|
||||||
|
|
||||||
return !(image?.repository || '').trim() || !(image?.tag || '').trim() || !(driverLocation || '').trim();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
close() {
|
|
||||||
this.$emit('close');
|
|
||||||
},
|
|
||||||
|
|
||||||
async enable(buttonCb) {
|
|
||||||
const addon = this.resources[0];
|
|
||||||
|
|
||||||
try {
|
|
||||||
addon.spec.valuesContent = jsyaml.dump(this.valuesContentJson);
|
|
||||||
addon.spec.enabled = true;
|
|
||||||
await addon.save();
|
|
||||||
buttonCb(true);
|
|
||||||
this.close();
|
|
||||||
} catch (err) {
|
|
||||||
addon.spec.enabled = false;
|
|
||||||
this.$store.dispatch('growl/fromError', {
|
|
||||||
title: this.t('generic.notification.title.error', { name: escapeHtml(addon.metadata.name) }),
|
|
||||||
err,
|
|
||||||
}, { root: true });
|
|
||||||
buttonCb(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Card :show-highlight-border="false">
|
|
||||||
<template #title>
|
|
||||||
<h4
|
|
||||||
v-clean-html="t('harvester.addons.nvidiaDriverToolkit.enable.title')"
|
|
||||||
class="text-default-text"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #body>
|
|
||||||
<div class="body">
|
|
||||||
<div class="row mb-15">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="valuesContentJson.image.repository"
|
|
||||||
:required="true"
|
|
||||||
:label="t('harvester.addons.nvidiaDriverToolkit.image.repository')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="valuesContentJson.image.tag"
|
|
||||||
:required="true"
|
|
||||||
:label="t('harvester.addons.nvidiaDriverToolkit.image.tag')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-15">
|
|
||||||
<div class="col span-12">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="valuesContentJson.driverLocation"
|
|
||||||
:required="true"
|
|
||||||
:label="t('harvester.addons.nvidiaDriverToolkit.driver.location')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #actions>
|
|
||||||
<div class="buttons actions">
|
|
||||||
<button
|
|
||||||
class="btn role-secondary mr-10"
|
|
||||||
@click="close"
|
|
||||||
>
|
|
||||||
{{ t('generic.cancel') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<AsyncButton
|
|
||||||
mode="enable"
|
|
||||||
:disabled="buttonDisabled"
|
|
||||||
@click="enable"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Card>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
import { NODE } from '@shell/config/types';
|
import { NODE } from '@shell/config/types';
|
||||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
|
|
||||||
import { Card } from '@components/Card';
|
import { Card } from '@components/Card';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import AsyncButton from '@shell/components/AsyncButton';
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
|
import { HCI } from '../types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['close'],
|
emits: ['close'],
|
||||||
@@ -59,46 +62,28 @@ export default {
|
|||||||
return this.resources[0];
|
return this.resources[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
anyCpuPinning() {
|
vmi() {
|
||||||
return this.resources.some((r) => r.isCpuPinning);
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
},
|
const vmiResources = this.$store.getters[`${ inStore }/all`](HCI.VMI);
|
||||||
|
const resource = vmiResources.find((VMI) => VMI.id === this.actionResource?.id) || null;
|
||||||
|
|
||||||
vmsByNode() {
|
return resource;
|
||||||
const groups = {};
|
|
||||||
|
|
||||||
for (const r of this.resources) {
|
|
||||||
const node = r.nodeName || '';
|
|
||||||
const name = r.nameDisplay || r.name || r.id;
|
|
||||||
|
|
||||||
if (!groups[node]) {
|
|
||||||
groups[node] = [];
|
|
||||||
}
|
|
||||||
groups[node].push(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.entries(groups).map(([node, vms]) => ({ node, vms })).sort((a, b) => a.node.localeCompare(b.node));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cpuPinningAlertMessage() {
|
cpuPinningAlertMessage() {
|
||||||
return this.t('harvester.virtualMachine.cpuPinning.migrationMessage');
|
return this.t('harvester.virtualMachine.cpuPinning.migrationMessage');
|
||||||
},
|
},
|
||||||
|
|
||||||
allVmsOnTargetNode() {
|
|
||||||
if (!this.nodeName) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.resources.every((r) => r.nodeName === this.nodeName);
|
|
||||||
},
|
|
||||||
|
|
||||||
nodeNameList() {
|
nodeNameList() {
|
||||||
const nodes = this.$store.getters['harvester/all'](NODE);
|
const nodes = this.$store.getters['harvester/all'](NODE);
|
||||||
|
|
||||||
return nodes.filter((n) => {
|
return nodes.filter((n) => {
|
||||||
|
const isNotSelfNode = !!this.availableNodes.includes(n.id);
|
||||||
const isNotWitnessNode = n.isEtcd !== 'true'; // do not allow to migrate to self node and witness node
|
const isNotWitnessNode = n.isEtcd !== 'true'; // do not allow to migrate to self node and witness node
|
||||||
const matchingCpuManagerConfig = !this.anyCpuPinning || n.isCPUManagerEnabled; // If cpu-pinning is enabled, filter-out non-enabled CPU manager nodes.
|
const isCpuPinning = this.actionResource?.isCpuPinning;
|
||||||
|
const matchingCpuManagerConfig = !isCpuPinning || n.isCPUManagerEnabled; // If cpu-pinning is enabled, filter-out non-enabled CPU manager nodes.
|
||||||
|
|
||||||
return isNotWitnessNode && matchingCpuManagerConfig;
|
return isNotSelfNode && isNotWitnessNode && matchingCpuManagerConfig;
|
||||||
}).map((n) => {
|
}).map((n) => {
|
||||||
let label = n?.metadata?.name;
|
let label = n?.metadata?.name;
|
||||||
const value = n?.metadata?.name;
|
const value = n?.metadata?.name;
|
||||||
@@ -141,29 +126,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Filter out VMs already running on the selected node
|
await this.actionResource.doAction('migrate', { nodeName: this.nodeName }, {}, false);
|
||||||
const toMigrate = this.resources.filter((r) => r.nodeName !== this.nodeName);
|
|
||||||
|
|
||||||
// await Promise.allSettled(toMigrate.map((r) => r.doAction('migrate', { nodeName: this.nodeName }, {}, false)));
|
|
||||||
// We want to show all migration errors if there are multiple VMs, so we use allSettled here and handle the results accordingly.
|
|
||||||
const results = await Promise.allSettled(toMigrate.map((r) => r.doAction('migrate', { nodeName: this.nodeName }, {}, false)));
|
|
||||||
|
|
||||||
const failedMigrations = results
|
|
||||||
.map((result, index) => ({ resource: toMigrate[index], result }))
|
|
||||||
.filter(({ result }) => result.status === 'rejected');
|
|
||||||
|
|
||||||
if (failedMigrations.length) {
|
|
||||||
this['errors'] = failedMigrations.flatMap(({ resource, result }) => {
|
|
||||||
const vmName = resource?.nameDisplay || resource?.name || resource?.metadata?.name || this.$store.getters['i18n/t']('generic.unknown');
|
|
||||||
const error = result.reason?.data || result.reason;
|
|
||||||
const messages = exceptionToErrorsArray(error);
|
|
||||||
|
|
||||||
return messages.map((message) => `${ vmName }: ${ message }`);
|
|
||||||
});
|
|
||||||
buttonDone(false);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buttonDone(true);
|
buttonDone(true);
|
||||||
this.close();
|
this.close();
|
||||||
@@ -183,35 +146,17 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<Card :show-highlight-border="false">
|
<Card :show-highlight-border="false">
|
||||||
<template #title>
|
<template #title>
|
||||||
{{ t('harvester.modal.migration.vmMigrationTitle', { count: resources.length }) }}
|
{{ t('harvester.modal.migration.title') }}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<Banner
|
<Banner
|
||||||
v-if="anyCpuPinning"
|
v-if="actionResource?.isCpuPinning"
|
||||||
color="warning"
|
color="warning"
|
||||||
:label="cpuPinningAlertMessage"
|
:label="cpuPinningAlertMessage"
|
||||||
/>
|
/>
|
||||||
<p>
|
|
||||||
{{ t('harvester.modal.migration.selectedVMs') }}
|
|
||||||
</p>
|
|
||||||
<ul class="vm-list">
|
|
||||||
<li
|
|
||||||
v-for="group in vmsByNode"
|
|
||||||
:key="group.node"
|
|
||||||
>
|
|
||||||
{{ group.node || t('harvester.modal.migration.unknownNode') }}: {{ group.vms.join(', ') }}
|
|
||||||
<span
|
|
||||||
v-if="nodeName && group.node === nodeName"
|
|
||||||
class="already-on-target"
|
|
||||||
>
|
|
||||||
({{ t('harvester.modal.migration.alreadyOnTarget') }})
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model:value="nodeName"
|
v-model:value="nodeName"
|
||||||
class="mt-15"
|
|
||||||
:label="t('harvester.modal.migration.fields.nodeName.label')"
|
:label="t('harvester.modal.migration.fields.nodeName.label')"
|
||||||
:placeholder="t('harvester.modal.migration.fields.nodeName.placeholder')"
|
:placeholder="t('harvester.modal.migration.fields.nodeName.placeholder')"
|
||||||
:options="nodeNameList"
|
:options="nodeNameList"
|
||||||
@@ -238,7 +183,7 @@ export default {
|
|||||||
|
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
mode="apply"
|
mode="apply"
|
||||||
:disabled="!nodeName || allVmsOnTargetNode"
|
:disabled="!nodeName"
|
||||||
@click="apply"
|
@click="apply"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,16 +201,4 @@ export default {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.already-on-target {
|
|
||||||
color: var(--warning);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vm-list {
|
|
||||||
list-style: disc;
|
|
||||||
padding-left: 1.5em;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import FileSelector, { createOnSelected } from '@shell/components/form/FileSelec
|
|||||||
|
|
||||||
import { randomStr } from '@shell/utils/string';
|
import { randomStr } from '@shell/utils/string';
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
import { getLoginAwareErrors } from '../utils/error';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditKeypair',
|
name: 'HarvesterEditKeypair',
|
||||||
@@ -64,14 +63,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
normalizedErrors() {
|
|
||||||
const message = this.t('harvester.virtualMachine.genericLoginError');
|
|
||||||
|
|
||||||
return getLoginAwareErrors(this.errors, message);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: { onKeySelected: createOnSelected('publicKey') },
|
methods: { onKeySelected: createOnSelected('publicKey') },
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -81,9 +72,10 @@ export default {
|
|||||||
:done-route="doneRoute"
|
:done-route="doneRoute"
|
||||||
:resource="value"
|
:resource="value"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:errors="normalizedErrors"
|
:errors="errors"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
|
@error="e=>errors=e"
|
||||||
>
|
>
|
||||||
<div class="header mb-20">
|
<div class="header mb-20">
|
||||||
<FileSelector
|
<FileSelector
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import KeyValue from '@shell/components/form/KeyValue';
|
|||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import RadioGroup from '@components/Form/Radio/RadioGroup';
|
import RadioGroup from '@components/Form/Radio/RadioGroup';
|
||||||
import Checkbox from '@components/Form/Checkbox/Checkbox';
|
|
||||||
import { SECRET, LONGHORN } from '@shell/config/types';
|
import { SECRET, LONGHORN } from '@shell/config/types';
|
||||||
import { _CREATE, _VIEW, _EDIT } from '@shell/config/query-params';
|
import { _CREATE, _VIEW } from '@shell/config/query-params';
|
||||||
import { CSI_SECRETS } from '@pkg/harvester/config/harvester-map';
|
import { CSI_SECRETS } from '@pkg/harvester/config/harvester-map';
|
||||||
import { clone } from '@shell/utils/object';
|
import { clone } from '@shell/utils/object';
|
||||||
import { uniq } from '@shell/utils/array';
|
import { uniq } from '@shell/utils/array';
|
||||||
@@ -17,9 +16,7 @@ const {
|
|||||||
CSI_NODE_PUBLISH_SECRET_NAME,
|
CSI_NODE_PUBLISH_SECRET_NAME,
|
||||||
CSI_NODE_PUBLISH_SECRET_NAMESPACE,
|
CSI_NODE_PUBLISH_SECRET_NAMESPACE,
|
||||||
CSI_NODE_STAGE_SECRET_NAME,
|
CSI_NODE_STAGE_SECRET_NAME,
|
||||||
CSI_NODE_STAGE_SECRET_NAMESPACE,
|
CSI_NODE_STAGE_SECRET_NAMESPACE
|
||||||
CSI_NODE_EXPAND_SECRET_NAME,
|
|
||||||
CSI_NODE_EXPAND_SECRET_NAMESPACE
|
|
||||||
} = CSI_SECRETS;
|
} = CSI_SECRETS;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -30,7 +27,6 @@ export default {
|
|||||||
LabeledSelect,
|
LabeledSelect,
|
||||||
LabeledInput,
|
LabeledInput,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
Checkbox,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -61,10 +57,7 @@ export default {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasExpandSecret = !!(this.value.parameters?.[CSI_NODE_EXPAND_SECRET_NAME] && this.value.parameters?.[CSI_NODE_EXPAND_SECRET_NAMESPACE]);
|
return { };
|
||||||
const volumeExpansionCheckBoxEnabled = this.realMode === _CREATE ? true : hasExpandSecret;
|
|
||||||
|
|
||||||
return { volumeExpansionCheckBoxEnabled };
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -105,11 +98,8 @@ export default {
|
|||||||
}, []);
|
}, []);
|
||||||
},
|
},
|
||||||
|
|
||||||
isEdit() {
|
|
||||||
return this.realMode === _EDIT;
|
|
||||||
},
|
|
||||||
isView() {
|
isView() {
|
||||||
return this.realMode === _VIEW;
|
return this.mode === _VIEW;
|
||||||
},
|
},
|
||||||
|
|
||||||
migratableOptions() {
|
migratableOptions() {
|
||||||
@@ -162,10 +152,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
enableOnlineExpansionVolumeEncryption() {
|
|
||||||
return this.value.expandOnlineEncryptedVolumeFeatureEnabled;
|
|
||||||
},
|
|
||||||
|
|
||||||
volumeEncryption: {
|
volumeEncryption: {
|
||||||
set(neu) {
|
set(neu) {
|
||||||
this.value['parameters'] = {
|
this.value['parameters'] = {
|
||||||
@@ -194,11 +180,6 @@ export default {
|
|||||||
set(selectedSecret) {
|
set(selectedSecret) {
|
||||||
const [namespace, name] = selectedSecret.split('/');
|
const [namespace, name] = selectedSecret.split('/');
|
||||||
|
|
||||||
const expandSecretParams = (this.enableOnlineExpansionVolumeEncryption && this.volumeExpansionCheckBoxEnabled) ? {
|
|
||||||
[CSI_NODE_EXPAND_SECRET_NAME]: name,
|
|
||||||
[CSI_NODE_EXPAND_SECRET_NAMESPACE]: namespace,
|
|
||||||
} : {};
|
|
||||||
|
|
||||||
this.value['parameters'] = {
|
this.value['parameters'] = {
|
||||||
...this.value.parameters,
|
...this.value.parameters,
|
||||||
[CSI_PROVISIONER_SECRET_NAME]: name,
|
[CSI_PROVISIONER_SECRET_NAME]: name,
|
||||||
@@ -206,8 +187,7 @@ export default {
|
|||||||
[CSI_NODE_STAGE_SECRET_NAME]: name,
|
[CSI_NODE_STAGE_SECRET_NAME]: name,
|
||||||
[CSI_PROVISIONER_SECRET_NAMESPACE]: namespace,
|
[CSI_PROVISIONER_SECRET_NAMESPACE]: namespace,
|
||||||
[CSI_NODE_PUBLISH_SECRET_NAMESPACE]: namespace,
|
[CSI_NODE_PUBLISH_SECRET_NAMESPACE]: namespace,
|
||||||
[CSI_NODE_STAGE_SECRET_NAMESPACE]: namespace,
|
[CSI_NODE_STAGE_SECRET_NAMESPACE]: namespace
|
||||||
...expandSecretParams,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -260,32 +240,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
|
||||||
volumeExpansionCheckBoxEnabled(enabled) {
|
|
||||||
const currentSecret = this.secret;
|
|
||||||
|
|
||||||
if (!currentSecret) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [namespace, name] = currentSecret.split('/');
|
|
||||||
|
|
||||||
if (enabled && this.enableOnlineExpansionVolumeEncryption) {
|
|
||||||
this.value['parameters'] = {
|
|
||||||
...this.value.parameters,
|
|
||||||
[CSI_NODE_EXPAND_SECRET_NAME]: name,
|
|
||||||
[CSI_NODE_EXPAND_SECRET_NAMESPACE]: namespace,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const params = { ...this.value.parameters };
|
|
||||||
|
|
||||||
delete params[CSI_NODE_EXPAND_SECRET_NAME];
|
|
||||||
delete params[CSI_NODE_EXPAND_SECRET_NAMESPACE];
|
|
||||||
this.value['parameters'] = params;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
@@ -383,16 +337,6 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="enableOnlineExpansionVolumeEncryption"
|
|
||||||
class="col span-6 flex items-center mt-20"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
v-model:value="volumeExpansionCheckBoxEnabled"
|
|
||||||
:label="t('harvester.storage.volumeExpansionCheckbox')"
|
|
||||||
:disabled="isEdit || isView"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<KeyValue
|
<KeyValue
|
||||||
|
|||||||
@@ -254,24 +254,15 @@ export default {
|
|||||||
v-if="restoreNewVm"
|
v-if="restoreNewVm"
|
||||||
v-model:value="restoreResource.spec.keepMacAddress"
|
v-model:value="restoreResource.spec.keepMacAddress"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="check mb-20"
|
|
||||||
:label="t('harvester.backup.restore.keepMacAddress')"
|
:label="t('harvester.backup.restore.keepMacAddress')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-if="!restoreNewVm"
|
v-if="!restoreNewVm"
|
||||||
v-model:value="deletionPolicy"
|
v-model:value="deletionPolicy"
|
||||||
class="mb-20"
|
|
||||||
:label="t('harvester.backup.restore.deletePreviousVolumes')"
|
:label="t('harvester.backup.restore.deletePreviousVolumes')"
|
||||||
:options="deletionPolicyOption"
|
:options="deletionPolicyOption"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Checkbox
|
|
||||||
v-model:value="restoreResource.spec.haltAfterRestore"
|
|
||||||
type="checkbox"
|
|
||||||
class="check mb-20"
|
|
||||||
:label="t('harvester.backup.restore.haltAfterRestore')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Footer
|
<Footer
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import CpuMemory from './kubevirt.io.virtualmachine/VirtualMachineCpuMemory';
|
|||||||
import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel';
|
import CpuModel from './kubevirt.io.virtualmachine/VirtualMachineCpuModel';
|
||||||
import CloudConfig from './kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
|
import CloudConfig from './kubevirt.io.virtualmachine/VirtualMachineCloudConfig';
|
||||||
import SSHKey from './kubevirt.io.virtualmachine/VirtualMachineSSHKey';
|
import SSHKey from './kubevirt.io.virtualmachine/VirtualMachineSSHKey';
|
||||||
import Filesystem from './kubevirt.io.virtualmachine/VirtualMachineFilesystem';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditVMTemplate',
|
name: 'HarvesterEditVMTemplate',
|
||||||
@@ -52,7 +51,6 @@ export default {
|
|||||||
UnitInput,
|
UnitInput,
|
||||||
Banner,
|
Banner,
|
||||||
KeyValue,
|
KeyValue,
|
||||||
Filesystem,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [CreateEditView, VM_MIXIN],
|
mixins: [CreateEditView, VM_MIXIN],
|
||||||
@@ -97,10 +95,6 @@ export default {
|
|||||||
secretNamePrefix() {
|
secretNamePrefix() {
|
||||||
return this.templateValue?.metadata?.name;
|
return this.templateValue?.metadata?.name;
|
||||||
},
|
},
|
||||||
|
|
||||||
filesystemEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('supportFilesystem');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -160,7 +154,6 @@ export default {
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.imageId = this.diskRows[0]?.image || '';
|
this.imageId = this.diskRows[0]?.image || '';
|
||||||
this['filesystemRows'] = this.getFilesystemRows(this.value.spec.vm);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -356,19 +349,6 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
|
||||||
v-if="filesystemEnabled"
|
|
||||||
name="filesystem"
|
|
||||||
:label="t('harvester.tab.filesystem')"
|
|
||||||
:weight="-8"
|
|
||||||
>
|
|
||||||
<Filesystem
|
|
||||||
v-model:value="filesystemRows"
|
|
||||||
:mode="mode"
|
|
||||||
:namespace="templateValue.metadata.namespace"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
name="labels"
|
name="labels"
|
||||||
:label="t('generic.labels')"
|
:label="t('generic.labels')"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import Footer from '@shell/components/form/Footer';
|
import Footer from '@shell/components/form/Footer';
|
||||||
import { RadioGroup } from '@components/Form/Radio';
|
import { RadioGroup } from '@components/Form/Radio';
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import Checkbox from '@components/Form/Checkbox/Checkbox';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
@@ -33,7 +32,6 @@ const createObject = {
|
|||||||
export default {
|
export default {
|
||||||
name: 'CreateRestore',
|
name: 'CreateRestore',
|
||||||
components: {
|
components: {
|
||||||
Checkbox,
|
|
||||||
Footer,
|
Footer,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
LabeledInput,
|
LabeledInput,
|
||||||
@@ -251,17 +249,9 @@ export default {
|
|||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-if="!restoreNewVm"
|
v-if="!restoreNewVm"
|
||||||
v-model:value="deletionPolicy"
|
v-model:value="deletionPolicy"
|
||||||
class="mb-20"
|
|
||||||
:label="t('harvester.backup.restore.deletePreviousVolumes')"
|
:label="t('harvester.backup.restore.deletePreviousVolumes')"
|
||||||
:options="deletionPolicyOption"
|
:options="deletionPolicyOption"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Checkbox
|
|
||||||
v-model:value="restoreResource.spec.haltAfterRestore"
|
|
||||||
type="checkbox"
|
|
||||||
class="check mb-20"
|
|
||||||
:label="t('harvester.backup.restore.haltAfterRestore')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Footer
|
<Footer
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
const vpc = this.$route.query.vpc || '';
|
const vpc = this.$route.query.vpc || '';
|
||||||
const enableDHCP = this.value?.spec?.enableDHCP || false;
|
const enableDHCP = this.value?.spec?.enableDHCP || false;
|
||||||
const natOutgoing = this.value?.spec?.natOutgoing || false;
|
|
||||||
|
|
||||||
set(this.value.spec, 'enableDHCP', enableDHCP);
|
set(this.value.spec, 'enableDHCP', enableDHCP);
|
||||||
set(this.value, 'spec', this.value.spec || {
|
set(this.value, 'spec', this.value.spec || {
|
||||||
@@ -51,11 +50,10 @@ export default {
|
|||||||
protocol: NETWORK_PROTOCOL.IPv4,
|
protocol: NETWORK_PROTOCOL.IPv4,
|
||||||
provider: '',
|
provider: '',
|
||||||
vpc,
|
vpc,
|
||||||
gateway: '',
|
gatewayIP: '',
|
||||||
excludeIps: [],
|
excludeIps: [],
|
||||||
private: false,
|
private: false,
|
||||||
enableDHCP,
|
enableDHCP,
|
||||||
natOutgoing,
|
|
||||||
acls: []
|
acls: []
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -131,10 +129,6 @@ export default {
|
|||||||
label: n.id,
|
label: n.id,
|
||||||
value: n.id,
|
value: n.id,
|
||||||
}));
|
}));
|
||||||
},
|
|
||||||
natOutgoingDisabled() {
|
|
||||||
// Disable the NAT Outgoing option when the subnet belongs to the ovn-cluster VPC and its name is join or ovn-default.
|
|
||||||
return this.value?.spec?.vpc === 'ovn-cluster' && ['join', 'ovn-default'].includes(this.value?.metadata?.name);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -310,20 +304,6 @@ export default {
|
|||||||
</Banner>
|
</Banner>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<RadioGroup
|
|
||||||
v-model:value="value.spec.natOutgoing"
|
|
||||||
name="enableExternalConnectivity"
|
|
||||||
:disabled="natOutgoingDisabled"
|
|
||||||
:options="[true, false]"
|
|
||||||
:label="t('harvester.subnet.externalConnectivity.label')"
|
|
||||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
|
||||||
:mode="mode"
|
|
||||||
:tooltip="t('harvester.subnet.externalConnectivity.tooltip')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-20">
|
<div class="row mt-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
|
|||||||
@@ -1,310 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
import { Banner } from '@components/Banner';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
||||||
import { CONFIG_MAP, SECRET, SERVICE_ACCOUNT } from '@shell/config/types';
|
|
||||||
import { _VIEW } from '@shell/config/query-params';
|
|
||||||
import CopyToClipboard from '@shell/components/CopyToClipboard';
|
|
||||||
import MessageLink from '@shell/components/MessageLink';
|
|
||||||
import { FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types';
|
|
||||||
|
|
||||||
const MAX_FILESYSTEMS = 3;
|
|
||||||
|
|
||||||
const { CONFIGMAP: FS_TYPE_CONFIGMAP, SECRET: FS_TYPE_SECRET, SERVICEACCOUNT: FS_TYPE_SERVICEACCOUNT } = FILESYSTEM_SOURCE_TYPE;
|
|
||||||
|
|
||||||
const DEFAULT_VOLUME_NAMES = {
|
|
||||||
[FS_TYPE_CONFIGMAP]: 'appconfigfs',
|
|
||||||
[FS_TYPE_SECRET]: 'appsecretfs',
|
|
||||||
[FS_TYPE_SERVICEACCOUNT]: 'appserviceaccountfs',
|
|
||||||
};
|
|
||||||
|
|
||||||
const FS_TYPE_OPTIONS = [
|
|
||||||
{ label: 'ConfigMap', value: FS_TYPE_CONFIGMAP },
|
|
||||||
{ label: 'Secret', value: FS_TYPE_SECRET },
|
|
||||||
{ label: 'ServiceAccount', value: FS_TYPE_SERVICEACCOUNT },
|
|
||||||
];
|
|
||||||
|
|
||||||
function emptyRow() {
|
|
||||||
return {
|
|
||||||
fsType: FS_TYPE_CONFIGMAP,
|
|
||||||
volumeName: DEFAULT_VOLUME_NAMES[FS_TYPE_CONFIGMAP],
|
|
||||||
resourceName: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'VirtualMachineFilesystem',
|
|
||||||
|
|
||||||
emits: ['update:value'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
Banner,
|
|
||||||
LabeledSelect,
|
|
||||||
LabeledInput,
|
|
||||||
CopyToClipboard,
|
|
||||||
MessageLink,
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'create',
|
|
||||||
},
|
|
||||||
namespace: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { rows: this.value.length > 0 ? this.value.map((r) => ({ ...r })) : [emptyRow()] };
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
value(newVal) {
|
|
||||||
if (newVal) {
|
|
||||||
const incoming = JSON.stringify(newVal);
|
|
||||||
const current = JSON.stringify(this.rows);
|
|
||||||
|
|
||||||
if (incoming !== current) {
|
|
||||||
this.rows = newVal.map((r) => ({ ...r }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
rows: {
|
|
||||||
deep: true,
|
|
||||||
handler(val) {
|
|
||||||
this.$emit('update:value', val.map((r) => ({ ...r })));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
inStore() {
|
|
||||||
return this.$store.getters['currentProduct'].inStore;
|
|
||||||
},
|
|
||||||
|
|
||||||
configMaps() {
|
|
||||||
return this.$store.getters[`${ this.inStore }/all`](CONFIG_MAP)
|
|
||||||
.filter((cm) => !this.namespace || cm.metadata.namespace === this.namespace)
|
|
||||||
.map((cm) => ({ label: cm.metadata.name, value: cm.metadata.name }));
|
|
||||||
},
|
|
||||||
|
|
||||||
secrets() {
|
|
||||||
return this.$store.getters[`${ this.inStore }/all`](SECRET)
|
|
||||||
.filter((s) => !this.namespace || s.metadata.namespace === this.namespace)
|
|
||||||
.map((s) => ({ label: s.metadata.name, value: s.metadata.name }));
|
|
||||||
},
|
|
||||||
|
|
||||||
serviceAccounts() {
|
|
||||||
return this.$store.getters[`${ this.inStore }/all`](SERVICE_ACCOUNT)
|
|
||||||
.filter((sa) => !this.namespace || sa.metadata.namespace === this.namespace)
|
|
||||||
.map((sa) => ({ label: sa.metadata.name, value: sa.metadata.name }));
|
|
||||||
},
|
|
||||||
|
|
||||||
canAddRow() {
|
|
||||||
return this.rows.length < MAX_FILESYSTEMS;
|
|
||||||
},
|
|
||||||
|
|
||||||
isView() {
|
|
||||||
return this.mode === _VIEW;
|
|
||||||
},
|
|
||||||
|
|
||||||
completedRows() {
|
|
||||||
return this.rows.filter((r) => r.fsType && r.volumeName && r.resourceName);
|
|
||||||
},
|
|
||||||
|
|
||||||
allMountCommands() {
|
|
||||||
return this.completedRows.map((r) => this.mountCommands(r)).join('\n');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
fsTypeOptions(currentIndex) {
|
|
||||||
const usedTypes = this.rows
|
|
||||||
.filter((_, i) => i !== currentIndex)
|
|
||||||
.map((r) => r.fsType);
|
|
||||||
|
|
||||||
return FS_TYPE_OPTIONS.filter((opt) => !usedTypes.includes(opt.value));
|
|
||||||
},
|
|
||||||
|
|
||||||
resourceOptions(fsType) {
|
|
||||||
if (fsType === FS_TYPE_CONFIGMAP) return this.configMaps;
|
|
||||||
if (fsType === FS_TYPE_SECRET) return this.secrets;
|
|
||||||
if (fsType === FS_TYPE_SERVICEACCOUNT) return this.serviceAccounts;
|
|
||||||
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
|
|
||||||
onFsTypeChange(row, newType) {
|
|
||||||
row.fsType = newType;
|
|
||||||
row.volumeName = DEFAULT_VOLUME_NAMES[newType] || '';
|
|
||||||
row.resourceName = '';
|
|
||||||
},
|
|
||||||
|
|
||||||
addRow() {
|
|
||||||
if (this.canAddRow) {
|
|
||||||
const usedTypes = this.rows.map((r) => r.fsType);
|
|
||||||
const nextType = FS_TYPE_OPTIONS.find((opt) => !usedTypes.includes(opt.value))?.value || FS_TYPE_CONFIGMAP;
|
|
||||||
|
|
||||||
this.rows.push({
|
|
||||||
fsType: nextType,
|
|
||||||
volumeName: DEFAULT_VOLUME_NAMES[nextType] || '',
|
|
||||||
resourceName: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
removeRow(index) {
|
|
||||||
this.rows.splice(index, 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
mountCommands(row) {
|
|
||||||
const vol = row.volumeName || '<volume-name>';
|
|
||||||
|
|
||||||
return `- mkdir -p /mnt/${ vol }\n- mount -t virtiofs ${ vol } /mnt/${ vol }`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="vm-filesystem">
|
|
||||||
<p class="mb-20">
|
|
||||||
{{ t('harvester.virtualMachine.filesystem.description') }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-for="(row, index) in rows"
|
|
||||||
:key="index"
|
|
||||||
class="filesystem-row mb-15"
|
|
||||||
>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="row.fsType"
|
|
||||||
:label="t('harvester.virtualMachine.filesystem.type')"
|
|
||||||
:options="fsTypeOptions(index)"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="onFsTypeChange(row, $event)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="row.volumeName"
|
|
||||||
:label="t('harvester.virtualMachine.filesystem.volume')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col span-5">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="row.resourceName"
|
|
||||||
:label="t('harvester.virtualMachine.filesystem.resource')"
|
|
||||||
:options="resourceOptions(row.fsType)"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="!isView"
|
|
||||||
class="col span-1 remove-col"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn role-link remove-btn"
|
|
||||||
@click="removeRow(index)"
|
|
||||||
>
|
|
||||||
{{ t('generic.remove') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-if="completedRows.length > 0"
|
|
||||||
color="warning"
|
|
||||||
class="mt-10"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<MessageLink
|
|
||||||
:to="{ hash: '#advanced' }"
|
|
||||||
prefix-label="harvester.virtualMachine.filesystem.mountBannerHint"
|
|
||||||
middle-label="harvester.virtualMachine.filesystem.mountBannerHintLink"
|
|
||||||
suffix-label="harvester.virtualMachine.filesystem.mountBannerHintSuffix"
|
|
||||||
/>
|
|
||||||
<div class="pre-wrapper mt-10">
|
|
||||||
<pre class="mt-5 mb-0">{{ allMountCommands }}</pre>
|
|
||||||
<CopyToClipboard
|
|
||||||
:text="allMountCommands"
|
|
||||||
:show-label="false"
|
|
||||||
class="icon-btn"
|
|
||||||
action-color="bg-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Banner>
|
|
||||||
|
|
||||||
<button
|
|
||||||
v-if="!isView && canAddRow"
|
|
||||||
type="button"
|
|
||||||
class="btn role-tertiary add"
|
|
||||||
@click="addRow"
|
|
||||||
>
|
|
||||||
{{ t('harvester.virtualMachine.filesystem.add') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.vm-filesystem {
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filesystem-row {
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-col {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-btn {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pre-wrapper {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
pre {
|
|
||||||
padding-right: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 5px;
|
|
||||||
padding: 2px 4px;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: var(--success) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -152,10 +152,6 @@ export default {
|
|||||||
return !rows.find((device) => !device.passthroughClaim);
|
return !rows.find((device) => !device.passthroughClaim);
|
||||||
},
|
},
|
||||||
|
|
||||||
canManageGroup(rows = []) {
|
|
||||||
return rows.length > 0 && rows.every((row) => row.canUpdate === true);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeRows(filterRows, parentSriov) {
|
changeRows(filterRows, parentSriov) {
|
||||||
this['filterRows'] = filterRows;
|
this['filterRows'] = filterRows;
|
||||||
this['parentSriov'] = parentSriov;
|
this['parentSriov'] = parentSriov;
|
||||||
@@ -188,10 +184,6 @@ export default {
|
|||||||
:ref="group.key"
|
:ref="group.key"
|
||||||
v-trim-whitespace
|
v-trim-whitespace
|
||||||
class="group-tab"
|
class="group-tab"
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="canManageGroup(group.rows)"
|
|
||||||
class="group-actions"
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="groupIsAllEnabled(group.rows)"
|
v-if="groupIsAllEnabled(group.rows)"
|
||||||
@@ -209,7 +201,6 @@ export default {
|
|||||||
>
|
>
|
||||||
{{ t('harvester.pci.enableGroup') }}
|
{{ t('harvester.pci.enableGroup') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
<span v-clean-html="group.key" />
|
<span v-clean-html="group.key" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -241,9 +232,3 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
</ResourceTable>
|
</ResourceTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.group-actions {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -52,21 +52,31 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedDevices = [];
|
const selectedDevices = [];
|
||||||
|
const oldFormatDevices = [];
|
||||||
|
|
||||||
const vmDevices = this.value?.domain?.devices?.hostDevices || [];
|
const vmDevices = this.value?.domain?.devices?.hostDevices || [];
|
||||||
|
const otherDevices = this.otherDevices(vmDevices).map(({ name }) => name);
|
||||||
const vmDeviceNames = vmDevices.map(({ name }) => name);
|
const vmDeviceNames = vmDevices.map(({ name }) => name);
|
||||||
|
|
||||||
this.pciDevices.forEach((row) => {
|
this.pciDevices.forEach((row) => {
|
||||||
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
|
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
vmDevices.forEach(({ name }) => {
|
vmDevices.forEach(({ name, deviceName }) => {
|
||||||
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
const checkName = (deviceName || '').split('/')?.[1];
|
||||||
|
|
||||||
|
if (checkName && name.includes(checkName) && !otherDevices.includes(name)) {
|
||||||
|
oldFormatDevices.push(name);
|
||||||
|
} else if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
||||||
selectedDevices.push(name);
|
selectedDevices.push(name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (oldFormatDevices.length > 0) {
|
||||||
|
this.oldFormatDevices = oldFormatDevices;
|
||||||
|
} else {
|
||||||
this.selectedDevices = selectedDevices;
|
this.selectedDevices = selectedDevices;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@@ -77,6 +87,7 @@ export default {
|
|||||||
selectedDevices: [],
|
selectedDevices: [],
|
||||||
pciDeviceSchema: this.$store.getters['harvester/schemaFor'](HCI.PCI_DEVICE),
|
pciDeviceSchema: this.$store.getters['harvester/schemaFor'](HCI.PCI_DEVICE),
|
||||||
showMatrix: false,
|
showMatrix: false,
|
||||||
|
oldFormatDevices: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -188,6 +199,11 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
oldFormatDevicesHTML() {
|
||||||
|
return this.oldFormatDevices.map((device) => {
|
||||||
|
return `<li>${ device }</li>`;
|
||||||
|
}).join('');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -211,6 +227,17 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<div
|
||||||
|
v-if="oldFormatDevices.length > 0"
|
||||||
|
class="row"
|
||||||
|
>
|
||||||
|
<div class="col span-12">
|
||||||
|
<Banner color="warning">
|
||||||
|
<p v-clean-html="t('harvester.pci.oldFormatDevices.help', {oldFormatDevicesHTML}, true)" />
|
||||||
|
</Banner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<Banner color="info">
|
<Banner color="info">
|
||||||
@@ -296,4 +323,5 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { _VIEW } from '@shell/config/query-params';
|
|||||||
|
|
||||||
import { NAMESPACE } from '@shell/config/types';
|
import { NAMESPACE } from '@shell/config/types';
|
||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
import { getLoginAwareErrors } from '../../utils/error';
|
|
||||||
|
|
||||||
const _NEW = '_NEW';
|
const _NEW = '_NEW';
|
||||||
|
|
||||||
@@ -215,9 +214,7 @@ export default {
|
|||||||
buttonCb(true);
|
buttonCb(true);
|
||||||
this.cancel();
|
this.cancel();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = this.t('harvester.virtualMachine.genericLoginError');
|
this.errors = [err.message];
|
||||||
|
|
||||||
this.errors = getLoginAwareErrors(err, message);
|
|
||||||
buttonCb(false);
|
buttonCb(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -64,12 +64,6 @@ export default {
|
|||||||
value: 'status.productID',
|
value: 'status.productID',
|
||||||
sort: ['status.productID', 'status.vendorID']
|
sort: ['status.productID', 'status.vendorID']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'classType',
|
|
||||||
labelKey: 'harvester.usb.classType',
|
|
||||||
value: 'status.classType',
|
|
||||||
sort: ['status.classType']
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!isSingleProduct) {
|
if (!isSingleProduct) {
|
||||||
@@ -113,11 +107,6 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
canManageGroup(rows = []) {
|
|
||||||
return rows.every((row) => row.canUpdate === true);
|
|
||||||
},
|
|
||||||
|
|
||||||
groupIsAllEnabled(rows = []) {
|
groupIsAllEnabled(rows = []) {
|
||||||
return !rows.find((device) => !device.passthroughClaim);
|
return !rows.find((device) => !device.passthroughClaim);
|
||||||
},
|
},
|
||||||
@@ -157,10 +146,6 @@ export default {
|
|||||||
:ref="group.key"
|
:ref="group.key"
|
||||||
v-trim-whitespace
|
v-trim-whitespace
|
||||||
class="group-tab"
|
class="group-tab"
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-if="canManageGroup(group.rows)"
|
|
||||||
class="group-actions"
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="groupIsAllEnabled(group.rows)"
|
v-if="groupIsAllEnabled(group.rows)"
|
||||||
@@ -178,7 +163,6 @@ export default {
|
|||||||
>
|
>
|
||||||
{{ t('harvester.usb.enableGroup') }}
|
{{ t('harvester.usb.enableGroup') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
<span v-clean-html="group.key" />
|
<span v-clean-html="group.key" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -191,9 +175,3 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
</ResourceTable>
|
</ResourceTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.group-actions {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -475,6 +475,7 @@ export default {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm bg-primary mr-15 mb-10"
|
class="btn btn-sm bg-primary mr-15 mb-10"
|
||||||
|
:disabled="rows.length === 0"
|
||||||
@click="addVolume(SOURCE_TYPE.NEW)"
|
@click="addVolume(SOURCE_TYPE.NEW)"
|
||||||
>
|
>
|
||||||
{{ t('harvester.virtualMachine.volume.addVolume') }}
|
{{ t('harvester.virtualMachine.volume.addVolume') }}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import Network from './VirtualMachineNetwork';
|
|||||||
import Volume from './VirtualMachineVolume';
|
import Volume from './VirtualMachineVolume';
|
||||||
import SSHKey from './VirtualMachineSSHKey';
|
import SSHKey from './VirtualMachineSSHKey';
|
||||||
import Reserved from './VirtualMachineReserved';
|
import Reserved from './VirtualMachineReserved';
|
||||||
import Filesystem from './VirtualMachineFilesystem';
|
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import MessageLink from '@shell/components/MessageLink';
|
import MessageLink from '@shell/components/MessageLink';
|
||||||
|
|
||||||
@@ -73,7 +72,6 @@ export default {
|
|||||||
Banner,
|
Banner,
|
||||||
MessageLink,
|
MessageLink,
|
||||||
UsbDevices,
|
UsbDevices,
|
||||||
Filesystem,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [CreateEditView, VM_MIXIN],
|
mixins: [CreateEditView, VM_MIXIN],
|
||||||
@@ -93,8 +91,6 @@ export default {
|
|||||||
|
|
||||||
const hostname = this.value.spec.template.spec.hostname || '';
|
const hostname = this.value.spec.template.spec.hostname || '';
|
||||||
|
|
||||||
const customizeDisplayName = !!(this.value.metadata?.annotations?.[HCI_ANNOTATIONS.VM_DISPLAY_NAME]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cloneVM,
|
cloneVM,
|
||||||
count: 2,
|
count: 2,
|
||||||
@@ -105,24 +101,12 @@ export default {
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
hostname,
|
hostname,
|
||||||
isRestartImmediately,
|
isRestartImmediately,
|
||||||
customizeDisplayName,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
...mapGetters({ t: 'i18n/t' }),
|
||||||
|
|
||||||
// VM display name is stored as an annotation; bind a dedicated input to it
|
|
||||||
// so we don't have to mutate metadata.name (which would break the k8s PUT).
|
|
||||||
displayName: {
|
|
||||||
get() {
|
|
||||||
return this.value.metadata?.annotations?.[HCI_ANNOTATIONS.VM_DISPLAY_NAME] || '';
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
this.value.setAnnotation(HCI_ANNOTATIONS.VM_DISPLAY_NAME, val);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
to() {
|
to() {
|
||||||
return {
|
return {
|
||||||
name: 'harvester-c-cluster-resource',
|
name: 'harvester-c-cluster-resource',
|
||||||
@@ -234,9 +218,6 @@ export default {
|
|||||||
usbPassthroughEnabled() {
|
usbPassthroughEnabled() {
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough');
|
return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough');
|
||||||
},
|
},
|
||||||
filesystemEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('supportFilesystem');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -308,12 +289,6 @@ export default {
|
|||||||
this.getInitConfig({ value: this.value, init: this.isCreate });
|
this.getInitConfig({ value: this.value, init: this.isCreate });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
customizeDisplayName(neu) {
|
|
||||||
if (!neu) {
|
|
||||||
this.value.setAnnotation(HCI_ANNOTATIONS.VM_DISPLAY_NAME, '');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -346,7 +321,6 @@ export default {
|
|||||||
const diskRows = this.getDiskRows(this.value);
|
const diskRows = this.getDiskRows(this.value);
|
||||||
|
|
||||||
this['diskRows'] = diskRows;
|
this['diskRows'] = diskRows;
|
||||||
this['filesystemRows'] = this.getFilesystemRows(this.value);
|
|
||||||
const templateId = this.$route.query.templateId;
|
const templateId = this.$route.query.templateId;
|
||||||
const templateVersionId = this.$route.query.versionId;
|
const templateVersionId = this.$route.query.versionId;
|
||||||
|
|
||||||
@@ -636,33 +610,6 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
</NameNsDescription>
|
</NameNsDescription>
|
||||||
|
|
||||||
<div v-if="isSingle">
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<Checkbox
|
|
||||||
v-model:value="customizeDisplayName"
|
|
||||||
class="check"
|
|
||||||
type="checkbox"
|
|
||||||
:label="t('harvester.virtualMachine.input.customizeDisplayName')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="customizeDisplayName"
|
|
||||||
class="row mb-20"
|
|
||||||
>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="displayName"
|
|
||||||
:mode="mode"
|
|
||||||
:label="t('harvester.virtualMachine.input.displayName')"
|
|
||||||
:placeholder="t('harvester.virtualMachine.input.displayNamePlaceholder')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="isCreate"
|
v-if="isCreate"
|
||||||
v-model:value="useTemplate"
|
v-model:value="useTemplate"
|
||||||
@@ -836,23 +783,10 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
|
||||||
v-if="filesystemEnabled"
|
|
||||||
name="filesystem"
|
|
||||||
:label="t('harvester.tab.filesystem')"
|
|
||||||
:weight="-9"
|
|
||||||
>
|
|
||||||
<Filesystem
|
|
||||||
v-model:value="filesystemRows"
|
|
||||||
:mode="mode"
|
|
||||||
:namespace="value.metadata.namespace"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
name="labels"
|
name="labels"
|
||||||
:label="t('generic.labels')"
|
:label="t('generic.labels')"
|
||||||
:weight="-10"
|
:weight="-9"
|
||||||
>
|
>
|
||||||
<Banner color="info">
|
<Banner color="info">
|
||||||
<t k="harvester.virtualMachine.labels.banner" />
|
<t k="harvester.virtualMachine.labels.banner" />
|
||||||
@@ -871,7 +805,7 @@ export default {
|
|||||||
<Tab
|
<Tab
|
||||||
name="instanceLabel"
|
name="instanceLabel"
|
||||||
:label="t('harvester.tab.instanceLabel')"
|
:label="t('harvester.tab.instanceLabel')"
|
||||||
:weight="-11"
|
:weight="-10"
|
||||||
>
|
>
|
||||||
<Banner color="info">
|
<Banner color="info">
|
||||||
<t k="harvester.virtualMachine.instanceLabels.banner" />
|
<t k="harvester.virtualMachine.instanceLabels.banner" />
|
||||||
@@ -892,7 +826,7 @@ export default {
|
|||||||
<Tab
|
<Tab
|
||||||
name="annotations"
|
name="annotations"
|
||||||
:label="t('harvester.tab.annotations')"
|
:label="t('harvester.tab.annotations')"
|
||||||
:weight="-12"
|
:weight="-11"
|
||||||
>
|
>
|
||||||
<Banner color="info">
|
<Banner color="info">
|
||||||
<t k="harvester.virtualMachine.annotations.banner" />
|
<t k="harvester.virtualMachine.annotations.banner" />
|
||||||
@@ -913,7 +847,7 @@ export default {
|
|||||||
<Tab
|
<Tab
|
||||||
name="advanced"
|
name="advanced"
|
||||||
:label="t('harvester.tab.advanced')"
|
:label="t('harvester.tab.advanced')"
|
||||||
:weight="-13"
|
:weight="-12"
|
||||||
>
|
>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
vmiResource() {
|
vmiResource() {
|
||||||
return this.$store.getters['harvester/byId'](HCI.VMI, this.vmResource?.id) || null;
|
const vmiList = this.$store.getters['harvester/all'](HCI.VMI) || [];
|
||||||
|
const vmi = vmiList.find( (VMI) => {
|
||||||
|
return VMI?.metadata?.ownerReferences?.[0]?.uid === this.vmResource?.metadata?.uid;
|
||||||
|
});
|
||||||
|
|
||||||
|
return vmi;
|
||||||
},
|
},
|
||||||
migrationState() {
|
migrationState() {
|
||||||
return this.vmiResource?.migrationState?.status || '';
|
return this.vmiResource?.migrationState?.status || '';
|
||||||
|
|||||||
@@ -14,6 +14,20 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
allNodeNetwork: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
allClusterNetwork: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -144,10 +144,6 @@ harvester:
|
|||||||
migration:
|
migration:
|
||||||
failedMessage: Latest migration failed!
|
failedMessage: Latest migration failed!
|
||||||
title: Migration
|
title: Migration
|
||||||
vmMigrationTitle: '{count, plural, one {Migrating # VM} other {Migrating # VMs}}'
|
|
||||||
selectedVMs: "The following virtual machine(s) will be migrated to the target node"
|
|
||||||
unknownNode: (unknown node)
|
|
||||||
alreadyOnTarget: Already on Target
|
|
||||||
fields:
|
fields:
|
||||||
nodeName:
|
nodeName:
|
||||||
label: Target Node
|
label: Target Node
|
||||||
@@ -252,7 +248,6 @@ harvester:
|
|||||||
suspendSchedule: Suspend
|
suspendSchedule: Suspend
|
||||||
restoreExistingVM: Replace Existing
|
restoreExistingVM: Replace Existing
|
||||||
migrate: Migrate
|
migrate: Migrate
|
||||||
vmMigrate: Virtual Machine Migration
|
|
||||||
cpuAndMemoryHotplug: Edit CPU and Memory
|
cpuAndMemoryHotplug: Edit CPU and Memory
|
||||||
abortMigration: Abort Migration
|
abortMigration: Abort Migration
|
||||||
storageMigration: Storage Migration
|
storageMigration: Storage Migration
|
||||||
@@ -303,7 +298,6 @@ harvester:
|
|||||||
phase: Phase
|
phase: Phase
|
||||||
attachedVM: Attached Virtual Machine
|
attachedVM: Attached Virtual Machine
|
||||||
cpuManager: CPU Manager
|
cpuManager: CPU Manager
|
||||||
routeConnectivityTooltip: Connectivity between the VM network and the management network, which the Harvester nodes are connected to.
|
|
||||||
fingerprint: Fingerprint
|
fingerprint: Fingerprint
|
||||||
value: Value
|
value: Value
|
||||||
actions: Actions
|
actions: Actions
|
||||||
@@ -355,7 +349,6 @@ harvester:
|
|||||||
snapshots: Snapshots
|
snapshots: Snapshots
|
||||||
instanceLabel: Instance Labels
|
instanceLabel: Instance Labels
|
||||||
annotations: Annotations
|
annotations: Annotations
|
||||||
filesystem: Filesystem Volume
|
|
||||||
fields:
|
fields:
|
||||||
version: Version
|
version: Version
|
||||||
name: Name
|
name: Name
|
||||||
@@ -392,6 +385,21 @@ harvester:
|
|||||||
middle: vGPU Devices
|
middle: vGPU Devices
|
||||||
suffix: page.
|
suffix: page.
|
||||||
deviceInTheSameHost: 'You can only select devices on the same host.'
|
deviceInTheSameHost: 'You can only select devices on the same host.'
|
||||||
|
oldFormatDevices:
|
||||||
|
help: |-
|
||||||
|
<p>
|
||||||
|
The following PCI devices are using the old naming convention and need to be updated in the YAML file:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{oldFormatDevicesHTML}
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Please use the following instructions to update the virtual machine:
|
||||||
|
</p>
|
||||||
|
<ol>
|
||||||
|
<li>Stop the virtual machine, edit the virtual machine YAML, and remove the <Code>hostDevices</Code> section, and save virtual machine the changes to the YAML file.</li>
|
||||||
|
<li>Edit the virtual machine, and add the already enabled PCI Device from the list of available PCIDevices, and save and start VM.</li>
|
||||||
|
</ol>
|
||||||
showCompatibility: Show device compatibility matrix
|
showCompatibility: Show device compatibility matrix
|
||||||
hideCompatibility: Hide device compatibility matrix
|
hideCompatibility: Hide device compatibility matrix
|
||||||
claimError: Error enabling passthrough on {name}
|
claimError: Error enabling passthrough on {name}
|
||||||
@@ -409,8 +417,6 @@ harvester:
|
|||||||
suffix: to enable the add-on to successfully manage your PCI devices.
|
suffix: to enable the add-on to successfully manage your PCI devices.
|
||||||
noPCIPermission: Please contact your system administrator to enable the PCI devices first.
|
noPCIPermission: Please contact your system administrator to enable the PCI devices first.
|
||||||
enablePassthroughWarning: Please be careful not to use host-owned PCI devices (e.g., management and VLAN NICs). Incorrect device allocation may cause damage to your cluster, including node failure.
|
enablePassthroughWarning: Please be careful not to use host-owned PCI devices (e.g., management and VLAN NICs). Incorrect device allocation may cause damage to your cluster, including node failure.
|
||||||
disableResourcePooling: Disable Resource Pooling
|
|
||||||
disableResourcePoolingDescription: Assigns this device a unique resource name so it can be pinned to a specific VM, instead of sharing the pool with other identical devices.
|
|
||||||
|
|
||||||
devices:
|
devices:
|
||||||
matrixHostName: Host Name
|
matrixHostName: Host Name
|
||||||
@@ -720,7 +726,6 @@ harvester:
|
|||||||
other {Start}
|
other {Start}
|
||||||
} Now
|
} Now
|
||||||
createSSHKey: Create a New...
|
createSSHKey: Create a New...
|
||||||
genericLoginError: Authentication failed. Please re-log in and try again.
|
|
||||||
installAgent: Install guest agent
|
installAgent: Install guest agent
|
||||||
enableUsb: Enable USB Tablet
|
enableUsb: Enable USB Tablet
|
||||||
advancedOptions:
|
advancedOptions:
|
||||||
@@ -818,18 +823,6 @@ harvester:
|
|||||||
username: Username
|
username: Username
|
||||||
password: Password
|
password: Password
|
||||||
reservedMemory: Reserved Memory
|
reservedMemory: Reserved Memory
|
||||||
customizeDisplayName: Customize virtual machine display name
|
|
||||||
displayNamePlaceholder: Virtual machine alias name
|
|
||||||
displayName: Display Name
|
|
||||||
filesystem:
|
|
||||||
description: Harvester supports filesystem volumes for VM via virtiofs.
|
|
||||||
type: Filesystem Type
|
|
||||||
volume: Volume
|
|
||||||
resource: Resource
|
|
||||||
add: Add
|
|
||||||
mountBannerHint: "Please update the mount path (e.g. /mnt/appconfigfs) to your preferred location, then add the corresponding commands to the"
|
|
||||||
mountBannerHintLink: "runcmd"
|
|
||||||
mountBannerHintSuffix: "in Advanced tab User Data."
|
|
||||||
machineTypeTip: 'Specify a processor architecture to emulate. To see a list of supported architectures, run: qemu-system-x86_64 -cpu ?'
|
machineTypeTip: 'Specify a processor architecture to emulate. To see a list of supported architectures, run: qemu-system-x86_64 -cpu ?'
|
||||||
detail:
|
detail:
|
||||||
tabs:
|
tabs:
|
||||||
@@ -1118,7 +1111,6 @@ harvester:
|
|||||||
replaceExisting: Replace existing
|
replaceExisting: Replace existing
|
||||||
virtualMachineName: Virtual Machine Name
|
virtualMachineName: Virtual Machine Name
|
||||||
keepMacAddress: Keep MAC Address
|
keepMacAddress: Keep MAC Address
|
||||||
haltAfterRestore: Keep powered off after restore
|
|
||||||
matchTarget: The current backup target does not match the existing one.
|
matchTarget: The current backup target does not match the existing one.
|
||||||
progress:
|
progress:
|
||||||
details: Volume details
|
details: Volume details
|
||||||
@@ -1156,9 +1148,6 @@ harvester:
|
|||||||
placeholder: key1=value1, key2=value2
|
placeholder: key1=value1, key2=value2
|
||||||
dhcpOptionBanner: DHCP options is a key/value string concatenate with comma. For more detail, please refer to <a href="https://kubeovn.github.io/docs/v1.13.x/en/kubevirt/dhcp/" target="_blank">KubOVN document</a>
|
dhcpOptionBanner: DHCP options is a key/value string concatenate with comma. For more detail, please refer to <a href="https://kubeovn.github.io/docs/v1.13.x/en/kubevirt/dhcp/" target="_blank">KubOVN document</a>
|
||||||
tooltip: Enable DHCP server for this subnet. When enabled, VMs can automatically obtain IP addresses from this subnet.
|
tooltip: Enable DHCP server for this subnet. When enabled, VMs can automatically obtain IP addresses from this subnet.
|
||||||
externalConnectivity:
|
|
||||||
label: NAT Outgoing
|
|
||||||
tooltip: Enable NAT for VMs using this subnet to access external networks.
|
|
||||||
private:
|
private:
|
||||||
label: Private Subnet
|
label: Private Subnet
|
||||||
tooltip: Enable network isolation for this Subnet. When enabled, VMs can only communicate within this subnet, even if other subnets exist under the same VPC.
|
tooltip: Enable network isolation for this Subnet. When enabled, VMs can only communicate within this subnet, even if other subnets exist under the same VPC.
|
||||||
@@ -1296,13 +1285,6 @@ harvester:
|
|||||||
rancherCluster:
|
rancherCluster:
|
||||||
kubeConfig: Rancher KubeConfig
|
kubeConfig: Rancher KubeConfig
|
||||||
removeUpstreamClusterWhenNamespaceIsDeleted: Remove Upstream Cluster When Namespace Is Deleted
|
removeUpstreamClusterWhenNamespaceIsDeleted: Remove Upstream Cluster When Namespace Is Deleted
|
||||||
clusterPodSecurityStandard:
|
|
||||||
whitelistedNamespaces:
|
|
||||||
label: 'Whitelisted Namespaces'
|
|
||||||
privilegedNamespaces:
|
|
||||||
label: 'Privileged Namespaces'
|
|
||||||
restrictedNamespaces:
|
|
||||||
label: 'Restricted Namespaces'
|
|
||||||
storageNetwork:
|
storageNetwork:
|
||||||
range:
|
range:
|
||||||
placeholder: e.g. 172.16.0.0/24
|
placeholder: e.g. 172.16.0.0/24
|
||||||
@@ -1526,7 +1508,6 @@ harvester:
|
|||||||
vmSnapshot:
|
vmSnapshot:
|
||||||
label: Virtual Machine Snapshots
|
label: Virtual Machine Snapshots
|
||||||
createText: Restore Snapshot
|
createText: Restore Snapshot
|
||||||
title: Restore Virtual Machine
|
|
||||||
snapshot: Snapshot
|
snapshot: Snapshot
|
||||||
|
|
||||||
storage:
|
storage:
|
||||||
@@ -1534,7 +1515,6 @@ harvester:
|
|||||||
useDefault: Use the default storage
|
useDefault: Use the default storage
|
||||||
volumeEncryption: Volume Encryption
|
volumeEncryption: Volume Encryption
|
||||||
secret: Secret
|
secret: Secret
|
||||||
volumeExpansionCheckbox: Enable Expansion
|
|
||||||
migratable:
|
migratable:
|
||||||
label: Migratable
|
label: Migratable
|
||||||
numberOfReplicas:
|
numberOfReplicas:
|
||||||
@@ -1784,8 +1764,6 @@ harvester:
|
|||||||
repository: Image Repository
|
repository: Image Repository
|
||||||
driver:
|
driver:
|
||||||
location: Driver Location
|
location: Driver Location
|
||||||
enable:
|
|
||||||
title: Enable NVIDIA Driver Toolkit
|
|
||||||
parsingSpecError:
|
parsingSpecError:
|
||||||
The field 'spec.valuesContent' has invalid format.
|
The field 'spec.valuesContent' has invalid format.
|
||||||
usbController:
|
usbController:
|
||||||
@@ -1915,8 +1893,6 @@ harvester:
|
|||||||
|
|
||||||
migconfiguration:
|
migconfiguration:
|
||||||
label: vGPU MIG Configurations
|
label: vGPU MIG Configurations
|
||||||
status:
|
|
||||||
outOfSync: Out of Sync
|
|
||||||
infoBanner: To configure the MIG configuration, please disable it first and re-enable after editing the configuration.
|
infoBanner: To configure the MIG configuration, please disable it first and re-enable after editing the configuration.
|
||||||
profileSpec: Profile Specs
|
profileSpec: Profile Specs
|
||||||
profileStatus: Profile Status
|
profileStatus: Profile Status
|
||||||
@@ -1979,7 +1955,6 @@ harvester:
|
|||||||
title: Cannot Disable Passthrough
|
title: Cannot Disable Passthrough
|
||||||
message: Please detach the device from the VM and save it first before disabling passthrough.
|
message: Please detach the device from the VM and save it first before disabling passthrough.
|
||||||
enablePassthroughWarning: 'Please re-enable the USB device if the device path changes in the following situations:<br/> 1) Re-plugging the USB device.<br/> 2) Rebooting the node.<br/><br/>An incorrect device path may cause passthrough to fail.'
|
enablePassthroughWarning: 'Please re-enable the USB device if the device path changes in the following situations:<br/> 1) Re-plugging the USB device.<br/> 2) Rebooting the node.<br/><br/>An incorrect device path may cause passthrough to fail.'
|
||||||
classType: Class Type
|
|
||||||
|
|
||||||
harvesterVlanConfigMigrateDialog:
|
harvesterVlanConfigMigrateDialog:
|
||||||
targetClusterNetwork:
|
targetClusterNetwork:
|
||||||
@@ -2064,7 +2039,6 @@ advancedSettings:
|
|||||||
'harv-max-hotplug-ratio': 'The ratio for kubevirt to limit the maximum CPU and memory that can be hotplugged to a VM. The value could be an integer between 1 and 20, default to 4.'
|
'harv-max-hotplug-ratio': 'The ratio for kubevirt to limit the maximum CPU and memory that can be hotplugged to a VM. The value could be an integer between 1 and 20, default to 4.'
|
||||||
'harv-kubevirt-migration': 'Configure cluster-wide KubeVirt live migration parameters.'
|
'harv-kubevirt-migration': 'Configure cluster-wide KubeVirt live migration parameters.'
|
||||||
'harv-instance-manager-resources': 'Configure resource percentage reservations for Longhorn instance manager V1 and V2. Valid instance manager CPU range between 0 - 40.'
|
'harv-instance-manager-resources': 'Configure resource percentage reservations for Longhorn instance manager V1 and V2. Valid instance manager CPU range between 0 - 40.'
|
||||||
'harv-cluster-pod-security-standard': 'Enforce Kubernetes Pod Security Standards (PSS) at the cluster level.'
|
|
||||||
|
|
||||||
typeLabel:
|
typeLabel:
|
||||||
kubevirt.io.virtualmachine: |-
|
kubevirt.io.virtualmachine: |-
|
||||||
@@ -2187,7 +2161,11 @@ typeLabel:
|
|||||||
one { PCI Device }
|
one { PCI Device }
|
||||||
other { PCI Devices }
|
other { PCI Devices }
|
||||||
}
|
}
|
||||||
|
persistentvolumeclaim: |-
|
||||||
|
{count, plural,
|
||||||
|
one { Volume }
|
||||||
|
other { Volumes }
|
||||||
|
}
|
||||||
network.harvesterhci.io.clusternetwork: |-
|
network.harvesterhci.io.clusternetwork: |-
|
||||||
{count, plural,
|
{count, plural,
|
||||||
one { Cluster Network }
|
one { Cluster Network }
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Banner } from '@components/Banner';
|
|||||||
import Loading from '@shell/components/Loading';
|
import Loading from '@shell/components/Loading';
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
import ResourceTable from '@shell/components/ResourceTable';
|
||||||
import BadgeState from '@shell/components/formatter/BadgeStateFormatter';
|
import BadgeState from '@shell/components/formatter/BadgeStateFormatter';
|
||||||
|
|
||||||
import { NAME, AGE, NAMESPACE, STATE } from '@shell/config/table-headers';
|
import { NAME, AGE, NAMESPACE, STATE } from '@shell/config/table-headers';
|
||||||
import { NETWORK_ATTACHMENT, SCHEMA } from '@shell/config/types';
|
import { NETWORK_ATTACHMENT, SCHEMA } from '@shell/config/types';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
@@ -112,7 +113,6 @@ export default {
|
|||||||
value: 'connectivity',
|
value: 'connectivity',
|
||||||
labelKey: 'tableHeaders.routeConnectivity',
|
labelKey: 'tableHeaders.routeConnectivity',
|
||||||
formatter: 'NetworkRouteConnectivity',
|
formatter: 'NetworkRouteConnectivity',
|
||||||
tooltip: 'harvester.tableHeaders.routeConnectivityTooltip',
|
|
||||||
formatterOpts: { arbitrary: true },
|
formatterOpts: { arbitrary: true },
|
||||||
width: 130,
|
width: 130,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,18 +12,11 @@ import { HCI } from '../types';
|
|||||||
import HarvesterVmState from '../formatters/HarvesterVmState';
|
import HarvesterVmState from '../formatters/HarvesterVmState';
|
||||||
import ConsoleBar from '../components/VMConsoleBar';
|
import ConsoleBar from '../components/VMConsoleBar';
|
||||||
|
|
||||||
const ENCRYPTED_VOLUME_TOOLTIP_KEYS = {
|
|
||||||
all: 'harvester.virtualMachine.volume.lockTooltip.all',
|
|
||||||
partial: 'harvester.virtualMachine.volume.lockTooltip.partial',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const VM_HEADERS = [
|
export const VM_HEADERS = [
|
||||||
STATE,
|
STATE,
|
||||||
{
|
{
|
||||||
...NAME,
|
...NAME,
|
||||||
width: 350,
|
width: 350,
|
||||||
value: 'nameDisplay',
|
|
||||||
sort: ['nameDisplay'],
|
|
||||||
},
|
},
|
||||||
NAMESPACE,
|
NAMESPACE,
|
||||||
{
|
{
|
||||||
@@ -100,9 +93,19 @@ export default {
|
|||||||
this.hasNode = true;
|
this.hasNode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.$store.getters[`${ inStore }/schemaFor`](HCI.NODE_NETWORK)) {
|
||||||
|
_hash.nodeNetworks = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.NODE_NETWORK });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.$store.getters[`${ inStore }/schemaFor`](HCI.CLUSTER_NETWORK)) {
|
||||||
|
_hash.clusterNetworks = this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER_NETWORK });
|
||||||
|
}
|
||||||
|
|
||||||
const hash = await allHash(_hash);
|
const hash = await allHash(_hash);
|
||||||
|
|
||||||
this.allVMs = hash.vms;
|
this.allVMs = hash.vms;
|
||||||
|
this.allNodeNetworks = hash.nodeNetworks || [];
|
||||||
|
this.allClusterNetworks = hash.clusterNetworks || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@@ -110,6 +113,8 @@ export default {
|
|||||||
hasNode: false,
|
hasNode: false,
|
||||||
allVMs: [],
|
allVMs: [],
|
||||||
allVMIs: [],
|
allVMIs: [],
|
||||||
|
allNodeNetworks: [],
|
||||||
|
allClusterNetworks: [],
|
||||||
restartNotificationDisplayed: false,
|
restartNotificationDisplayed: false,
|
||||||
HCI
|
HCI
|
||||||
};
|
};
|
||||||
@@ -158,12 +163,6 @@ export default {
|
|||||||
*/
|
*/
|
||||||
hasBackUpRestoreInProgress() {
|
hasBackUpRestoreInProgress() {
|
||||||
return !!this.rows.find((r) => r.restoreResource && !r.restoreResource.fromSnapshot && !r.restoreResource.isComplete);
|
return !!this.rows.find((r) => r.restoreResource && !r.restoreResource.fromSnapshot && !r.restoreResource.isComplete);
|
||||||
},
|
|
||||||
|
|
||||||
vmRestartRequiredNames() {
|
|
||||||
return this.allVMs
|
|
||||||
.filter((vm) => vm.isRestartRequired)
|
|
||||||
.map((vm) => vm.metadata.name);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -182,7 +181,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
vmRestartRequiredNames(vmNames) {
|
allVMs: {
|
||||||
|
handler(neu) {
|
||||||
|
const vmNames = [];
|
||||||
|
|
||||||
|
neu.forEach((vm) => {
|
||||||
|
if (vm.isRestartRequired) {
|
||||||
|
vmNames.push(vm.metadata.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
const count = vmNames.length;
|
const count = vmNames.length;
|
||||||
|
|
||||||
if ( count === 0 && this.restartNotificationDisplayed) {
|
if ( count === 0 && this.restartNotificationDisplayed) {
|
||||||
@@ -196,7 +203,9 @@ export default {
|
|||||||
if (this.restartNotificationDisplayed) {
|
if (this.restartNotificationDisplayed) {
|
||||||
this.$store.dispatch('growl/clear');
|
this.$store.dispatch('growl/clear');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0 && vmNames.length > 0) {
|
||||||
this.$store.dispatch('growl/warning', {
|
this.$store.dispatch('growl/warning', {
|
||||||
title: this.t('harvester.notification.restartRequired.title', { count }),
|
title: this.t('harvester.notification.restartRequired.title', { count }),
|
||||||
message: this.t('harvester.notification.restartRequired.message', { vmNames: vmNames.join(', ') }),
|
message: this.t('harvester.notification.restartRequired.message', { vmNames: vmNames.join(', ') }),
|
||||||
@@ -204,13 +213,21 @@ export default {
|
|||||||
}, { root: true });
|
}, { root: true });
|
||||||
this.restartNotificationDisplayed = true;
|
this.restartNotificationDisplayed = true;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
lockIconTooltipMessage(row) {
|
lockIconTooltipMessage(row) {
|
||||||
const key = ENCRYPTED_VOLUME_TOOLTIP_KEYS[row.encryptedVolumeType];
|
const message = '';
|
||||||
|
|
||||||
return key ? this.t(key) : '';
|
if (row.encryptedVolumeType === 'all') {
|
||||||
|
return this.t('harvester.virtualMachine.volume.lockTooltip.all');
|
||||||
|
} else if (row.encryptedVolumeType === 'partial') {
|
||||||
|
return this.t('harvester.virtualMachine.volume.lockTooltip.partial');
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -236,6 +253,8 @@ export default {
|
|||||||
<HarvesterVmState
|
<HarvesterVmState
|
||||||
class="vmstate"
|
class="vmstate"
|
||||||
:row="scope.row"
|
:row="scope.row"
|
||||||
|
:all-node-network="allNodeNetworks"
|
||||||
|
:all-cluster-network="allClusterNetworks"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -246,16 +265,16 @@ export default {
|
|||||||
v-if="scope.row.type !== HCI.VMI"
|
v-if="scope.row.type !== HCI.VMI"
|
||||||
:to="scope.row.detailLocation"
|
:to="scope.row.detailLocation"
|
||||||
>
|
>
|
||||||
{{ scope.row.nameDisplay }}
|
{{ scope.row.metadata.name }}
|
||||||
<i
|
<i
|
||||||
v-if="scope.row.encryptedVolumeType !== 'none'"
|
v-if="lockIconTooltipMessage(scope.row)"
|
||||||
v-tooltip="lockIconTooltipMessage(scope.row)"
|
v-tooltip="lockIconTooltipMessage(scope.row)"
|
||||||
class="icon icon-lock"
|
class="icon icon-lock"
|
||||||
:class="{'green-icon': scope.row.encryptedVolumeType === 'all', 'yellow-icon': scope.row.encryptedVolumeType === 'partial'}"
|
:class="{'green-icon': scope.row.encryptedVolumeType === 'all', 'yellow-icon': scope.row.encryptedVolumeType === 'partial'}"
|
||||||
/>
|
/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ scope.row.nameDisplay }}
|
{{ scope.row.metadata.name }}
|
||||||
</span>
|
</span>
|
||||||
<ConsoleBar
|
<ConsoleBar
|
||||||
:resource-type="scope.row"
|
:resource-type="scope.row"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { base64Decode } from '@shell/utils/crypto';
|
|||||||
import { formatSi, parseSi } from '@shell/utils/units';
|
import { formatSi, parseSi } from '@shell/utils/units';
|
||||||
import { _CLONE, _CREATE, _VIEW } from '@shell/config/query-params';
|
import { _CLONE, _CREATE, _VIEW } from '@shell/config/query-params';
|
||||||
import {
|
import {
|
||||||
PV, PVC, STORAGE_CLASS, NODE, SECRET, CONFIG_MAP, SERVICE_ACCOUNT, NETWORK_ATTACHMENT, NAMESPACE, LONGHORN
|
PV, PVC, STORAGE_CLASS, NODE, SECRET, CONFIG_MAP, NETWORK_ATTACHMENT, NAMESPACE, LONGHORN
|
||||||
} from '@shell/config/types';
|
} from '@shell/config/types';
|
||||||
import { HOSTNAME } from '@shell/config/labels-annotations';
|
import { HOSTNAME } from '@shell/config/labels-annotations';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
@@ -25,7 +25,7 @@ import { HCI } from '../../types';
|
|||||||
import { parseVolumeClaimTemplates, EMPTY_IMAGE } from '../../utils/vm';
|
import { parseVolumeClaimTemplates, EMPTY_IMAGE } from '../../utils/vm';
|
||||||
import impl, { QGA_JSON, USB_TABLET } from './impl';
|
import impl, { QGA_JSON, USB_TABLET } from './impl';
|
||||||
import { GIBIBYTE } from '../../utils/unit';
|
import { GIBIBYTE } from '../../utils/unit';
|
||||||
import { VOLUME_MODE, FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
||||||
|
|
||||||
@@ -102,8 +102,6 @@ export default {
|
|||||||
vmims: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMIM }),
|
vmims: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMIM }),
|
||||||
vms: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VM }),
|
vms: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VM }),
|
||||||
secrets: this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }),
|
secrets: this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET }),
|
||||||
configMaps: this.$store.dispatch(`${ inStore }/findAll`, { type: CONFIG_MAP }),
|
|
||||||
serviceAccounts: this.$store.dispatch(`${ inStore }/findAll`, { type: SERVICE_ACCOUNT }),
|
|
||||||
addons: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }),
|
addons: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.ADD_ONS }),
|
||||||
longhornV2Engine: this.$store.dispatch(`${ inStore }/find`, { type: LONGHORN.SETTINGS, id: LONGHORN_V2_DATA_ENGINE }),
|
longhornV2Engine: this.$store.dispatch(`${ inStore }/find`, { type: LONGHORN.SETTINGS, id: LONGHORN_V2_DATA_ENGINE }),
|
||||||
};
|
};
|
||||||
@@ -158,7 +156,6 @@ export default {
|
|||||||
imageId: '',
|
imageId: '',
|
||||||
diskRows: [],
|
diskRows: [],
|
||||||
networkRows: [],
|
networkRows: [],
|
||||||
filesystemRows: [],
|
|
||||||
machineType: '',
|
machineType: '',
|
||||||
machineTypes: [],
|
machineTypes: [],
|
||||||
secretName: '',
|
secretName: '',
|
||||||
@@ -443,7 +440,6 @@ export default {
|
|||||||
this['imageId'] = imageId;
|
this['imageId'] = imageId;
|
||||||
|
|
||||||
this['diskRows'] = diskRows;
|
this['diskRows'] = diskRows;
|
||||||
this['filesystemRows'] = this.getFilesystemRows(vm);
|
|
||||||
|
|
||||||
this.refreshYamlEditor();
|
this.refreshYamlEditor();
|
||||||
},
|
},
|
||||||
@@ -645,80 +641,6 @@ export default {
|
|||||||
this.parseAccessCredentials();
|
this.parseAccessCredentials();
|
||||||
this.parseNetworkRows(this.networkRows);
|
this.parseNetworkRows(this.networkRows);
|
||||||
this.parseDiskRows(this.diskRows);
|
this.parseDiskRows(this.diskRows);
|
||||||
this.parseFilesystemRows();
|
|
||||||
},
|
|
||||||
|
|
||||||
getFilesystemRows(vm) {
|
|
||||||
const _filesystems = vm.spec.template.spec.domain.devices?.filesystems || [];
|
|
||||||
const _volumes = vm.spec.template.spec.volumes || [];
|
|
||||||
|
|
||||||
return _filesystems.map((fs) => {
|
|
||||||
const volume = _volumes.find((v) => v.name === fs.name);
|
|
||||||
let fsType = FILESYSTEM_SOURCE_TYPE.CONFIGMAP;
|
|
||||||
let resourceName = '';
|
|
||||||
|
|
||||||
if (volume?.configMap) {
|
|
||||||
fsType = FILESYSTEM_SOURCE_TYPE.CONFIGMAP;
|
|
||||||
resourceName = volume.configMap.name;
|
|
||||||
} else if (volume?.secret) {
|
|
||||||
fsType = FILESYSTEM_SOURCE_TYPE.SECRET;
|
|
||||||
resourceName = volume.secret.secretName;
|
|
||||||
} else if (volume?.serviceAccount) {
|
|
||||||
fsType = FILESYSTEM_SOURCE_TYPE.SERVICEACCOUNT;
|
|
||||||
resourceName = volume.serviceAccount.serviceAccountName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fsType,
|
|
||||||
volumeName: fs.name,
|
|
||||||
resourceName,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
parseFilesystemRows() {
|
|
||||||
const completedRows = this.filesystemRows.filter(
|
|
||||||
(r) => r.fsType && r.volumeName && r.resourceName
|
|
||||||
);
|
|
||||||
|
|
||||||
const filesystems = completedRows.map((r) => ({
|
|
||||||
name: r.volumeName,
|
|
||||||
virtiofs: {},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const fsVolumes = completedRows.map((r) => {
|
|
||||||
if (r.fsType === FILESYSTEM_SOURCE_TYPE.CONFIGMAP) {
|
|
||||||
return {
|
|
||||||
name: r.volumeName,
|
|
||||||
configMap: { name: r.resourceName },
|
|
||||||
};
|
|
||||||
} else if (r.fsType === FILESYSTEM_SOURCE_TYPE.SECRET) {
|
|
||||||
return {
|
|
||||||
name: r.volumeName,
|
|
||||||
secret: { secretName: r.resourceName },
|
|
||||||
};
|
|
||||||
} else if (r.fsType === FILESYSTEM_SOURCE_TYPE.SERVICEACCOUNT) {
|
|
||||||
return {
|
|
||||||
name: r.volumeName,
|
|
||||||
serviceAccount: { serviceAccountName: r.resourceName },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}).filter(Boolean);
|
|
||||||
|
|
||||||
if (filesystems.length > 0) {
|
|
||||||
this.spec.template.spec.domain.devices['filesystems'] = filesystems;
|
|
||||||
} else {
|
|
||||||
delete this.spec.template.spec.domain.devices['filesystems'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fsVolumes.length > 0) {
|
|
||||||
if (!this.spec.template.spec.volumes) {
|
|
||||||
this.spec.template.spec['volumes'] = [];
|
|
||||||
}
|
|
||||||
this.spec.template.spec.volumes.push(...fsVolumes);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
parseOther() {
|
parseOther() {
|
||||||
@@ -985,22 +907,14 @@ export default {
|
|||||||
const specInterfaces = this.spec?.template?.spec?.domain?.devices?.interfaces;
|
const specInterfaces = this.spec?.template?.spec?.domain?.devices?.interfaces;
|
||||||
const mergedInterfaces = this.mergeInterfaceList(specInterfaces, interfaces);
|
const mergedInterfaces = this.mergeInterfaceList(specInterfaces, interfaces);
|
||||||
|
|
||||||
const devices = {
|
|
||||||
...this.spec.template.spec.domain.devices,
|
|
||||||
interfaces: mergedInterfaces,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.isEdit && networkRow.length === 0) {
|
|
||||||
devices.autoattachPodInterface = false;
|
|
||||||
} else {
|
|
||||||
delete devices.autoattachPodInterface;
|
|
||||||
}
|
|
||||||
|
|
||||||
const spec = {
|
const spec = {
|
||||||
...this.spec.template.spec,
|
...this.spec.template.spec,
|
||||||
domain: {
|
domain: {
|
||||||
...this.spec.template.spec.domain,
|
...this.spec.template.spec.domain,
|
||||||
devices,
|
devices: {
|
||||||
|
...this.spec.template.spec.domain.devices,
|
||||||
|
interfaces: mergedInterfaces,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
networks
|
networks
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import HarvesterResource from './harvester';
|
|||||||
export default class MIGCONFIGURATION extends HarvesterResource {
|
export default class MIGCONFIGURATION extends HarvesterResource {
|
||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
let out = super._availableActions;
|
let out = super._availableActions;
|
||||||
const canUpdate = !!this.linkFor('update');
|
|
||||||
|
|
||||||
out = out.map((action) => {
|
out = out.map((action) => {
|
||||||
if (action.action === 'showConfiguration') {
|
if (action.action === 'showConfiguration') {
|
||||||
@@ -27,13 +26,13 @@ export default class MIGCONFIGURATION extends HarvesterResource {
|
|||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enableConfig',
|
action: 'enableConfig',
|
||||||
enabled: !this.isEnabled && canUpdate,
|
enabled: !this.isEnabled,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable',
|
label: 'Enable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disableConfig',
|
action: 'disableConfig',
|
||||||
enabled: this.isEnabled && canUpdate,
|
enabled: this.isEnabled,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable',
|
label: 'Disable',
|
||||||
},
|
},
|
||||||
@@ -63,27 +62,13 @@ export default class MIGCONFIGURATION extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get stateDisplay() {
|
get stateDisplay() {
|
||||||
if (this.configStatus === 'out-of-sync') {
|
|
||||||
return this.t('harvester.migconfiguration.status.outOfSync');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.actualState;
|
return this.actualState;
|
||||||
}
|
}
|
||||||
|
|
||||||
get stateDescription() {
|
|
||||||
if (this.status?.message) {
|
|
||||||
return this.status.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.stateDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
get stateColor() {
|
get stateColor() {
|
||||||
if (this.configStatus === 'out-of-sync') {
|
const state = this.actualState;
|
||||||
return 'text-warning';
|
|
||||||
}
|
|
||||||
|
|
||||||
return colorForState(this.actualState);
|
return colorForState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
get isEnabled() {
|
get isEnabled() {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class PCIDevice extends SteveModel {
|
|||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enablePassthroughBulk',
|
action: 'enablePassthroughBulk',
|
||||||
enabled: !this.isEnabling && !this.isvGPUDevice && this.canUpdate,
|
enabled: !this.isEnabling && !this.isvGPUDevice,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable Passthrough',
|
label: 'Enable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
@@ -43,7 +43,7 @@ export default class PCIDevice extends SteveModel {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disablePassthrough',
|
action: 'disablePassthrough',
|
||||||
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice && this.canUpdate,
|
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable Passthrough',
|
label: 'Disable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
@@ -54,10 +54,6 @@ export default class PCIDevice extends SteveModel {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canUpdate() {
|
|
||||||
return !!this.linkFor('update');
|
|
||||||
}
|
|
||||||
|
|
||||||
get isvGPUDevice() {
|
get isvGPUDevice() {
|
||||||
if (!this.vGPUAsPCIDeviceFeatureEnabled) {
|
if (!this.vGPUAsPCIDeviceFeatureEnabled) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ export default class SRIOVDevice extends SteveModel {
|
|||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enableDevice',
|
action: 'enableDevice',
|
||||||
enabled: !this.isEnabled && this.canUpdate,
|
enabled: !this.isEnabled,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable',
|
label: 'Enable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disableDevice',
|
action: 'disableDevice',
|
||||||
enabled: this.isEnabled && this.canUpdate,
|
enabled: this.isEnabled,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable',
|
label: 'Disable',
|
||||||
},
|
},
|
||||||
@@ -31,10 +31,6 @@ export default class SRIOVDevice extends SteveModel {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canUpdate() {
|
|
||||||
return !!this.linkFor('update');
|
|
||||||
}
|
|
||||||
|
|
||||||
get canYaml() {
|
get canYaml() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default class USBDevice extends SteveModel {
|
|||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enablePassthroughBulk',
|
action: 'enablePassthroughBulk',
|
||||||
enabled: !this.passthroughClaim && !this.status.enabled && this.canUpdate,
|
enabled: !this.passthroughClaim && !this.status.enabled,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable Passthrough',
|
label: 'Enable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
@@ -42,7 +42,7 @@ export default class USBDevice extends SteveModel {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disablePassthrough',
|
action: 'disablePassthrough',
|
||||||
enabled: this.status.enabled && this.canUpdate,
|
enabled: this.status.enabled,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable Passthrough',
|
label: 'Disable Passthrough',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
@@ -53,10 +53,6 @@ export default class USBDevice extends SteveModel {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
get canUpdate() {
|
|
||||||
return !!this.linkFor('update');
|
|
||||||
}
|
|
||||||
|
|
||||||
get canYaml() {
|
get canYaml() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,18 +27,17 @@ const STATUS_DISPLAY = {
|
|||||||
export default class VGpuDevice extends SteveModel {
|
export default class VGpuDevice extends SteveModel {
|
||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
const out = super._availableActions;
|
const out = super._availableActions;
|
||||||
const canUpdate = !!this.linkFor('update');
|
|
||||||
|
|
||||||
out.push(
|
out.push(
|
||||||
{
|
{
|
||||||
action: 'enableVGpu',
|
action: 'enableVGpu',
|
||||||
enabled: !this.isEnabled && canUpdate,
|
enabled: !this.isEnabled,
|
||||||
icon: 'icon icon-fw icon-dot',
|
icon: 'icon icon-fw icon-dot',
|
||||||
label: 'Enable',
|
label: 'Enable',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'disableVGpu',
|
action: 'disableVGpu',
|
||||||
enabled: this.isEnabled && canUpdate,
|
enabled: this.isEnabled,
|
||||||
icon: 'icon icon-fw icon-dot-open',
|
icon: 'icon icon-fw icon-dot-open',
|
||||||
label: 'Disable',
|
label: 'Disable',
|
||||||
bulkable: true,
|
bulkable: true,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const OBSCURE_NAMESPACE_PREFIX = [
|
|||||||
|
|
||||||
export default class HciNamespace extends namespace {
|
export default class HciNamespace extends namespace {
|
||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
let out = super._availableActions;
|
const out = super._availableActions;
|
||||||
const remove = out.findIndex((a) => a.action === 'promptRemove');
|
const remove = out.findIndex((a) => a.action === 'promptRemove');
|
||||||
|
|
||||||
const promptRemove = {
|
const promptRemove = {
|
||||||
@@ -53,16 +53,6 @@ export default class HciNamespace extends namespace {
|
|||||||
insertAt(out, out.length - 1, promptRemove);
|
insertAt(out, out.length - 1, promptRemove);
|
||||||
insertAt(out, out.length - 5, editQuotaAction);
|
insertAt(out, out.length - 5, editQuotaAction);
|
||||||
|
|
||||||
const canUpdate = !!this.linkFor('update');
|
|
||||||
|
|
||||||
out = out.map((action) => {
|
|
||||||
if (['move'].includes(action.action)) {
|
|
||||||
return { ...action, enabled: action.enabled && canUpdate };
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
|
||||||
});
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,20 +5,6 @@ import Secret from '@shell/models/secret';
|
|||||||
import { NAMESPACE } from '@shell/config/types';
|
import { NAMESPACE } from '@shell/config/types';
|
||||||
|
|
||||||
export default class HciSecret extends Secret {
|
export default class HciSecret extends Secret {
|
||||||
get _availableActions() {
|
|
||||||
let out = super._availableActions;
|
|
||||||
|
|
||||||
out = out.map((action) => {
|
|
||||||
if (['download'].includes(action.action)) {
|
|
||||||
return { ...action, enabled: !!this.linkFor('update') };
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
|
||||||
});
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent harvester secret detail page be overridden.
|
// prevent harvester secret detail page be overridden.
|
||||||
// See isFullPageOverride in https://github.com/rancher/dashboard/blob/master/shell/components/ResourceDetail/index.vue
|
// See isFullPageOverride in https://github.com/rancher/dashboard/blob/master/shell/components/ResourceDetail/index.vue
|
||||||
get fullDetailPageOverride() {
|
get fullDetailPageOverride() {
|
||||||
|
|||||||
@@ -96,10 +96,6 @@ export default class HciStorageClass extends StorageClass {
|
|||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
get expandOnlineEncryptedVolumeFeatureEnabled() {
|
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('expandOnlineEncryptedVolume');
|
|
||||||
}
|
|
||||||
|
|
||||||
get thirdPartyStorageFeatureEnabled() {
|
get thirdPartyStorageFeatureEnabled() {
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
}
|
}
|
||||||
@@ -110,15 +106,6 @@ export default class HciStorageClass extends StorageClass {
|
|||||||
|
|
||||||
get availableActions() {
|
get availableActions() {
|
||||||
let out = super.availableActions || [];
|
let out = super.availableActions || [];
|
||||||
const canUpdate = !!this.linkFor('update');
|
|
||||||
|
|
||||||
out = out.map((action) => {
|
|
||||||
if (['setDefault', 'setAsDefault', 'resetDefault'].includes(action.action)) {
|
|
||||||
return { ...action, enabled: canUpdate };
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.isInternalStorageClass()) {
|
if (this.isInternalStorageClass()) {
|
||||||
out = out.filter((action) => {
|
out = out.filter((action) => {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { HCI as HCI_ANNOTATIONS } from '../config/labels-annotations';
|
|||||||
import HarvesterResource from './harvester';
|
import HarvesterResource from './harvester';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
|
|
||||||
const HARVESTER_NVIDIA_DRIVER_TOOLKIT = 'harvester-system/nvidia-driver-toolkit';
|
|
||||||
|
|
||||||
export default class HciAddonConfig extends HarvesterResource {
|
export default class HciAddonConfig extends HarvesterResource {
|
||||||
get availableActions() {
|
get availableActions() {
|
||||||
const out = super._availableActions;
|
const out = super._availableActions;
|
||||||
@@ -21,10 +19,9 @@ export default class HciAddonConfig extends HarvesterResource {
|
|||||||
out.push(rancherDashboard);
|
out.push(rancherDashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
const canUpdate = !!this.linkFor('update');
|
|
||||||
const toggleAddon = {
|
const toggleAddon = {
|
||||||
action: 'toggleAddon',
|
action: 'toggleAddon',
|
||||||
enabled: canUpdate,
|
enabled: true,
|
||||||
icon: this.spec.enabled ? 'icon icon-pause' : 'icon icon-play',
|
icon: this.spec.enabled ? 'icon icon-pause' : 'icon icon-play',
|
||||||
label: this.spec.enabled ? this.t('generic.disable') : this.t('generic.enable'),
|
label: this.spec.enabled ? this.t('generic.disable') : this.t('generic.enable'),
|
||||||
};
|
};
|
||||||
@@ -48,15 +45,6 @@ export default class HciAddonConfig extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.spec.enabled && this.id === HARVESTER_NVIDIA_DRIVER_TOOLKIT) {
|
|
||||||
this.$dispatch('promptModal', {
|
|
||||||
resources: [this],
|
|
||||||
component: 'HarvesterEnableNvidiaDriverToolkit',
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.spec.enabled = !this.spec.enabled;
|
this.spec.enabled = !this.spec.enabled;
|
||||||
await this.save();
|
await this.save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -3,20 +3,6 @@ import { findBy } from '@shell/utils/array';
|
|||||||
import HarvesterResource from './harvester';
|
import HarvesterResource from './harvester';
|
||||||
|
|
||||||
export default class HciKeypair extends HarvesterResource {
|
export default class HciKeypair extends HarvesterResource {
|
||||||
get _availableActions() {
|
|
||||||
let out = super._availableActions;
|
|
||||||
|
|
||||||
out = out.map((action) => {
|
|
||||||
if (['download'].includes(action.action)) {
|
|
||||||
return { ...action, enabled: !!this.linkFor('update') };
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
|
||||||
});
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
get stateDisplay() {
|
get stateDisplay() {
|
||||||
const conditions = get(this, 'status.conditions');
|
const conditions = get(this, 'status.conditions');
|
||||||
const status = (findBy(conditions, 'type', 'validated') || {}).status ;
|
const status = (findBy(conditions, 'type', 'validated') || {}).status ;
|
||||||
|
|||||||
@@ -19,18 +19,16 @@ export default class ScheduleVmBackup extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const canUpdate = !!this.linkFor('update');
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: 'resumeSchedule',
|
action: 'resumeSchedule',
|
||||||
enabled: canUpdate && ucFirst(this.state) === STATES.suspended.label,
|
enabled: ucFirst(this.state) === STATES.suspended.label,
|
||||||
icon: 'icons icon-play',
|
icon: 'icons icon-play',
|
||||||
label: this.t('harvester.action.resumeSchedule'),
|
label: this.t('harvester.action.resumeSchedule'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'suspendSchedule',
|
action: 'suspendSchedule',
|
||||||
enabled: canUpdate && ucFirst(this.state) === STATES.active.label,
|
enabled: ucFirst(this.state) === STATES.active.label,
|
||||||
icon: 'icons icon-pause',
|
icon: 'icons icon-pause',
|
||||||
label: this.t('harvester.action.suspendSchedule'),
|
label: this.t('harvester.action.suspendSchedule'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ function isReady() {
|
|||||||
export default class HciVmImage extends HarvesterResource {
|
export default class HciVmImage extends HarvesterResource {
|
||||||
get availableActions() {
|
get availableActions() {
|
||||||
let out = super._availableActions;
|
let out = super._availableActions;
|
||||||
|
const toFilter = ['goToEditYaml'];
|
||||||
|
|
||||||
|
out = out.filter( (A) => !toFilter.includes(A.action));
|
||||||
|
|
||||||
// show `Clone` only when imageSource is `download`
|
// show `Clone` only when imageSource is `download`
|
||||||
if (this.imageSource !== 'download') {
|
if (this.imageSource !== 'download') {
|
||||||
@@ -52,7 +55,6 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
canCreateVM = false;
|
canCreateVM = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canCreateImage = !!this.$getters?.['schemaFor']?.(HCI.IMAGE)?.collectionMethods?.some((method) => method.toLowerCase() === 'post');
|
|
||||||
const customActions = this.isReady ? [
|
const customActions = this.isReady ? [
|
||||||
{
|
{
|
||||||
action: 'createFromImage',
|
action: 'createFromImage',
|
||||||
@@ -62,13 +64,13 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'encryptImage',
|
action: 'encryptImage',
|
||||||
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted && canCreateImage,
|
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted,
|
||||||
icon: 'icon icon-lock',
|
icon: 'icon icon-lock',
|
||||||
label: this.t('harvester.action.encryptImage'),
|
label: this.t('harvester.action.encryptImage'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'decryptImage',
|
action: 'decryptImage',
|
||||||
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted && canCreateImage,
|
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted,
|
||||||
icon: 'icon icon-unlock',
|
icon: 'icon icon-unlock',
|
||||||
label: this.t('harvester.action.decryptImage'),
|
label: this.t('harvester.action.decryptImage'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,17 +23,17 @@ export default class HciVmTemplateVersion extends HarvesterResource {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const schema = this.$getters['schemaFor'](HCI.VM);
|
const schema = this.$getters['schemaFor'](HCI.VM);
|
||||||
let canCreateVM = false;
|
let canCreateVM = true;
|
||||||
|
|
||||||
if (schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase())) ) {
|
if ( schema && !schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase())) ) {
|
||||||
canCreateVM = true;
|
canCreateVM = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: 'launchFromTemplate',
|
action: 'launchFromTemplate',
|
||||||
icon: 'icon icon-spinner',
|
icon: 'icon icon-spinner',
|
||||||
enabled: canCreateVM && this.isReady,
|
disabled: !canCreateVM || !this.isReady,
|
||||||
label: this.t('harvester.action.launchFormTemplate'),
|
label: this.t('harvester.action.launchFormTemplate'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -83,42 +83,6 @@ const VMIPhase = {
|
|||||||
|
|
||||||
let productInStore;
|
let productInStore;
|
||||||
|
|
||||||
let _podOwnerMap = null;
|
|
||||||
let _podOwnerMapSource = null;
|
|
||||||
|
|
||||||
function getPodByOwnerName(rootGetters, inStore, ownerName) {
|
|
||||||
const podList = rootGetters[`${ inStore }/all`](POD);
|
|
||||||
|
|
||||||
if (!Array.isArray(podList)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
// if not equals (usually means the pod list has been updated), we need to rebuild the map, otherwise we can reuse the map for better performance
|
|
||||||
if (_podOwnerMapSource !== podList) {
|
|
||||||
_podOwnerMap = new Map(); // use Map to store ownerReference name and pod mapping
|
|
||||||
for (const pod of podList) {
|
|
||||||
const refName = pod.metadata?.ownerReferences?.[0]?.name;
|
|
||||||
|
|
||||||
if (refName) {
|
|
||||||
_podOwnerMap.set(refName, pod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_podOwnerMapSource = podList;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _podOwnerMap.get(ownerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPvcsByNames(rootGetters, inStore, names) {
|
|
||||||
const pvcList = rootGetters[`${ inStore }/all`](PVC);
|
|
||||||
|
|
||||||
if (!Array.isArray(pvcList)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const uniqueNames = new Set(names);
|
|
||||||
|
|
||||||
return pvcList.filter((pvc) => uniqueNames.has(pvc.metadata?.name));
|
|
||||||
}
|
|
||||||
|
|
||||||
const IgnoreMessages = ['pod has unbound immediate PersistentVolumeClaims'];
|
const IgnoreMessages = ['pod has unbound immediate PersistentVolumeClaims'];
|
||||||
|
|
||||||
export default class VirtVm extends HarvesterResource {
|
export default class VirtVm extends HarvesterResource {
|
||||||
@@ -130,8 +94,6 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
clone.action = 'goToCloneVM';
|
clone.action = 'goToCloneVM';
|
||||||
}
|
}
|
||||||
|
|
||||||
const canCreateVMSSchedule = !!this.$getters?.['schemaFor']?.(HCI.SCHEDULE_VM_BACKUP)?.collectionMethods?.find((x) => ['post'].includes(x.toLowerCase()));
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: 'stopVM',
|
action: 'stopVM',
|
||||||
@@ -164,7 +126,6 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'restartVM',
|
action: 'restartVM',
|
||||||
altAction: 'altRestartVM',
|
|
||||||
enabled: !!this.actions?.restart,
|
enabled: !!this.actions?.restart,
|
||||||
icon: 'icon icon-refresh',
|
icon: 'icon icon-refresh',
|
||||||
label: this.t('harvester.action.restart'),
|
label: this.t('harvester.action.restart'),
|
||||||
@@ -173,7 +134,6 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'softrebootVM',
|
action: 'softrebootVM',
|
||||||
altAction: 'doSoftReboot',
|
|
||||||
enabled: !!this.actions?.softreboot,
|
enabled: !!this.actions?.softreboot,
|
||||||
icon: 'icon icon-pipeline',
|
icon: 'icon icon-pipeline',
|
||||||
label: this.t('harvester.action.softreboot')
|
label: this.t('harvester.action.softreboot')
|
||||||
@@ -211,7 +171,7 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'createSchedule',
|
action: 'createSchedule',
|
||||||
enabled: canCreateVMSSchedule && this.schedulingVMBackupFeatureEnabled,
|
enabled: this.schedulingVMBackupFeatureEnabled,
|
||||||
icon: 'icon icon-history',
|
icon: 'icon icon-history',
|
||||||
label: this.t('harvester.action.createSchedule')
|
label: this.t('harvester.action.createSchedule')
|
||||||
},
|
},
|
||||||
@@ -231,9 +191,7 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
action: 'migrateVM',
|
action: 'migrateVM',
|
||||||
enabled: !!this.actions?.migrate,
|
enabled: !!this.actions?.migrate,
|
||||||
icon: 'icon icon-copy',
|
icon: 'icon icon-copy',
|
||||||
label: this.t('harvester.action.vmMigrate'),
|
label: this.t('harvester.action.migrate')
|
||||||
bulkable: true,
|
|
||||||
bulkAction: 'migrateVM'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'abortMigrationVM',
|
action: 'abortMigrationVM',
|
||||||
@@ -373,10 +331,6 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
this.metadata.annotations[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE] = JSON.stringify(deleteDataSource);
|
this.metadata.annotations[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE] = JSON.stringify(deleteDataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
altRestartVM() {
|
|
||||||
this.doActionGrowl('restart', {});
|
|
||||||
}
|
|
||||||
|
|
||||||
restartVM(resources = this) {
|
restartVM(resources = this) {
|
||||||
this.$dispatch('promptModal', {
|
this.$dispatch('promptModal', {
|
||||||
resources,
|
resources,
|
||||||
@@ -706,13 +660,16 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
|
|
||||||
get podResource() {
|
get podResource() {
|
||||||
const inStore = this.productInStore;
|
const inStore = this.productInStore;
|
||||||
|
|
||||||
const vmiResource = this.$rootGetters[`${ inStore }/byId`](HCI.VMI, this.id);
|
const vmiResource = this.$rootGetters[`${ inStore }/byId`](HCI.VMI, this.id);
|
||||||
|
const podList = this.$rootGetters[`${ inStore }/all`](POD);
|
||||||
|
|
||||||
if (!vmiResource?.metadata?.name) {
|
return podList.find((P) => {
|
||||||
return undefined;
|
return (
|
||||||
}
|
vmiResource?.metadata?.name &&
|
||||||
|
vmiResource?.metadata?.name === P.metadata?.ownerReferences?.[0].name
|
||||||
return getPodByOwnerName(this.$rootGetters, inStore, vmiResource.metadata.name);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get isPaused() {
|
get isPaused() {
|
||||||
@@ -753,13 +710,17 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
get vmi() {
|
get vmi() {
|
||||||
const inStore = this.productInStore;
|
const inStore = this.productInStore;
|
||||||
|
|
||||||
return this.$rootGetters[`${ inStore }/byId`](HCI.VMI, this.id);
|
const vmis = this.$rootGetters[`${ inStore }/all`](HCI.VMI);
|
||||||
|
|
||||||
|
return vmis.find((VMI) => VMI.id === this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
get volumes() {
|
get volumes() {
|
||||||
|
const pvcs = this.$rootGetters[`${ this.productInStore }/all`](PVC);
|
||||||
|
|
||||||
const volumeClaimNames = this.spec.template.spec.volumes?.map((v) => v.persistentVolumeClaim?.claimName).filter((v) => !!v) || [];
|
const volumeClaimNames = this.spec.template.spec.volumes?.map((v) => v.persistentVolumeClaim?.claimName).filter((v) => !!v) || [];
|
||||||
|
|
||||||
return getPvcsByNames(this.$rootGetters, this.productInStore, volumeClaimNames);
|
return pvcs.filter((pvc) => volumeClaimNames.includes(pvc.metadata.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
get lvmVolumes() {
|
get lvmVolumes() {
|
||||||
@@ -792,6 +753,17 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return { status: 'VMI error', detailedMessage: vmiFailureCond.message };
|
return { status: 'VMI error', detailedMessage: vmiFailureCond.message };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((this.vmi || this.isVMCreated) && this.podResource) {
|
||||||
|
// const podStatus = this.podResource.getPodStatus;
|
||||||
|
// if (POD_STATUS_ALL_ERROR.includes(podStatus?.status)) {
|
||||||
|
// return {
|
||||||
|
// ...podStatus,
|
||||||
|
// status: 'LAUNCHER_POD_ERROR',
|
||||||
|
// pod: this.podResource,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
return this?.vmi?.status?.phase;
|
return this?.vmi?.status?.phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,7 +901,9 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
|
|
||||||
const inStore = this.productInStore;
|
const inStore = this.productInStore;
|
||||||
|
|
||||||
const res = this.$rootGetters[`${ inStore }/byId`](HCI.RESTORE, id);
|
const allRestore = this.$rootGetters[`${ inStore }/all`](HCI.RESTORE);
|
||||||
|
|
||||||
|
const res = allRestore.find((O) => O.id === id);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
const allBackups = this.$rootGetters[`${ inStore }/all`](HCI.BACKUP);
|
const allBackups = this.$rootGetters[`${ inStore }/all`](HCI.BACKUP);
|
||||||
@@ -1099,6 +1073,42 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get warningCount() {
|
||||||
|
return this.resourcesStatus.warningCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get errorCount() {
|
||||||
|
return this.resourcesStatus.errorCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get resourcesStatus() {
|
||||||
|
const inStore = this.productInStore;
|
||||||
|
const vmList = this.$rootGetters[`${ inStore }/all`](HCI.VM);
|
||||||
|
let warningCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
vmList.forEach((vm) => {
|
||||||
|
const status = vm.actualState;
|
||||||
|
|
||||||
|
if (status === VM_ERROR) {
|
||||||
|
errorCount += 1;
|
||||||
|
} else if (
|
||||||
|
status === 'Stopping' ||
|
||||||
|
status === 'Waiting' ||
|
||||||
|
status === 'Pending' ||
|
||||||
|
status === 'Starting' ||
|
||||||
|
status === 'Terminating'
|
||||||
|
) {
|
||||||
|
warningCount += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
warningCount,
|
||||||
|
errorCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
get volumeClaimTemplates() {
|
get volumeClaimTemplates() {
|
||||||
return parseVolumeClaimTemplates(this);
|
return parseVolumeClaimTemplates(this);
|
||||||
}
|
}
|
||||||
@@ -1116,6 +1126,7 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
get rootImageId() {
|
get rootImageId() {
|
||||||
let imageId = '';
|
let imageId = '';
|
||||||
const inStore = this.productInStore;
|
const inStore = this.productInStore;
|
||||||
|
const pvcs = this.$rootGetters[`${ inStore }/all`](PVC) || [];
|
||||||
|
|
||||||
const volumes = this.spec.template.spec.volumes || [];
|
const volumes = this.spec.template.spec.volumes || [];
|
||||||
|
|
||||||
@@ -1125,7 +1136,9 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!isNoExistingVolume) {
|
if (!isNoExistingVolume) {
|
||||||
const existingVolume = this.$rootGetters[`${ inStore }/byId`](PVC, `${ this.metadata.namespace }/${ firstVolumeName }`);
|
const existingVolume = pvcs.find(
|
||||||
|
(P) => P.id === `${ this.metadata.namespace }/${ firstVolumeName }`
|
||||||
|
);
|
||||||
|
|
||||||
if (existingVolume) {
|
if (existingVolume) {
|
||||||
return existingVolume?.metadata?.annotations?.[
|
return existingVolume?.metadata?.annotations?.[
|
||||||
@@ -1274,10 +1287,6 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('schedulingVMBackup');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('schedulingVMBackup');
|
||||||
}
|
}
|
||||||
|
|
||||||
get nameDisplay() {
|
|
||||||
return this.metadata?.annotations?.[HCI_ANNOTATIONS.VM_DISPLAY_NAME] || this.metadata?.name || this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get volumeEncryptionFeatureEnabled() {
|
get volumeEncryptionFeatureEnabled() {
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
||||||
}
|
}
|
||||||
@@ -1307,7 +1316,8 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isBackupTargetUnavailable() {
|
get isBackupTargetUnavailable() {
|
||||||
const backupTargetSetting = this.$rootGetters['harvester/byId'](HCI.SETTING, 'backup-target');
|
const allSettings = this.$rootGetters['harvester/all'](HCI.SETTING) || [];
|
||||||
|
const backupTargetSetting = allSettings.find( (O) => O.id === 'backup-target');
|
||||||
|
|
||||||
return isBackupTargetSettingUnavailable(backupTargetSetting);
|
return isBackupTargetSettingUnavailable(backupTargetSetting);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
import shellProject from '@shell/models/management.cattle.io.project';
|
|
||||||
|
|
||||||
// This model controls `Project / Namespace` page in rancher integration mode
|
|
||||||
// Extend management.cattle.io.project model from shell
|
|
||||||
export default class Project extends shellProject {
|
|
||||||
get _availableActions() {
|
|
||||||
const canUpdate = !!this.linkFor('update');
|
|
||||||
|
|
||||||
// disable `Edit Config` action if user does not have update permission.
|
|
||||||
return super._availableActions.map((action) => {
|
|
||||||
if (action.action === 'goToEdit') {
|
|
||||||
return { ...action, enabled: canUpdate };
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -57,11 +57,7 @@ export default class HciVlanConfig extends HarvesterResource {
|
|||||||
get _availableActions() {
|
get _availableActions() {
|
||||||
const out = super._availableActions;
|
const out = super._availableActions;
|
||||||
|
|
||||||
const canMigrate = !!this.$getters?.['schemaFor']?.(HCI.VLAN_CONFIG)?.collectionMethods?.find((x) => ['post'].includes(x.toLowerCase()));
|
|
||||||
|
|
||||||
if (canMigrate) {
|
|
||||||
insertAt(out, 0, this.migrateAction);
|
insertAt(out, 0, this.migrateAction);
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester",
|
"name": "harvester",
|
||||||
"description": "Rancher UI Extension for Harvester",
|
"description": "Rancher UI Extension for Harvester",
|
||||||
"version": "1.9.0-dev",
|
"version": "1.8.2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"rancher": {
|
"rancher": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
|
|||||||
@@ -123,26 +123,5 @@ export default {
|
|||||||
const clusterId = currentCluster.id;
|
const clusterId = currentCluster.id;
|
||||||
|
|
||||||
return projectsInAllClusters.filter((project: any) => project.spec.clusterName === clusterId && project.nameDisplay !== 'System');
|
return projectsInAllClusters.filter((project: any) => project.spec.clusterName === clusterId && project.nameDisplay !== 'System');
|
||||||
},
|
|
||||||
|
|
||||||
// Few harvester resources name and REAL resource are different. E.g. HCI.NETWORK_ATTACHMENT page resource is NETWORK_ATTACHMENT.
|
|
||||||
// Check in config/harvester-cluster.js for more details.
|
|
||||||
// We need to look up the schema by resource name, and fallback to find using real resource name
|
|
||||||
schemaFor: (state, getters, rootState, rootGetters) => (type, _fuzzy = false, _allowThrow = true) => {
|
|
||||||
// follow the same logic as type-map/schemaFor in /dashboard/shell/plugins/dashboard-store/getters.js
|
|
||||||
const normalizedType = getters.normalizeType(type);
|
|
||||||
const schemas = state.types['schema'];
|
|
||||||
const out = schemas?.map?.get(normalizedType);
|
|
||||||
|
|
||||||
if (out) return out;
|
|
||||||
|
|
||||||
// if not found, use the resource mapping in configureType for a second try
|
|
||||||
const resourceType = rootGetters['type-map/optionsFor'](type)?.resource;
|
|
||||||
if (resourceType && resourceType !== type) {
|
|
||||||
const normalizedResource = getters.normalizeType(resourceType);
|
|
||||||
return schemas?.map?.get(normalizedResource) || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,42 +31,22 @@ export function registerAddonSideNav(store, productName, {
|
|||||||
}, 600);
|
}, 600);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasAccessibleSchema = (t) => {
|
// Adds or removes the resource IDs from the product visibility whitelist.
|
||||||
try {
|
const setMenuVisibility = (visible) => {
|
||||||
return !!store.getters[`${ productName }/schemaFor`]?.(t);
|
if (visible) {
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const showTypes = (visibleTypes) => {
|
|
||||||
store.commit('type-map/basicType', {
|
store.commit('type-map/basicType', {
|
||||||
product: productName,
|
product: productName,
|
||||||
group: navGroup,
|
group: navGroup,
|
||||||
types: visibleTypes
|
types
|
||||||
});
|
});
|
||||||
};
|
} else {
|
||||||
|
// Manually delete the keys from the state object to hide them.
|
||||||
const hideTypes = () => {
|
|
||||||
const basicTypes = store.state['type-map'].basicTypes[productName];
|
const basicTypes = store.state['type-map'].basicTypes[productName];
|
||||||
|
|
||||||
if (basicTypes) {
|
if (basicTypes) {
|
||||||
types.forEach((t) => delete basicTypes[t]);
|
types.forEach((t) => delete basicTypes[t]);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Adds or removes the resource IDs from the product visibility whitelist.
|
|
||||||
const setMenuVisibility = (visible) => {
|
|
||||||
const accessibleTypes = visible ? types.filter(hasAccessibleSchema) : [];
|
|
||||||
|
|
||||||
// Always clear first to remove any previously-registered types that are
|
|
||||||
// no longer accessible (e.g. partial permission changes like types=[A,B] where B is dropped).
|
|
||||||
hideTypes();
|
|
||||||
|
|
||||||
if (accessibleTypes.length > 0) {
|
|
||||||
showTypes(accessibleTypes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kickSideNav();
|
kickSideNav();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
const AUTH_ERROR_CODES = [401, 403, 404];
|
|
||||||
|
|
||||||
export function getLoginAwareErrors(err, message = '') {
|
|
||||||
const errors = Array.isArray(err) ? err : (err ? [err] : []);
|
|
||||||
|
|
||||||
if (!errors.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const generic = message;
|
|
||||||
|
|
||||||
if (errors.some((e) => AUTH_ERROR_CODES.includes(e?._status || e?.response?.status))) {
|
|
||||||
return [generic];
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgs = errors
|
|
||||||
.map((e) => (typeof e === 'string' ? e : (e?.message || e?._statusText || '')))
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
return msgs.length ? msgs : [generic];
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,23 @@ OUTPUT_DIR=dist/${DIR}-embedded
|
|||||||
echo "Building..."
|
echo "Building..."
|
||||||
COMMIT=${COMMIT} VERSION=${VERSION} OUTPUT_DIR=$OUTPUT_DIR ROUTER_BASE='/dashboard/' RESOURCE_BASE='/dashboard/' RANCHER_ENV=harvester yarn run build
|
COMMIT=${COMMIT} VERSION=${VERSION} OUTPUT_DIR=$OUTPUT_DIR ROUTER_BASE='/dashboard/' RESOURCE_BASE='/dashboard/' RANCHER_ENV=harvester yarn run build
|
||||||
|
|
||||||
|
if [ -v EMBED_PKG ]; then
|
||||||
|
echo "Build and embed plugin from: $EMBED_PKG"
|
||||||
|
PKG_FILE_NAME=${EMBED_PKG##*/}
|
||||||
|
echo PKG_FILE_NAME: $PKG_FILE_NAME
|
||||||
|
|
||||||
|
PKG_NAME="${PKG_FILE_NAME/.tar.gz/""}"
|
||||||
|
echo "Plugin name: '$PKG_NAME'"
|
||||||
|
|
||||||
|
# Fetch file, unpack and move to dist
|
||||||
|
curl $EMBED_PKG --output $PKG_FILE_NAME
|
||||||
|
OUTPUT_DIR_PKG=$OUTPUT_DIR/$PKG_NAME
|
||||||
|
mkdir -p $OUTPUT_DIR_PKG
|
||||||
|
tar xvfz $PKG_FILE_NAME -C $OUTPUT_DIR/$PKG_NAME
|
||||||
|
echo "Plugin contents that will be served from $PKG_NAME"
|
||||||
|
ls -alR $OUTPUT_DIR/$PKG_NAME
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Destroying..."
|
echo "Destroying..."
|
||||||
find $OUTPUT_DIR -name "index.html" -mindepth 2 -exec rm {} \;
|
find $OUTPUT_DIR -name "index.html" -mindepth 2 -exec rm {} \;
|
||||||
find $OUTPUT_DIR -type d -empty -depth -exec rmdir {} \;
|
find $OUTPUT_DIR -type d -empty -depth -exec rmdir {} \;
|
||||||
|
|||||||
Reference in New Issue
Block a user