mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-03 20:56:06 +08:00
Compare commits
304
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a4f2c96bb | ||
|
|
d7bc112a64 | ||
|
|
08e81b0628 | ||
|
|
4599159bff | ||
|
|
ef40cf5d8d | ||
|
|
d57dbd4c8e | ||
|
|
4c68d057c1 | ||
|
|
b4b2017c61 | ||
|
|
9f0d927c9c | ||
|
|
18a53f5baf | ||
|
|
f872bb5abf | ||
|
|
cd015d57e7 | ||
|
|
635254afdc | ||
|
|
3c464e9d10 | ||
|
|
d412ee2aa6 | ||
|
|
580b2892cb | ||
|
|
965c7d9b72 | ||
|
|
01e3867da1 | ||
|
|
fd52218350 | ||
|
|
1839bf387e | ||
|
|
3e9a0a4377 | ||
|
|
a2898a2554 | ||
|
|
d11a045aed | ||
|
|
ae2a2f6d28 | ||
|
|
cf68c3976a | ||
|
|
33f8300c0e | ||
|
|
b26e34a2bd | ||
|
|
01fd3e5e61 | ||
|
|
d5df7ba602 | ||
|
|
607aa106e1 | ||
|
|
49e2be6ff4 | ||
|
|
9ee81f4054 | ||
|
|
f8fa82f614 | ||
|
|
a6c60de2cb | ||
|
|
843b388ede | ||
|
|
f2e8cce29f | ||
|
|
31682dd165 | ||
|
|
9fac43d5e5 | ||
|
|
cd6ea6e402 | ||
|
|
3bfe57dc54 | ||
|
|
089573f193 | ||
|
|
5d0c4f0c5a | ||
|
|
57bb1e8562 | ||
|
|
30ff129691 | ||
|
|
9175f16bfc | ||
|
|
7c5e0f93d1 | ||
|
|
8a7d3dffbe | ||
|
|
b7810c5970 | ||
|
|
72dedd46d1 | ||
|
|
24f716ab20 | ||
|
|
38ad0fe1a7 | ||
|
|
c84d262099 | ||
|
|
dbb53eae00 | ||
|
|
4afacd754a | ||
|
|
10d159e2c7 | ||
|
|
de755a662a | ||
|
|
f1200f4508 | ||
|
|
0ca2b0591a | ||
|
|
44f003309c | ||
|
|
7c1596c965 | ||
|
|
a5130986a8 | ||
|
|
e4ae7cec5e | ||
|
|
2c7b59daed | ||
|
|
224e34315b | ||
|
|
9f56f9eccc | ||
|
|
dd76956972 | ||
|
|
d92a53d9a7 | ||
|
|
afc3f0d87f | ||
|
|
a607b604f8 | ||
|
|
4d7b0f7350 | ||
|
|
c570b5870b | ||
|
|
af36163b29 | ||
|
|
b3dbc2f0d3 | ||
|
|
fed86d4c24 | ||
|
|
d18ebfb26b | ||
|
|
477206d8d0 | ||
|
|
fd0836d489 | ||
|
|
2e3536abc6 | ||
|
|
45c0dce61b | ||
|
|
6b8b1f68eb | ||
|
|
bb645c00db | ||
|
|
ed8c6e81ca | ||
|
|
ed692b579d | ||
|
|
f02074a25e | ||
|
|
a8f8f0d288 | ||
|
|
9fa9b3837b | ||
|
|
4b3bdf8a59 | ||
|
|
7bdf40d737 | ||
|
|
a1e7345437 | ||
|
|
9322a77b20 | ||
|
|
ed7d7ffd2e | ||
|
|
2ef8e2e0aa | ||
|
|
a0ea6beedb | ||
|
|
85a723e20c | ||
|
|
bbe6b1bdf0 | ||
|
|
81a572e5b5 | ||
|
|
1a727f0001 | ||
|
|
91862244e1 | ||
|
|
c7b0e9ee7a | ||
|
|
d9337ff188 | ||
|
|
c16922c228 | ||
|
|
bbade7e50c | ||
|
|
193bf092d5 | ||
|
|
308b6ddbd7 | ||
|
|
c850b78da2 | ||
|
|
0fe9557eb8 | ||
|
|
b6953d73f7 | ||
|
|
8a89a9c502 | ||
|
|
d5f17bfdf1 | ||
|
|
9d0bb6e1f4 | ||
|
|
2ec37d81d9 | ||
|
|
e50bff8b5e | ||
|
|
e898181834 | ||
|
|
a0f877a23e | ||
|
|
a805490930 | ||
|
|
887a122358 | ||
|
|
1f48e8aea7 | ||
|
|
b2c1a3b068 | ||
|
|
df1566f394 | ||
|
|
0a3b122316 | ||
|
|
d497613cc4 | ||
|
|
91494f5832 | ||
|
|
c61d30a607 | ||
|
|
9428a07850 | ||
|
|
28603d3ab3 | ||
|
|
f863b881a9 | ||
|
|
083ab9f3e9 | ||
|
|
5d52c368a1 | ||
|
|
d72f379ab1 | ||
|
|
2931248fdb | ||
|
|
7166a5b1bb | ||
|
|
00c884cda0 | ||
|
|
1c65b71e35 | ||
|
|
3e4bc31a9d | ||
|
|
bc4bbd9a40 | ||
|
|
a6666ef384 | ||
|
|
92588c9adc | ||
|
|
832df26efc | ||
|
|
f7869cb241 | ||
|
|
27c0124682 | ||
|
|
6c69579c41 | ||
|
|
145b534c5d | ||
|
|
ce017a0e40 | ||
|
|
72a6f77fd2 | ||
|
|
275b15ad62 | ||
|
|
06c9a8b3d9 | ||
|
|
36b1406738 | ||
|
|
06b13a9cc5 | ||
|
|
2a3cd32447 | ||
|
|
40dc31226b | ||
|
|
def9e1a24e | ||
|
|
da25b28f7f | ||
|
|
10abbd0452 | ||
|
|
8a7cc69a69 | ||
|
|
c188f3367d | ||
|
|
26d85a7760 | ||
|
|
6eb845b46b | ||
|
|
8c20ddb440 | ||
|
|
a92f27d80c | ||
|
|
f523a161b6 | ||
|
|
ad2273b5ba | ||
|
|
171939e66b | ||
|
|
18bfbbaac1 | ||
|
|
e79f493cb6 | ||
|
|
e84c0fe886 | ||
|
|
bbfeb55af9 | ||
|
|
e7446e7e23 | ||
|
|
adacaf65fa | ||
|
|
7c761b2829 | ||
|
|
cc8532ff99 | ||
|
|
ede281a42d | ||
|
|
d34279d98f | ||
|
|
efd93c5e52 | ||
|
|
8d338b5f72 | ||
|
|
f59c81da31 | ||
|
|
cf6d44601e | ||
|
|
89b43060df | ||
|
|
7194d52000 | ||
|
|
a8ea0645e4 | ||
|
|
713173b0e4 | ||
|
|
6ed13bcec1 | ||
|
|
02519820d7 | ||
|
|
2604101eb2 | ||
|
|
1cb16f0c74 | ||
|
|
752cf2e93a | ||
|
|
f4ea7b6ea0 | ||
|
|
a6405989e9 | ||
|
|
fb8a81fd0f | ||
|
|
3a5ead3c2c | ||
|
|
def4b64cd3 | ||
|
|
a191b845d8 | ||
|
|
32a57d791a | ||
|
|
2a7e51ef4c | ||
|
|
f726fb99e5 | ||
|
|
9c19ae1f9b | ||
|
|
6ece2d0284 | ||
|
|
aec424b007 | ||
|
|
ff80b91311 | ||
|
|
52252c16f7 | ||
|
|
19cf1e9302 | ||
|
|
ddb8e8106b | ||
|
|
9af5ff7b12 | ||
|
|
fff6153f15 | ||
|
|
1ecac7adf4 | ||
|
|
408818904a | ||
|
|
b96637e6d9 | ||
|
|
8bdaca777f | ||
|
|
d278687e75 | ||
|
|
3ea8edb824 | ||
|
|
e33302050b | ||
|
|
8fefb59802 | ||
|
|
872aec1c10 | ||
|
|
8814bd4cda | ||
|
|
72a17cd9c2 | ||
|
|
8d79b69748 | ||
|
|
47760a8426 | ||
|
|
e6fc641a29 | ||
|
|
90b85d2be7 | ||
|
|
ee9575f710 | ||
|
|
7d97e6821c | ||
|
|
42fc9d8fae | ||
|
|
0f03fc0805 | ||
|
|
6d4a7cd1b1 | ||
|
|
f69d445ef7 | ||
|
|
d358a0a618 | ||
|
|
47841e49b1 | ||
|
|
57c7f0f856 | ||
|
|
161adde1da | ||
|
|
28ece801d5 | ||
|
|
a8cf6f5947 | ||
|
|
28e4991bd0 | ||
|
|
0d94e66b8f | ||
|
|
871ad2e88d | ||
|
|
03d1174d8e | ||
|
|
e236ab2d5f | ||
|
|
0b6569ff64 | ||
|
|
259988f230 | ||
|
|
bbbdd1a881 | ||
|
|
83edd23ada | ||
|
|
a6f2301498 | ||
|
|
0171f70b36 | ||
|
|
578fb72cbb | ||
|
|
45773987f3 | ||
|
|
f2b98b851d | ||
|
|
42d793b50e | ||
|
|
e2474ebf8d | ||
|
|
ad37d502f7 | ||
|
|
666fe7b3b8 | ||
|
|
81f1a56434 | ||
|
|
d6bf6f1b55 | ||
|
|
2774b1c30c | ||
|
|
8b35ea1eaa | ||
|
|
e86df77224 | ||
|
|
6d0ed11057 | ||
|
|
7d17baf774 | ||
|
|
69e257d41c | ||
|
|
e5d4d0556d | ||
|
|
e2773f8c69 | ||
|
|
b7b259c699 | ||
|
|
687943ae92 | ||
|
|
4d48e75447 | ||
|
|
94429d6b64 | ||
|
|
990c06ba98 | ||
|
|
a207ec078b | ||
|
|
8d4524c4c6 | ||
|
|
f04c0c6c9b | ||
|
|
ab77b21aa8 | ||
|
|
c86b4b0466 | ||
|
|
59aec78631 | ||
|
|
e42610ca6d | ||
|
|
e33cc457e9 | ||
|
|
0208a65052 | ||
|
|
b5af90e475 | ||
|
|
a0e81cffff | ||
|
|
d774b323d1 | ||
|
|
fe0dc396dc | ||
|
|
fbd384d923 | ||
|
|
9a89f45a2a | ||
|
|
b8a1403655 | ||
|
|
5b0202a13b | ||
|
|
3d5a8556f2 | ||
|
|
6782edf7b0 | ||
|
|
b8a132c998 | ||
|
|
bc761e0747 | ||
|
|
ce2ead4426 | ||
|
|
4fe5c2e740 | ||
|
|
8a5f425b74 | ||
|
|
a7bb16636f | ||
|
|
63994c1896 | ||
|
|
7745f9805c | ||
|
|
36dd2b4541 | ||
|
|
4a19223afe | ||
|
|
4f44be4826 | ||
|
|
8b226980f3 | ||
|
|
51cf301d18 | ||
|
|
264eadb7dd | ||
|
|
c3cf1eb9a4 | ||
|
|
de10007dcc | ||
|
|
5bb40649e9 | ||
|
|
dd04d405f0 | ||
|
|
eac4fbe020 | ||
|
|
07775130e6 | ||
|
|
39fb49c619 | ||
|
|
d81d347c48 |
@@ -13,3 +13,4 @@ dist-pkg
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
pkg/harvester/index.ts
|
pkg/harvester/index.ts
|
||||||
pkg/harvester/store/*
|
pkg/harvester/store/*
|
||||||
|
scripts/pkgfile.js
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
pull_request_rules:
|
||||||
|
- name: Automatically open backport PR to release-harvester-v1.5
|
||||||
|
conditions:
|
||||||
|
- base=main
|
||||||
|
- label="require backport 1.5"
|
||||||
|
actions:
|
||||||
|
backport:
|
||||||
|
branches:
|
||||||
|
- "release-harvester-v1.5"
|
||||||
|
assignees:
|
||||||
|
- "{{ author }}"
|
||||||
|
labels:
|
||||||
|
- "v1.5 backport PR"
|
||||||
|
|
||||||
|
- name: Ask to resolve conflict
|
||||||
|
conditions:
|
||||||
|
- conflict
|
||||||
|
actions:
|
||||||
|
comment:
|
||||||
|
message: This pull request is now in conflict. Could you fix it @{{author}}? 🙏
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Build Harvester Catalog Image and Publish on Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: ./
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
uses: ./.github/actions/lint
|
||||||
|
|
||||||
|
build-and-push-extension-catalog:
|
||||||
|
needs: lint
|
||||||
|
uses: ./.github/workflows/build-extension-catalog.yml
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
with:
|
||||||
|
registry_target: docker.io
|
||||||
|
registry_user: rancher
|
||||||
|
secrets: inherit
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
name: Build Harvester Dashboard and Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
CI_BRANCH:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
CI_BUILD_TAG:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
env:
|
||||||
|
GOOGLE_AUTH: ''
|
||||||
|
DOCKER_USERNAME: ''
|
||||||
|
DOCKER_PASSWORD: ''
|
||||||
|
CI_BUILD_TAG: ${{inputs.CI_BUILD_TAG}}
|
||||||
|
CI_BRANCH: ${{inputs.CI_BRANCH}}
|
||||||
|
GIT_REPO: ${{github.repository}}
|
||||||
|
GIT_COMMIT: ${{github.sha}}
|
||||||
|
REPO: ${{github.event.repository.name || ''}}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-upload-hosted:
|
||||||
|
name: Build & Upload Hosted
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
# Note - Cannot use the setup action here as it uses a different yarn install arg
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
|
# Build a directory containing the dashboard that can be used with ui-dashboard-index
|
||||||
|
- id: build-hosted
|
||||||
|
name: Build Hosted
|
||||||
|
run: ./scripts/build-hosted
|
||||||
|
|
||||||
|
- id: upload-gate
|
||||||
|
name: Upload Gate (superseded by a newer build?)
|
||||||
|
run: ./scripts/build-upload-gate
|
||||||
|
|
||||||
|
- name: Get gcs auth
|
||||||
|
uses: rancher-eio/read-vault-secrets@main
|
||||||
|
with:
|
||||||
|
secrets: |
|
||||||
|
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
|
||||||
|
|
||||||
|
- name: Apply gcs auth
|
||||||
|
# https://github.com/google-github-actions/auth
|
||||||
|
uses: 'google-github-actions/auth@v2'
|
||||||
|
with:
|
||||||
|
credentials_json: "${{ env.GOOGLE_AUTH }}"
|
||||||
|
|
||||||
|
- name: Upload build
|
||||||
|
uses: 'google-github-actions/upload-cloud-storage@v2'
|
||||||
|
# https://github.com/google-github-actions/upload-cloud-storage
|
||||||
|
with:
|
||||||
|
path: ${{steps.build-hosted.outputs.BUILD_HOSTED_DIR}}
|
||||||
|
destination: releases.rancher.com/harvester-ui/dashboard/${{ steps.build-hosted.outputs.BUILD_HOSTED_LOCATION }}
|
||||||
|
parent: false
|
||||||
|
headers: |-
|
||||||
|
cache-control: no-cache,must-revalidate
|
||||||
|
process_gcloudignore: false
|
||||||
|
|
||||||
|
build-and-upload-embedded:
|
||||||
|
name: Build & Upload Embedded
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
# Note - Cannot use the setup action here as it uses a different yarn install arg
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
|
# Build a tar that will be picked up by rancher builds and embedded into it
|
||||||
|
- id: build-embedded
|
||||||
|
name: Build Embedded
|
||||||
|
run: ./scripts/build-embedded
|
||||||
|
env:
|
||||||
|
DISABLED_EMBED_PKG: https://releases.rancher.com/harvester-ui/plugin/harvester-1.0.3.tar.gz
|
||||||
|
|
||||||
|
- name: Get gcs auth
|
||||||
|
uses: rancher-eio/read-vault-secrets@main
|
||||||
|
with:
|
||||||
|
secrets: |
|
||||||
|
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
|
||||||
|
|
||||||
|
- name: Apply gcs auth
|
||||||
|
# https://github.com/google-github-actions/auth
|
||||||
|
uses: 'google-github-actions/auth@v2'
|
||||||
|
with:
|
||||||
|
credentials_json: "${{ env.GOOGLE_AUTH }}"
|
||||||
|
|
||||||
|
- name: Upload tar
|
||||||
|
uses: 'google-github-actions/upload-cloud-storage@v2'
|
||||||
|
with:
|
||||||
|
path: ${{steps.build-embedded.outputs.BUILD_EMBEDED_TGZ}}
|
||||||
|
destination: releases.rancher.com/harvester-ui/dashboard
|
||||||
|
parent: false
|
||||||
|
headers: |-
|
||||||
|
cache-control: no-cache,must-revalidate
|
||||||
|
process_gcloudignore: false
|
||||||
|
|
||||||
|
build-and-upload-harvester-plugin:
|
||||||
|
name: Build & Upload Plugin
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
# Note - Cannot use the setup action here as it uses a different yarn install arg
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
|
- id: ci-build-pkg
|
||||||
|
name: Build pkg
|
||||||
|
run: ./scripts/ci-build-pkg.sh harvester
|
||||||
|
|
||||||
|
- id: upload-gate
|
||||||
|
name: Upload Gate
|
||||||
|
run: ./scripts/build-upload-gate
|
||||||
|
|
||||||
|
- name: Get gcs auth
|
||||||
|
uses: rancher-eio/read-vault-secrets@main
|
||||||
|
with:
|
||||||
|
secrets: |
|
||||||
|
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
|
||||||
|
|
||||||
|
- name: Apply gcs auth
|
||||||
|
# https://github.com/google-github-actions/auth
|
||||||
|
uses: 'google-github-actions/auth@v2'
|
||||||
|
with:
|
||||||
|
credentials_json: "${{ env.GOOGLE_AUTH }}"
|
||||||
|
|
||||||
|
- name: Upload plugin tar
|
||||||
|
uses: 'google-github-actions/upload-cloud-storage@v2'
|
||||||
|
with:
|
||||||
|
path: dist-pkg/${{steps.ci-build-pkg.outputs.PKG_TARBALL}}
|
||||||
|
destination: releases.rancher.com/harvester-ui/plugin
|
||||||
|
parent: false
|
||||||
|
headers: |-
|
||||||
|
cache-control: no-cache,must-revalidate
|
||||||
|
process_gcloudignore: false
|
||||||
|
|
||||||
|
- name: Upload plugin directory
|
||||||
|
uses: 'google-github-actions/upload-cloud-storage@v2'
|
||||||
|
with:
|
||||||
|
path: dist-pkg/${{steps.ci-build-pkg.outputs.PKG_NAME}}
|
||||||
|
destination: releases.rancher.com/harvester-ui/plugin/${{steps.ci-build-pkg.outputs.PKG_NAME}}
|
||||||
|
parent: false
|
||||||
|
headers: |-
|
||||||
|
cache-control: no-cache,must-revalidate
|
||||||
|
process_gcloudignore: false
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
name: Build and release Extension Catalog Image to registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
registry_target:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
registry_user:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
outputs:
|
||||||
|
build-extension-catalog-job-status:
|
||||||
|
value: ${{ jobs.build-extension-catalog.outputs.build-status }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push-extension-catalog:
|
||||||
|
name: Build container image
|
||||||
|
if: github.ref_type == 'tag' || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
outputs:
|
||||||
|
build-status: ${{ job.status }}
|
||||||
|
steps:
|
||||||
|
- name: Read Secrets
|
||||||
|
uses: rancher-eio/read-vault-secrets@main
|
||||||
|
with:
|
||||||
|
secrets: |
|
||||||
|
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ;
|
||||||
|
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD ;
|
||||||
|
|
||||||
|
- name: Checkout repository (normal flow)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Enable Corepack
|
||||||
|
run: corepack enable
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name 'github-actions[bot]'
|
||||||
|
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ env.DOCKER_USERNAME }}
|
||||||
|
password: ${{ env.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Setup Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
with:
|
||||||
|
version: v3.8.0
|
||||||
|
|
||||||
|
- name: Setup Nodejs with yarn caching
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: yarn
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn
|
||||||
|
|
||||||
|
- name: Build and push UI image
|
||||||
|
run: |
|
||||||
|
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
|
||||||
|
$publish
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
name: Build and Release Extension Charts
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
|
||||||
types: [released]
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: ./
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
uses: ./.github/actions/lint
|
|
||||||
build-extension-charts:
|
|
||||||
needs:
|
|
||||||
- lint
|
|
||||||
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
|
|
||||||
permissions:
|
|
||||||
actions: write
|
|
||||||
contents: write
|
|
||||||
deployments: write
|
|
||||||
pages: write
|
|
||||||
with:
|
|
||||||
target_branch: gh-pages
|
|
||||||
tagged_release: ${{ github.ref_name }}
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
name: Build and Release Extension Charts on PR Merge
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'release-harvester-v*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup-target-branch:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
target_branch: ${{ steps.get-version.outputs.target_branch }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Determine target branch
|
||||||
|
id: get-version
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.ref_name }}" =~ ^release-harvester-v([0-9]+\.[0-9]+)$ ]]; then
|
||||||
|
TARGET_BRANCH="v${BASH_REMATCH[1]}-head"
|
||||||
|
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_ENV
|
||||||
|
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Error: invalid branch format." && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Ensure target branch exists
|
||||||
|
run: |
|
||||||
|
git fetch --all
|
||||||
|
if ! git ls-remote --exit-code --heads origin "${{ env.target_branch }}"; then
|
||||||
|
git checkout gh-pages
|
||||||
|
git checkout -b "${{ env.target_branch }}"
|
||||||
|
git push origin "${{ env.target_branch }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
extract-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.get_version.outputs.version }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Extract version from package.json
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(jq -r '.version' pkg/harvester/package.json)
|
||||||
|
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||||
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
build-extension-charts:
|
||||||
|
needs:
|
||||||
|
- setup-target-branch
|
||||||
|
- extract-version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup environment
|
||||||
|
run: |
|
||||||
|
corepack enable
|
||||||
|
yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Setup Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
with:
|
||||||
|
version: v3.8.0
|
||||||
|
|
||||||
|
- name: Build Helm charts
|
||||||
|
run: |
|
||||||
|
yarn publish-pkgs -s ${{ github.repository }} -b ${{ needs.setup-target-branch.outputs.target_branch }} -t harvester-${{ needs.extract-version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Upload charts artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: charts
|
||||||
|
path: tmp
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs:
|
||||||
|
- setup-target-branch
|
||||||
|
- extract-version
|
||||||
|
- build-extension-charts
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout release branch
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: '${{ github.ref_name }}'
|
||||||
|
|
||||||
|
- name: Get last commit hash
|
||||||
|
id: last_commit
|
||||||
|
run: |
|
||||||
|
LAST_COMMIT=$(git rev-parse HEAD)
|
||||||
|
echo "LAST_COMMIT=${LAST_COMMIT}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Checkout target branch
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: '${{ needs.setup-target-branch.outputs.target_branch }}'
|
||||||
|
|
||||||
|
- name: Remove old artifacts
|
||||||
|
run: |
|
||||||
|
rm -rf extensions/harvester/${{ needs.extract-version.outputs.version }}
|
||||||
|
rm -rf charts/harvester/${{ needs.extract-version.outputs.version }}
|
||||||
|
rm -f assets/harvester/harvester-${{ needs.extract-version.outputs.version }}.tgz
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name 'github-actions[bot]'
|
||||||
|
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
- name: Download build artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: charts
|
||||||
|
|
||||||
|
- name: Commit and push artifacts
|
||||||
|
run: |
|
||||||
|
git add ./{assets,charts,extensions,index.yaml}
|
||||||
|
git commit -m "CI Build Artifacts (commit: ${{ env.LAST_COMMIT }}, version: ${{ needs.extract-version.outputs.version }})"
|
||||||
|
git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
|
||||||
|
|
||||||
|
- name: Run Helm chart releaser
|
||||||
|
uses: helm/chart-releaser-action@v1.4.1
|
||||||
|
with:
|
||||||
|
charts_dir: ./charts
|
||||||
|
env:
|
||||||
|
CR_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
CR_SKIP_EXISTING: true
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: Build and Release Extension Charts on Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: ./
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup-release-tag:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
release_tag: ${{ steps.determine_tag.outputs.release_tag }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Determine release tag
|
||||||
|
id: determine_tag
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event.release.tag_name }}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||||
|
RELEASE_TAG="harvester-${BASH_REMATCH[1]}"
|
||||||
|
echo "${RELEASE_TAG}"
|
||||||
|
echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Error: invalid tag format." && exit 1
|
||||||
|
fi
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
uses: ./.github/actions/lint
|
||||||
|
build-extension-charts:
|
||||||
|
needs:
|
||||||
|
- setup-release-tag
|
||||||
|
- lint
|
||||||
|
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
contents: write
|
||||||
|
deployments: write
|
||||||
|
pages: write
|
||||||
|
with:
|
||||||
|
target_branch: gh-pages
|
||||||
|
tagged_release: '${{ needs.setup-release-tag.outputs.release_tag }}'
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
name: Build Standalone on PR Merge
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'release-harvester-v*'
|
||||||
|
- '*-dev'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'release-harvester-v*'
|
||||||
|
- '*-dev'
|
||||||
|
types:
|
||||||
|
- merged
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-validation:
|
||||||
|
name: Build Test
|
||||||
|
uses: ./.github/workflows/run-lint.yaml
|
||||||
|
build:
|
||||||
|
name: Build and Upload Package
|
||||||
|
uses: ./.github/workflows/build-and-publish-standalone.yaml
|
||||||
|
needs:
|
||||||
|
- build-validation
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
with:
|
||||||
|
CI_BRANCH: ${{github.ref_name}}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
name: Build Standalone on Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[1-9].*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-validation:
|
||||||
|
name: Build Test
|
||||||
|
uses: ./.github/workflows/run-lint.yaml
|
||||||
|
build:
|
||||||
|
name: Build and Upload Package
|
||||||
|
uses: ./.github/workflows/build-and-publish-standalone.yaml
|
||||||
|
needs:
|
||||||
|
- build-validation
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
|
with:
|
||||||
|
CI_BUILD_TAG: ${{github.ref_name}}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
name: Tests
|
name: Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_call: # This tells GH that the workflow is reusable
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -1,2 +1,72 @@
|
|||||||
# harvester
|
# harvester-ui-extension
|
||||||
harvester UI extension
|
|
||||||
|
Rancher Extension used in [rancher/dashboard](https://github.com/rancher/dashboard) for [Harvester](https://harvesterhci.io) Dashboard UI.
|
||||||
|
|
||||||
|
`
|
||||||
|
From Rancher 2.10.0, Harvester integration is provided by this new Harvester UI Extension.
|
||||||
|
`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install the Harvester UI Extension in Rancher 2.10.0 and newer, please refer to [installation](https://docs.harvesterhci.io/v1.5/rancher/harvester-ui-extension#installation-on-rancher-210) on harvester doc.
|
||||||
|
|
||||||
|
## Running for Development
|
||||||
|
|
||||||
|
This is what you probably want to get started.
|
||||||
|
|
||||||
|
### Prerequisite
|
||||||
|
|
||||||
|
Nodejs version >= 20
|
||||||
|
|
||||||
|
|
||||||
|
### Standalone Mode
|
||||||
|
|
||||||
|
For development, serve with hot reload at https://localhost:8005
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# Use RANCHER_ENV to specify the endpoint for your Harvester API
|
||||||
|
RANCHER_ENV=harvester API=https://your-harvester-ip yarn dev
|
||||||
|
|
||||||
|
# Use VUE_APP_SERVER_VERSION to specify the Harvester server version
|
||||||
|
# in case your are running a dev build of Harvester
|
||||||
|
RANCHER_ENV=harvester VUE_APP_SERVER_VERSION=your-harvester-server-version API=https://your-harvester-ip yarn dev
|
||||||
|
|
||||||
|
# or put your env variables into a .env file
|
||||||
|
|
||||||
|
# Example
|
||||||
|
RANCHER_ENV=harvester VUE_APP_SERVER_VERSION=v1.4.0 API=https://192.168.1.123 yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rancher Integration Mode
|
||||||
|
|
||||||
|
In this mode, you need to have Rancher and run Harvester UI Extension as Rancher extension, see more detail in [Rancher UI extension](https://extensions.rancher.io/extensions/next/extensions-getting-started#running-the-app).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
API=https://your-rancher-ip yarn dev
|
||||||
|
|
||||||
|
# Example
|
||||||
|
API=https://192.168.1.123 yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
For developers, after reading through the introduction on this page, head over to our [Getting Started](https://extensions.rancher.io/extensions/next/extensions-getting-started) guide to learn more.
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
Copyright (c) 2014-2025 [SUSE, LLC.](https://www.suse.com/)
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|||||||
+8
-7
@@ -1,25 +1,26 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester-ui-extension",
|
"name": "harvester-ui-extension",
|
||||||
"version": "0.1.0",
|
"version": "1.5.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rancher/shell": "^3.0.0-rc.1",
|
"@rancher/shell": "3.0.2-rc.6",
|
||||||
"cache-loader": "^4.1.0",
|
"cache-loader": "^4.1.0",
|
||||||
"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",
|
||||||
"sortablejs-vue3": "^1.2.11",
|
"vue-draggable-next": "^2.2.1",
|
||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1",
|
||||||
|
"@babel/plugin-transform-class-static-block": "7.26.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "~20.10.0",
|
"@types/node": "~20.10.0",
|
||||||
"cronstrue": "2.50.0",
|
"cronstrue": "2.50.0",
|
||||||
"d3-color": "3.1.0",
|
"d3-color": "3.1.0",
|
||||||
"ejs": "3.1.9",
|
"ejs": "3.1.10",
|
||||||
"follow-redirects": "1.15.2",
|
"follow-redirects": "1.15.6",
|
||||||
"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",
|
||||||
@@ -46,4 +47,4 @@
|
|||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^7.1.0"
|
"eslint-plugin-promise": "^7.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1 +1,9 @@
|
|||||||
module.exports = require('@rancher/shell/pkg/babel.config');
|
const baseConfig = require('@rancher/shell/pkg/babel.config');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...baseConfig,
|
||||||
|
plugins: [
|
||||||
|
...(baseConfig.plugins || []),
|
||||||
|
'@babel/plugin-transform-class-static-block'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|||||||
@@ -81,8 +81,10 @@ export default {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<v-dropdown
|
<v-dropdown
|
||||||
|
popper-class="filter-parent-sriov"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
|
:distance="20"
|
||||||
>
|
>
|
||||||
<slot name="header">
|
<slot name="header">
|
||||||
<button
|
<button
|
||||||
@@ -142,4 +144,11 @@ export default {
|
|||||||
.required {
|
.required {
|
||||||
color: var(--error);
|
color: var(--error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.filter-parent-sriov .v-popper__arrow-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export default {
|
|||||||
|
|
||||||
return Array.from(new Set(options));
|
return Array.from(new Set(options));
|
||||||
},
|
},
|
||||||
|
enableFilterButton() {
|
||||||
|
return this.rows.some((r) => r.sourceSchedule !== undefined);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -63,27 +66,30 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="vm-schedule-filter">
|
<div
|
||||||
<template>
|
class="vm-schedule-filter"
|
||||||
<span
|
>
|
||||||
v-if="selected"
|
<span
|
||||||
class="banner-item bg-warning"
|
v-if="selected"
|
||||||
>
|
class="banner-item bg-warning"
|
||||||
{{ t('harvester.tableHeaders.vmSchedule') }}{{ selected ? ` = ${selected}`: '' }}<i
|
>
|
||||||
class="icon icon-close ml-5"
|
{{ t('harvester.tableHeaders.vmSchedule') }}{{ selected ? ` = ${selected}`: '' }}
|
||||||
@click="remove"
|
<i
|
||||||
/>
|
class="icon icon-close ml-5"
|
||||||
</span>
|
@click="remove"
|
||||||
</template>
|
/>
|
||||||
|
</span>
|
||||||
<v-dropdown
|
<v-dropdown
|
||||||
|
popper-class="vm-schedule-dropdown"
|
||||||
:triggers="scheduleOptions.length ? ['click'] : []"
|
:triggers="scheduleOptions.length ? ['click'] : []"
|
||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
offset="1"
|
offset="1"
|
||||||
|
:distance="20"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
ref="actionDropDown"
|
ref="actionDropDown"
|
||||||
class="btn bg-primary mr-10"
|
class="btn bg-primary mr-10"
|
||||||
|
:disabled="!enableFilterButton"
|
||||||
>
|
>
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
{{ t('harvester.fields.filterSchedule') }}
|
{{ t('harvester.fields.filterSchedule') }}
|
||||||
@@ -98,7 +104,7 @@ export default {
|
|||||||
name="model"
|
name="model"
|
||||||
:options="scheduleOptions"
|
:options="scheduleOptions"
|
||||||
:labels="scheduleOptions"
|
:labels="scheduleOptions"
|
||||||
@input="onSelect"
|
@update:value="onSelect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -106,6 +112,12 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.vm-schedule-dropdown .v-popper__arrow-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.vm-schedule-filter {
|
.vm-schedule-filter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { Checkbox } from '@components/Form/Checkbox';
|
|||||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
import UpgradeInfo from './UpgradeInfo';
|
import UpgradeInfo from './UpgradeInfo';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterUpgrade',
|
name: 'HarvesterUpgrade',
|
||||||
|
|
||||||
@@ -28,14 +30,15 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
upgrade: [],
|
upgrade: [],
|
||||||
upgradeMessage: [],
|
upgradeMessage: [],
|
||||||
errors: '',
|
errors: '',
|
||||||
selectMode: true,
|
selectMode: true,
|
||||||
version: '',
|
version: '',
|
||||||
enableLogging: true,
|
enableLogging: true,
|
||||||
readyReleaseNote: false,
|
skipSingleReplicaDetachedVol: false,
|
||||||
isOpen: false
|
readyReleaseNote: false,
|
||||||
|
isOpen: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -68,6 +71,10 @@ export default {
|
|||||||
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
|
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
skipSingleReplicaDetachedVolFeatureEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('skipSingleReplicaDetachedVol');
|
||||||
|
},
|
||||||
|
|
||||||
releaseLink() {
|
releaseLink() {
|
||||||
return `https://github.com/harvester/harvester/releases/tag/${ this.version }`;
|
return `https://github.com/harvester/harvester/releases/tag/${ this.version }`;
|
||||||
}
|
}
|
||||||
@@ -104,6 +111,11 @@ export default {
|
|||||||
spec: { version: this.version }
|
spec: { version: this.version }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.skipSingleReplicaDetachedVolFeatureEnabled && this.skipSingleReplicaDetachedVol) {
|
||||||
|
upgradeValue.metadata.annotations =
|
||||||
|
{ [HCI_ANNOTATIONS.SKIP_SINGLE_REPLICA_DETACHED_VOL]: JSON.stringify(this.skipSingleReplicaDetachedVol) };
|
||||||
|
}
|
||||||
|
|
||||||
if (this.canEnableLogging) {
|
if (this.canEnableLogging) {
|
||||||
upgradeValue.spec.logEnabled = this.enableLogging;
|
upgradeValue.spec.logEnabled = this.enableLogging;
|
||||||
}
|
}
|
||||||
@@ -190,6 +202,21 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="skipSingleReplicaDetachedVolFeatureEnabled"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
v-model:value="skipSingleReplicaDetachedVol"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.upgradePage.skipSingleReplicaDetachedVol')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr
|
||||||
|
v-if="version"
|
||||||
|
class="divider"
|
||||||
|
/>
|
||||||
<div v-if="version">
|
<div v-if="version">
|
||||||
<p
|
<p
|
||||||
v-clean-html="t('harvester.upgradePage.releaseTip', {url: releaseLink}, true)"
|
v-clean-html="t('harvester.upgradePage.releaseTip', {url: releaseLink}, true)"
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ export default {
|
|||||||
v-clean-tooltip="{
|
v-clean-tooltip="{
|
||||||
placement: 'bottom-left',
|
placement: 'bottom-left',
|
||||||
}"
|
}"
|
||||||
|
popper-class="upgrade-header-dropdown"
|
||||||
class="hand"
|
class="hand"
|
||||||
>
|
>
|
||||||
<slot name="button-content">
|
<slot name="button-content">
|
||||||
@@ -272,7 +273,7 @@ export default {
|
|||||||
|
|
||||||
<p
|
<p
|
||||||
v-if="overallMessage"
|
v-if="overallMessage"
|
||||||
class="text-warning mb-20"
|
class="text-error mb-20"
|
||||||
>
|
>
|
||||||
{{ overallMessage }}
|
{{ overallMessage }}
|
||||||
</p>
|
</p>
|
||||||
@@ -300,14 +301,14 @@ export default {
|
|||||||
|
|
||||||
<ProgressBarList
|
<ProgressBarList
|
||||||
:title="t('harvester.upgradePage.upgradeNode')"
|
:title="t('harvester.upgradePage.upgradeNode')"
|
||||||
:precent="nodesPercent"
|
:percent="nodesPercent"
|
||||||
:list="nodesStatus"
|
:list="nodesStatus"
|
||||||
/>
|
/>
|
||||||
<p class="bordered-section"></p>
|
<p class="bordered-section"></p>
|
||||||
|
|
||||||
<ProgressBarList
|
<ProgressBarList
|
||||||
:title="t('harvester.upgradePage.upgradeSysService')"
|
:title="t('harvester.upgradePage.upgradeSysService')"
|
||||||
:precent="sysServiceTotal"
|
:percent="sysServiceTotal"
|
||||||
:list="sysServiceUpgradeMessage"
|
:list="sysServiceUpgradeMessage"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -338,6 +339,12 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.upgrade-header-dropdown .v-popper__arrow-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
a {
|
a {
|
||||||
float: right;
|
float: right;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default {
|
|||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
precent: {
|
percent: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
@@ -39,16 +39,18 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bar-list">
|
<div class="bar-list">
|
||||||
<h4>{{ title }} <span class="float-r text-info">{{ precent }}%</span></h4>
|
<h4>{{ title }} <span class="float-r text-info">{{ percent }}%</span></h4>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<Collapse v-model:open="open">
|
<Collapse v-model:open="open">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="total-bar">
|
<div class="total-bar">
|
||||||
<span class="bar"><PercentageBar
|
<span class="bar">
|
||||||
:model-value="precent"
|
<PercentageBar
|
||||||
preferred-direction="MORE"
|
:model-value="percent"
|
||||||
/></span>
|
preferred-direction="MORE"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
class="on-off"
|
class="on-off"
|
||||||
@click="handleSwitch"
|
@click="handleSwitch"
|
||||||
@@ -56,28 +58,26 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template>
|
<div class="custom-content">
|
||||||
<div class="custom-content">
|
<div
|
||||||
<div
|
v-for="(item, i) in list"
|
||||||
v-for="(item, i) in list"
|
:key="i"
|
||||||
:key="i"
|
>
|
||||||
>
|
<p>
|
||||||
<p>
|
{{ item.name }} <span
|
||||||
{{ item.name }} <span
|
class="status"
|
||||||
class="status"
|
:class="{ [item.state]: true }"
|
||||||
:class="{ [item.state]: true }"
|
>{{ item.state }}</span>
|
||||||
>{{ item.state }}</span>
|
</p>
|
||||||
</p>
|
<PercentageBar
|
||||||
<PercentageBar
|
:model-value="item.percent"
|
||||||
:model-value="item.percent"
|
preferred-direction="MORE"
|
||||||
preferred-direction="MORE"
|
/>
|
||||||
/>
|
<p class="warning">
|
||||||
<p class="warning">
|
{{ item.message }}
|
||||||
{{ item.message }}
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +93,7 @@ export default {
|
|||||||
.total-bar {
|
.total-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
.bar {
|
> .bar {
|
||||||
width: 85%;
|
width: 85%;
|
||||||
}
|
}
|
||||||
.on-off {
|
.on-off {
|
||||||
@@ -102,25 +102,22 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.custom-content {
|
.custom-content {
|
||||||
.item {
|
margin-bottom: 14px;
|
||||||
margin-bottom: 14px;
|
p {
|
||||||
p {
|
margin-bottom: 4px;
|
||||||
margin-bottom: 4px;
|
}
|
||||||
}
|
.status {
|
||||||
.status {
|
float: right;
|
||||||
float: right;
|
}
|
||||||
}
|
.Succeeded, .Upgrading, .Pending {
|
||||||
.Succeeded, .Upgrading, .Pending {
|
color: var(--success);
|
||||||
color: var(--success);
|
}
|
||||||
}
|
.failed {
|
||||||
.failed {
|
color: var(--error)
|
||||||
color: var(--error)
|
}
|
||||||
}
|
.warning {
|
||||||
.warning {
|
color: var(--error);
|
||||||
color: var(--error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue = [];
|
this.queue = [];
|
||||||
}, 5)
|
}, 10),
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default {
|
|||||||
{{ t('harvester.upgradePage.upgradeInfo.tip') }}
|
{{ t('harvester.upgradePage.upgradeInfo.tip') }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="mb-5">
|
<p>
|
||||||
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a
|
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a
|
||||||
:href="releaseVersion"
|
:href="releaseVersion"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import CreateEditView from '@shell/mixins/create-edit-view';
|
|||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { HCI_SETTING } from '../../config/settings';
|
import { HCI_SETTING } from '../../config/settings';
|
||||||
|
import UnitInput from '@shell/components/form/UnitInput';
|
||||||
|
|
||||||
const DEFAULT_TYPE = 's3';
|
const DEFAULT_TYPE = 's3';
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export default {
|
|||||||
name: 'HarvesterEditBackupTarget',
|
name: 'HarvesterEditBackupTarget',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
LabeledInput, LabeledSelect, Tip, Password, MessageLink
|
LabeledInput, LabeledSelect, Tip, Password, MessageLink, UnitInput
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView],
|
||||||
@@ -24,7 +25,9 @@ export default {
|
|||||||
try {
|
try {
|
||||||
parseDefaultValue = JSON.parse(this.value.value);
|
parseDefaultValue = JSON.parse(this.value.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
parseDefaultValue = { type: '', endpoint: '' };
|
parseDefaultValue = {
|
||||||
|
type: '', endpoint: '', refreshIntervalInSeconds: 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// set default type to s3
|
// set default type to s3
|
||||||
@@ -62,6 +65,10 @@ export default {
|
|||||||
}];
|
}];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refreshIntervalInSecondEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('refreshIntervalInSecond');
|
||||||
|
},
|
||||||
|
|
||||||
isS3() {
|
isS3() {
|
||||||
return this.parseDefaultValue.type === DEFAULT_TYPE;
|
return this.parseDefaultValue.type === DEFAULT_TYPE;
|
||||||
},
|
},
|
||||||
@@ -83,7 +90,9 @@ export default {
|
|||||||
try {
|
try {
|
||||||
parseDefaultValue = JSON.parse(neu.value);
|
parseDefaultValue = JSON.parse(neu.value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
parseDefaultValue = { type: '', endpoint: '' };
|
parseDefaultValue = {
|
||||||
|
type: '', endpoint: '', refreshIntervalInSeconds: 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this['parseDefaultValue'] = parseDefaultValue;
|
this['parseDefaultValue'] = parseDefaultValue;
|
||||||
@@ -111,7 +120,6 @@ export default {
|
|||||||
if (this.isS3 && !this.parseDefaultValue.virtualHostedStyle) {
|
if (this.isS3 && !this.parseDefaultValue.virtualHostedStyle) {
|
||||||
this.parseDefaultValue.virtualHostedStyle = false;
|
this.parseDefaultValue.virtualHostedStyle = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.parseDefaultValue.type) {
|
if (!this.parseDefaultValue.type) {
|
||||||
delete this.value['value'];
|
delete this.value['value'];
|
||||||
} else {
|
} else {
|
||||||
@@ -120,7 +128,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
useDefault() {
|
useDefault() {
|
||||||
this['parseDefaultValue'] = { type: '', endpoint: '' };
|
this['parseDefaultValue'] = {
|
||||||
|
type: '', endpoint: '', refreshIntervalInSeconds: 0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -139,6 +149,23 @@ export default {
|
|||||||
:options="typeOption"
|
:options="typeOption"
|
||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
|
<UnitInput
|
||||||
|
v-if="refreshIntervalInSecondEnabled"
|
||||||
|
v-model:value="parseDefaultValue.refreshIntervalInSeconds"
|
||||||
|
:suffix="parseDefaultValue.refreshIntervalInSeconds <= 1 ? 'Second' : 'Seconds'"
|
||||||
|
:label="t('harvester.backup.refreshInterval.label')"
|
||||||
|
:mode="mode"
|
||||||
|
:positive="true"
|
||||||
|
class="mb-5"
|
||||||
|
required
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
<Tip
|
||||||
|
v-if="refreshIntervalInSecondEnabled"
|
||||||
|
class="mb-20"
|
||||||
|
icon="icon icon-info"
|
||||||
|
:text="t('harvester.backup.refreshInterval.tip')"
|
||||||
|
/>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model:value="parseDefaultValue.endpoint"
|
v-model:value="parseDefaultValue.endpoint"
|
||||||
class="mb-5"
|
class="mb-5"
|
||||||
|
|||||||
@@ -75,7 +75,11 @@ export default {
|
|||||||
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
|
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
|
||||||
|
|
||||||
return this.configArr.length >= csiDrivers.length;
|
return this.configArr.length >= csiDrivers.length;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
allowEmptySnapshotClassNameFeatureEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('allowEmptySnapshotClassName');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -139,7 +143,7 @@ export default {
|
|||||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.volumeSnapshotClassName') }, true));
|
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.volumeSnapshotClassName') }, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.value.backupVolumeSnapshotClassName) {
|
if (!this.allowEmptySnapshotClassNameFeatureEnabled && !config.value.backupVolumeSnapshotClassName) {
|
||||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName') }, true));
|
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName') }, true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -158,10 +162,18 @@ export default {
|
|||||||
this.configArr.splice(idx, 1);
|
this.configArr.splice(idx, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isBackupVolumeSnapshotRequired(driver) {
|
||||||
|
return driver === LONGHORN_DRIVER;
|
||||||
|
},
|
||||||
|
|
||||||
disableEdit(driver) {
|
disableEdit(driver) {
|
||||||
return driver === LONGHORN_DRIVER;
|
return driver === LONGHORN_DRIVER;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isBackupVolumeSnapshotClassNameDisabled(driver) {
|
||||||
|
return driver === LONGHORN_DRIVER || this.allowEmptySnapshotClassNameFeatureEnabled;
|
||||||
|
},
|
||||||
|
|
||||||
add() {
|
add() {
|
||||||
this.configArr.push({
|
this.configArr.push({
|
||||||
key: '',
|
key: '',
|
||||||
@@ -184,6 +196,7 @@ export default {
|
|||||||
<InfoBox
|
<InfoBox
|
||||||
v-for="(driver, idx) in configArr"
|
v-for="(driver, idx) in configArr"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
|
class="box"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
:disabled="disableEdit(driver.key)"
|
:disabled="disableEdit(driver.key)"
|
||||||
@@ -226,9 +239,9 @@ export default {
|
|||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model:value="driver.value.backupVolumeSnapshotClassName"
|
v-model:value="driver.value.backupVolumeSnapshotClassName"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
required
|
:disabled="isBackupVolumeSnapshotClassNameDisabled(driver.key)"
|
||||||
:disabled="disableEdit(driver.key)"
|
|
||||||
:options="getVolumeSnapshotOptions(driver.key)"
|
:options="getVolumeSnapshotOptions(driver.key)"
|
||||||
|
:required="isBackupVolumeSnapshotRequired(driver.key)"
|
||||||
:label="t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName')"
|
:label="t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName')"
|
||||||
@keydown.native.enter.prevent="()=>{}"
|
@keydown.native.enter.prevent="()=>{}"
|
||||||
@update:value="update"
|
@update:value="update"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
|
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
|
||||||
@@ -62,7 +61,7 @@ export default {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="row"
|
class="row"
|
||||||
@update:value="update"
|
@input="update"
|
||||||
>
|
>
|
||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import { NODE } from '@shell/config/types';
|
|||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
import { DOC } from '../../config/doc-links';
|
import { DOC } from '../../config/doc-links';
|
||||||
import { docLink } from '../../utils/feature-flags';
|
import { docLink } from '../../utils/feature-flags';
|
||||||
|
import { NETWORK_TYPE } from '../../config/types';
|
||||||
|
|
||||||
|
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditStorageNetwork',
|
name: 'HarvesterEditStorageNetwork',
|
||||||
@@ -57,11 +60,14 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
let parsedDefaultValue = {};
|
let parsedDefaultValue = {};
|
||||||
let openVlan = false;
|
let openVlan = false;
|
||||||
|
let networkType = L2VLAN;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parsedDefaultValue = JSON.parse(this.value.value);
|
parsedDefaultValue = JSON.parse(this.value.value);
|
||||||
|
networkType = 'vlan' in parsedDefaultValue ? L2VLAN : UNTAGGED; // backend doesn't provide networkType, so we check if vlan is provided instead
|
||||||
openVlan = true;
|
openVlan = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
networkType = L2VLAN;
|
||||||
parsedDefaultValue = {
|
parsedDefaultValue = {
|
||||||
vlan: '',
|
vlan: '',
|
||||||
clusterNetwork: '',
|
clusterNetwork: '',
|
||||||
@@ -73,6 +79,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
openVlan,
|
openVlan,
|
||||||
|
networkType,
|
||||||
errors: [],
|
errors: [],
|
||||||
exclude,
|
exclude,
|
||||||
parsedDefaultValue,
|
parsedDefaultValue,
|
||||||
@@ -87,16 +94,35 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
showVlan() {
|
||||||
|
return this.networkType === L2VLAN;
|
||||||
|
},
|
||||||
|
networkTypes() {
|
||||||
|
const types = [L2VLAN];
|
||||||
|
|
||||||
|
if (this.untaggedNetworkSettingEnabled) {
|
||||||
|
types.push(UNTAGGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
},
|
||||||
storageNetworkExampleLink() {
|
storageNetworkExampleLink() {
|
||||||
const version = this.$store.getters['harvester-common/getServerVersion']();
|
const version = this.$store.getters['harvester-common/getServerVersion']();
|
||||||
|
|
||||||
return docLink(DOC.STORAGE_NETWORK_EXAMPLE, version);
|
return docLink(DOC.STORAGE_NETWORK_EXAMPLE, version);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
untaggedNetworkSettingEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('untaggedNetworkSetting');
|
||||||
|
},
|
||||||
|
|
||||||
clusterNetworkOptions() {
|
clusterNetworkOptions() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
|
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
|
||||||
|
// untaggedNetwork filter out mgmt cluster network
|
||||||
|
const clusterNetworksOptions = this.networkType === UNTAGGED ? clusterNetworks.filter((net) => net.id !== 'mgmt') : clusterNetworks;
|
||||||
|
|
||||||
return clusterNetworks.map((n) => {
|
return clusterNetworksOptions.map((n) => {
|
||||||
const disabled = !n.isReadyForStorageNetwork;
|
const disabled = !n.isReadyForStorageNetwork;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -108,7 +134,50 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
networkType: {
|
||||||
|
handler(neu) {
|
||||||
|
this.parsedDefaultValue.clusterNetwork = '';
|
||||||
|
|
||||||
|
if (neu === L2VLAN) {
|
||||||
|
this.parsedDefaultValue.vlan = '';
|
||||||
|
} else {
|
||||||
|
delete this.parsedDefaultValue.vlan;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
inputVlan(neu) {
|
||||||
|
if (neu === '') {
|
||||||
|
this.parsedDefaultValue.vlan = '';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newValue = Number(neu);
|
||||||
|
|
||||||
|
if (newValue > 4094) {
|
||||||
|
this.parsedDefaultValue.vlan = 4094;
|
||||||
|
} else if (newValue < 1) {
|
||||||
|
this.parsedDefaultValue.vlan = 1;
|
||||||
|
} else {
|
||||||
|
this.parsedDefaultValue.vlan = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
useDefault() {
|
||||||
|
this.openVlan = false;
|
||||||
|
this.networkType = L2VLAN;
|
||||||
|
this.parsedDefaultValue = {
|
||||||
|
vlan: '',
|
||||||
|
clusterNetwork: '',
|
||||||
|
range: '',
|
||||||
|
exclude: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
const exclude = this.exclude.filter((ip) => ip);
|
const exclude = this.exclude.filter((ip) => ip);
|
||||||
|
|
||||||
@@ -138,7 +207,7 @@ export default {
|
|||||||
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
|
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.parsedDefaultValue.vlan) {
|
if (this.networkType === L2VLAN && !this.parsedDefaultValue.vlan) {
|
||||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
|
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,12 +260,25 @@ export default {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="openVlan">
|
<div v-if="openVlan">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="networkType"
|
||||||
|
class="mb-20"
|
||||||
|
:options="networkTypes"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('harvester.fields.type')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model.number="parsedDefaultValue.vlan"
|
v-if="showVlan"
|
||||||
|
v-model:value.number="parsedDefaultValue.vlan"
|
||||||
|
type="number"
|
||||||
class="mb-20"
|
class="mb-20"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
required
|
required
|
||||||
|
placeholder="e.g. 1 - 4094"
|
||||||
label-key="harvester.setting.storageNetwork.vlan"
|
label-key="harvester.setting.storageNetwork.vlan"
|
||||||
|
@update:value="inputVlan"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default {
|
|||||||
name="model"
|
name="model"
|
||||||
:options="[true,false]"
|
:options="[true,false]"
|
||||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
||||||
@input="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
|
|||||||
@@ -1,42 +1,55 @@
|
|||||||
// https://github.com/harvester/dashboard/releases/tag/v1.3.0
|
import semver from 'semver';
|
||||||
const featuresV130 = [
|
|
||||||
'supportHarvesterClusterVersion'
|
|
||||||
];
|
|
||||||
|
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.3.1
|
const FEATURE_FLAGS = {
|
||||||
const featuresV131 = [
|
'v1.3.0': [
|
||||||
...featuresV130,
|
'supportHarvesterClusterVersion'
|
||||||
'autoRotateRke2CertsSetting',
|
],
|
||||||
'supportBundleNodeCollectionTimeoutSetting'
|
'v1.3.1': [
|
||||||
];
|
'autoRotateRke2CertsSetting',
|
||||||
|
'supportBundleNodeCollectionTimeoutSetting'
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.3.2
|
],
|
||||||
const featuresV132 = [
|
'v1.3.2': [
|
||||||
...featuresV131,
|
'kubeconfigDefaultTokenTTLMinutesSetting',
|
||||||
'kubeconfigDefaultTokenTTLMinutesSetting',
|
'improveMaintenanceMode',
|
||||||
'improveMaintenanceMode',
|
],
|
||||||
];
|
'v1.3.3': [],
|
||||||
|
'v1.4.0': [
|
||||||
// TODO: change to https://github.com/harvester/dashboard/releases/tag/v1.4.0 after v1.4.0 release
|
'cpuPinning',
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc5
|
'usbPassthrough',
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc4
|
'volumeEncryption',
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc3
|
'schedulingVMBackup',
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc2
|
'vmSnapshotQuota',
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc1
|
'longhornV2LVMSupport',
|
||||||
const featuresV140 = [
|
'improveMaintenanceMode',
|
||||||
...featuresV132,
|
],
|
||||||
'cpuPinning',
|
'v1.4.1': [],
|
||||||
'usbPassthrough',
|
'v1.4.2': [
|
||||||
'volumeEncryption',
|
'refreshIntervalInSecond',
|
||||||
'schedulingVMBackup',
|
'allowEmptySnapshotClassName'
|
||||||
'vmSnapshotQuota',
|
],
|
||||||
'longhornV2LVMSupport',
|
'v1.4.3': [],
|
||||||
'improveMaintenanceMode',
|
'v1.5.0': [
|
||||||
];
|
'tpmPersistentState',
|
||||||
|
'efiPersistentState',
|
||||||
export const RELEASE_FEATURES = {
|
'untaggedNetworkSetting',
|
||||||
'v1.3.0': featuresV130,
|
'skipSingleReplicaDetachedVol',
|
||||||
'v1.3.1': featuresV131,
|
'thirdPartyStorage'
|
||||||
'v1.3.2': featuresV132,
|
],
|
||||||
'v1.4.0': featuresV140,
|
'v1.5.1': [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateFeatureFlags = () => {
|
||||||
|
const versions = [...Object.keys(FEATURE_FLAGS)].filter((version) => semver.valid(version)).sort(semver.compare);
|
||||||
|
|
||||||
|
const generatedFlags = {};
|
||||||
|
|
||||||
|
versions.forEach((version, index) => {
|
||||||
|
const previousVersion = versions[index - 1];
|
||||||
|
|
||||||
|
generatedFlags[version] = previousVersion ? [...generatedFlags[previousVersion], ...FEATURE_FLAGS[version]] : [...FEATURE_FLAGS[version]];
|
||||||
|
});
|
||||||
|
|
||||||
|
return generatedFlags;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RELEASE_FEATURES = generateFeatureFlags();
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import {
|
|||||||
FINGERPRINT,
|
FINGERPRINT,
|
||||||
IMAGE_PROGRESS,
|
IMAGE_PROGRESS,
|
||||||
SNAPSHOT_TARGET_VOLUME,
|
SNAPSHOT_TARGET_VOLUME,
|
||||||
IMAGE_VIRTUAL_SIZE
|
IMAGE_VIRTUAL_SIZE,
|
||||||
|
IMAGE_STORAGE_CLASS
|
||||||
} from './table-headers';
|
} from './table-headers';
|
||||||
|
|
||||||
const TEMPLATE = HCI.VM_VERSION;
|
const TEMPLATE = HCI.VM_VERSION;
|
||||||
@@ -221,6 +222,7 @@ export function init($plugin, store) {
|
|||||||
STATE,
|
STATE,
|
||||||
NAME_COL,
|
NAME_COL,
|
||||||
NAMESPACE_COL,
|
NAMESPACE_COL,
|
||||||
|
IMAGE_STORAGE_CLASS,
|
||||||
IMAGE_PROGRESS,
|
IMAGE_PROGRESS,
|
||||||
IMAGE_DOWNLOAD_SIZE,
|
IMAGE_DOWNLOAD_SIZE,
|
||||||
IMAGE_VIRTUAL_SIZE,
|
IMAGE_VIRTUAL_SIZE,
|
||||||
@@ -724,7 +726,7 @@ export function init($plugin, store) {
|
|||||||
configureType(HCI.PCI_DEVICE, {
|
configureType(HCI.PCI_DEVICE, {
|
||||||
isCreatable: false,
|
isCreatable: false,
|
||||||
hiddenNamespaceGroupButton: true,
|
hiddenNamespaceGroupButton: true,
|
||||||
canYaml: false,
|
canYaml: true,
|
||||||
listGroups: [
|
listGroups: [
|
||||||
{
|
{
|
||||||
icon: 'icon-list-grouped',
|
icon: 'icon-list-grouped',
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
import { HCI, MANAGEMENT, CAPI } from '@shell/config/types';
|
|
||||||
import { HARVESTER, MULTI_CLUSTER } from '@shell/store/features';
|
|
||||||
import { allHash } from '@shell/utils/promise';
|
|
||||||
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
||||||
|
|
||||||
export const PRODUCT_NAME = 'harvester-manager';
|
|
||||||
|
|
||||||
export const NAME = 'harvesterManager';
|
|
||||||
|
|
||||||
const harvesterClustersLocation = {
|
|
||||||
name: 'c-cluster-product-resource',
|
|
||||||
params: {
|
|
||||||
cluster: BLANK_CLUSTER,
|
|
||||||
product: NAME,
|
|
||||||
resource: HCI.CLUSTER
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function init($plugin, store) {
|
|
||||||
const {
|
|
||||||
product,
|
|
||||||
basicType,
|
|
||||||
spoofedType,
|
|
||||||
configureType
|
|
||||||
} = $plugin.DSL(store, NAME);
|
|
||||||
|
|
||||||
product({
|
|
||||||
ifHaveType: CAPI.RANCHER_CLUSTER,
|
|
||||||
ifFeature: [MULTI_CLUSTER, HARVESTER],
|
|
||||||
inStore: 'management',
|
|
||||||
icon: 'harvester',
|
|
||||||
removable: false,
|
|
||||||
showClusterSwitcher: false,
|
|
||||||
weight: 100,
|
|
||||||
to: harvesterClustersLocation,
|
|
||||||
category: 'hci',
|
|
||||||
});
|
|
||||||
|
|
||||||
configureType(HCI.CLUSTER, { showListMasthead: false });
|
|
||||||
|
|
||||||
basicType([HCI.CLUSTER]);
|
|
||||||
spoofedType({
|
|
||||||
labelKey: 'harvesterManager.cluster.label',
|
|
||||||
name: HCI.CLUSTER,
|
|
||||||
type: HCI.CLUSTER,
|
|
||||||
namespaced: false,
|
|
||||||
weight: -1,
|
|
||||||
route: {
|
|
||||||
name: 'c-cluster-product-resource',
|
|
||||||
params: {
|
|
||||||
product: NAME,
|
|
||||||
resource: HCI.CLUSTER,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
exact: false,
|
|
||||||
schemas: [
|
|
||||||
{
|
|
||||||
id: HCI.CLUSTER,
|
|
||||||
type: 'schema',
|
|
||||||
collectionMethods: [],
|
|
||||||
resourceFields: {},
|
|
||||||
attributes: { namespaced: true },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
group: 'Root',
|
|
||||||
getInstances: async() => {
|
|
||||||
const hash = {
|
|
||||||
rancherClusters: store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER }),
|
|
||||||
clusters: store.dispatch('management/findAll', { type: MANAGEMENT.CLUSTER }),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (store.getters['management/schemaFor'](MANAGEMENT.NODE)) {
|
|
||||||
hash.nodes = store.dispatch('management/findAll', { type: MANAGEMENT.NODE });
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await allHash(hash);
|
|
||||||
|
|
||||||
return res.rancherClusters.map((c) => {
|
|
||||||
return {
|
|
||||||
...c,
|
|
||||||
type: HCI.CLUSTER,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// TODO: delete this not used variable
|
||||||
export const MemoryUnit = [{
|
export const MemoryUnit = [{
|
||||||
label: 'Mi',
|
label: 'Mi',
|
||||||
value: 'Mi'
|
value: 'Mi'
|
||||||
|
|||||||
@@ -7,60 +7,64 @@ export const LABELS_TO_IGNORE_REGEX = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const HCI = {
|
export const HCI = {
|
||||||
CLOUD_INIT: 'harvesterhci.io/cloud-init-template',
|
CLOUD_INIT: 'harvesterhci.io/cloud-init-template',
|
||||||
CURRENT_IP: 'rke2.io/internal-ip',
|
CURRENT_IP: 'rke2.io/internal-ip',
|
||||||
IMAGE_ID: 'harvesterhci.io/imageId',
|
IMAGE_ID: 'harvesterhci.io/imageId',
|
||||||
SSH_NAMES: 'harvesterhci.io/sshNames',
|
SSH_NAMES: 'harvesterhci.io/sshNames',
|
||||||
NETWORK_IPS: 'network.harvesterhci.io/ips',
|
NETWORK_IPS: 'network.harvesterhci.io/ips',
|
||||||
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
|
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
|
||||||
CREATOR: 'harvesterhci.io/creator',
|
CREATOR: 'harvesterhci.io/creator',
|
||||||
OS: 'harvesterhci.io/os',
|
OS: 'harvesterhci.io/os',
|
||||||
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
GOLDEN_IMAGE: 'harvesterhci.io/goldenImage',
|
||||||
VM_NAME: 'harvesterhci.io/vmName',
|
NETWORK_TYPE: 'network.harvesterhci.io/type',
|
||||||
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
VM_NAME: 'harvesterhci.io/vmName',
|
||||||
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
|
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
|
||||||
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
|
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
|
||||||
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
|
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
|
||||||
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
|
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
|
||||||
RESTORE_NAME: 'restore.harvesterhci.io/name',
|
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
|
||||||
NODE_ROLE_MASTER: 'node-role.kubernetes.io/master',
|
RESTORE_NAME: 'restore.harvesterhci.io/name',
|
||||||
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
|
NODE_ROLE_MASTER: 'node-role.kubernetes.io/master',
|
||||||
NODE_ROLE_ETCD: 'node-role.harvesterhci.io/witness',
|
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
|
||||||
PROMOTE_STATUS: 'harvesterhci.io/promote-status',
|
NODE_ROLE_ETCD: 'node-role.harvesterhci.io/witness',
|
||||||
MIGRATION_STATE: 'harvesterhci.io/migrationState',
|
PROMOTE_STATUS: 'harvesterhci.io/promote-status',
|
||||||
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
|
MIGRATION_STATE: 'harvesterhci.io/migrationState',
|
||||||
IMAGE_NAME: 'harvesterhci.io/image-name',
|
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
|
||||||
INIT_IP: 'etcd.rke2.cattle.io/node-address',
|
IMAGE_NAME: 'harvesterhci.io/image-name',
|
||||||
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
|
INIT_IP: 'etcd.rke2.cattle.io/node-address',
|
||||||
NETWORK_ROUTE: 'network.harvesterhci.io/route',
|
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
|
||||||
MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
|
NETWORK_ROUTE: 'network.harvesterhci.io/route',
|
||||||
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
|
MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
|
||||||
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
|
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
|
||||||
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
|
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
|
||||||
REAY_MESSAGE: 'harvesterhci.io/read-message',
|
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
|
||||||
DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names',
|
REAY_MESSAGE: 'harvesterhci.io/read-message',
|
||||||
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
|
DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names',
|
||||||
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
|
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
|
||||||
OS_TYPE: 'harvesterhci.io/os-type',
|
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
|
||||||
STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
|
OS_TYPE: 'harvesterhci.io/os-type',
|
||||||
HOST_REQUEST: 'management.cattle.io/pod-requests',
|
STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
|
||||||
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
|
SKIP_SINGLE_REPLICA_DETACHED_VOL: 'harvesterhci.io/skipSingleReplicaDetachedVol',
|
||||||
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
|
HOST_REQUEST: 'management.cattle.io/pod-requests',
|
||||||
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
|
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
|
||||||
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
|
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
|
||||||
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
|
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
|
||||||
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
|
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
|
||||||
NODE_MANUFACTURER: 'manufacturer',
|
VOLUME_FOR_VM: 'harvesterhci.io/volumeForVirtualMachine',
|
||||||
NODE_MODEL: 'model',
|
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
|
||||||
NODE_SERIAL_NUMBER: 'serialNumber',
|
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
|
||||||
VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota',
|
NODE_MANUFACTURER: 'manufacturer',
|
||||||
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service',
|
NODE_MODEL: 'model',
|
||||||
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device',
|
NODE_SERIAL_NUMBER: 'serialNumber',
|
||||||
PARENT_SRIOV_GPU: 'harvesterhci.io/parentSRIOVGPUDevice',
|
VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota',
|
||||||
VM_MAINTENANCE_MODE_STRATEGY: 'harvesterhci.io/maintain-mode-strategy',
|
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service',
|
||||||
NODE_CPU_MANAGER_UPDATE_STATUS: 'harvesterhci.io/cpu-manager-update-status',
|
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device',
|
||||||
CPU_MANAGER: 'cpumanager',
|
PARENT_SRIOV_GPU: 'harvesterhci.io/parentSRIOVGPUDevice',
|
||||||
VM_DEVICE_ALLOCATION_DETAILS: 'harvesterhci.io/deviceAllocationDetails',
|
VM_MAINTENANCE_MODE_STRATEGY: 'harvesterhci.io/maintain-mode-strategy',
|
||||||
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
|
NODE_CPU_MANAGER_UPDATE_STATUS: 'harvesterhci.io/cpu-manager-update-status',
|
||||||
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine',
|
CPU_MANAGER: 'cpumanager',
|
||||||
|
VM_DEVICE_ALLOCATION_DETAILS: 'harvesterhci.io/deviceAllocationDetails',
|
||||||
|
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
|
||||||
|
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine',
|
||||||
|
K8S_ARCH: 'kubernetes.io/arch'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ export const HCI_ALLOWED_SETTINGS = {
|
|||||||
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
|
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
|
||||||
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: { featureFlag: 'supportBundleNodeCollectionTimeoutSetting' },
|
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: { featureFlag: 'supportBundleNodeCollectionTimeoutSetting' },
|
||||||
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
|
||||||
[HCI_SETTING.STORAGE_NETWORK]: { kind: 'custom', from: 'import' },
|
[HCI_SETTING.STORAGE_NETWORK]: {
|
||||||
|
kind: 'custom', from: 'import', canReset: true
|
||||||
|
},
|
||||||
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
|
||||||
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
|
||||||
[HCI_SETTING.SSL_PARAMETERS]: {
|
[HCI_SETTING.SSL_PARAMETERS]: {
|
||||||
|
|||||||
@@ -88,3 +88,13 @@ export const MACHINE_POOLS = {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100,
|
width: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The STORAGE_CLASS column in VM image list page
|
||||||
|
export const IMAGE_STORAGE_CLASS = {
|
||||||
|
name: 'imageStorageClass',
|
||||||
|
labelKey: 'harvester.tableHeaders.storageClass',
|
||||||
|
sort: 'imageStorageClass',
|
||||||
|
value: 'imageStorageClass',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,3 +2,13 @@ export const BACKUP_TYPE = {
|
|||||||
BACKUP: 'backup',
|
BACKUP: 'backup',
|
||||||
SNAPSHOT: 'snapshot'
|
SNAPSHOT: 'snapshot'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NETWORK_TYPE = {
|
||||||
|
L2VLAN: 'L2VlanNetwork',
|
||||||
|
UNTAGGED: 'UntaggedNetwork'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VOLUME_MODE = {
|
||||||
|
BLOCK: 'Block',
|
||||||
|
FILE_SYSTEM: 'Filesystem'
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
|||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
import { LONGHORN, METRIC } from '@shell/config/types';
|
import { LONGHORN, METRIC } from '@shell/config/types';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
|
import { UNIT_SUFFIX } from '../../utils/unit';
|
||||||
import HarvesterCPUUsed from '../../formatters/HarvesterCPUUsed';
|
import HarvesterCPUUsed from '../../formatters/HarvesterCPUUsed';
|
||||||
import HarvesterMemoryUsed from '../../formatters/HarvesterMemoryUsed';
|
import HarvesterMemoryUsed from '../../formatters/HarvesterMemoryUsed';
|
||||||
import HarvesterStorageUsed from '../../formatters/HarvesterStorageUsed';
|
import HarvesterStorageUsed from '../../formatters/HarvesterStorageUsed';
|
||||||
@@ -121,7 +122,7 @@ export default {
|
|||||||
memoryUnits() {
|
memoryUnits() {
|
||||||
const exponent = exponentNeeded(this.memoryTotal, 1024);
|
const exponent = exponentNeeded(this.memoryTotal, 1024);
|
||||||
|
|
||||||
return `${ UNITS[exponent] }iB`;
|
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
|
||||||
},
|
},
|
||||||
|
|
||||||
nodeType() {
|
nodeType() {
|
||||||
|
|||||||
@@ -24,35 +24,24 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
const hash = 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 }),
|
||||||
allClusterNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.CLUSTER_NETWORK }),
|
allClusterNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.CLUSTER_NETWORK }),
|
||||||
});
|
});
|
||||||
const instanceMap = {};
|
|
||||||
|
|
||||||
(hash.vmis || []).forEach((vmi) => {
|
|
||||||
const vmiUID = vmi?.metadata?.ownerReferences?.[0]?.uid;
|
|
||||||
|
|
||||||
if (vmiUID) {
|
|
||||||
instanceMap[vmiUID] = vmi;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.allClusterNetwork = hash.allClusterNetwork;
|
|
||||||
this.rows = hash.vms.filter((row) => {
|
|
||||||
return instanceMap[row.metadata?.uid]?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
rows: [],
|
|
||||||
allClusterNetwork: []
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
allClusterNetwork() {
|
||||||
|
return this.$store.getters['harvester/all'](HCI.CLUSTER_NETWORK);
|
||||||
|
},
|
||||||
|
|
||||||
|
rows() {
|
||||||
|
const vms = this.$store.getters['harvester/all'](HCI.VM);
|
||||||
|
|
||||||
|
return vms.filter((vm) => vm.vmi?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME]);
|
||||||
|
},
|
||||||
|
|
||||||
headers() {
|
headers() {
|
||||||
return [
|
return [
|
||||||
STATE,
|
STATE,
|
||||||
@@ -87,8 +76,6 @@ export default {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
|
|||||||
import Loading from '@shell/components/Loading.vue';
|
import Loading from '@shell/components/Loading.vue';
|
||||||
import SortableTable from '@shell/components/SortableTable';
|
import SortableTable from '@shell/components/SortableTable';
|
||||||
import Banner from '@components/Banner/Banner.vue';
|
import Banner from '@components/Banner/Banner.vue';
|
||||||
|
import { UNIT_SUFFIX } from '../../utils/unit';
|
||||||
import metricPoller from '@shell/mixins/metric-poller';
|
import metricPoller from '@shell/mixins/metric-poller';
|
||||||
import {
|
import {
|
||||||
METRIC, NODE, LONGHORN, POD, EVENT
|
METRIC, NODE, LONGHORN, POD, EVENT
|
||||||
@@ -178,7 +178,7 @@ export default {
|
|||||||
minExponent: 3,
|
minExponent: 3,
|
||||||
maxExponent: 3,
|
maxExponent: 3,
|
||||||
maxPrecision: 2,
|
maxPrecision: 2,
|
||||||
suffix: 'iB',
|
suffix: UNIT_SUFFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
const longhornDisks = Object.keys(diskStatus).map((key) => {
|
const longhornDisks = Object.keys(diskStatus).map((key) => {
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ export default {
|
|||||||
:label="t('harvester.modal.cloneVM.name')"
|
:label="t('harvester.modal.cloneVM.name')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template
|
<template
|
||||||
@@ -127,11 +133,6 @@ export default {
|
|||||||
@click="create"
|
@click="create"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
|
import { alternateLabel } from '@shell/utils/platform';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import { Card } from '@components/Card';
|
||||||
|
import { escapeHtml } from '@shell/utils/string';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name ConfirmExecutionDialog
|
||||||
|
* @description Dialog component to confirm the related resources before executing the action.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'ConfirmExecutionDialog',
|
||||||
|
|
||||||
|
emits: ['close'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
AsyncButton,
|
||||||
|
Banner,
|
||||||
|
Card,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* @property resources to be deleted.
|
||||||
|
* @type {Resource[]} Array of the resource model's instance
|
||||||
|
*/
|
||||||
|
resources: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return { errors: [] };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState('action-menu', ['modalData']),
|
||||||
|
|
||||||
|
warningMessageKey() {
|
||||||
|
return this.modalData.warningMessageKey;
|
||||||
|
},
|
||||||
|
|
||||||
|
names() {
|
||||||
|
return this.resources.map((obj) => obj.nameDisplay).slice(0, 5);
|
||||||
|
},
|
||||||
|
|
||||||
|
resourceNames() {
|
||||||
|
return this.names.reduce((res, name, i) => {
|
||||||
|
if (i >= 5) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
res += `<b>${ escapeHtml(name) }</b>`;
|
||||||
|
if (i === this.names.length - 1) {
|
||||||
|
res += this.plusMore;
|
||||||
|
} else {
|
||||||
|
res += i === this.resources.length - 2 ? ' and ' : ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
plusMore() {
|
||||||
|
const remaining = this.resources.length - this.names.length;
|
||||||
|
|
||||||
|
return this.t('dialog.confirmExecution.andOthers', { count: remaining }, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
type() {
|
||||||
|
const types = new Set(this.resources.reduce((array, each) => {
|
||||||
|
array.push(each.type);
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}, []));
|
||||||
|
|
||||||
|
if (types.size > 1) {
|
||||||
|
return this.t('generic.resource', { count: this.resources.length });
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = this.resources[0]?.schema;
|
||||||
|
|
||||||
|
if ( !schema ) {
|
||||||
|
return `resource${ this.resources.length === 1 ? '' : 's' }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.$store.getters['type-map/labelFor'](schema, this.resources.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
protip() {
|
||||||
|
return this.t('dialog.confirmExecution.protip', { alternateLabel });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
escapeHtml,
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.errors = [];
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
async apply(buttonDone) {
|
||||||
|
try {
|
||||||
|
for (const resource of this.resources) {
|
||||||
|
await resource.doActionGrowl(this.modalData.action, {});
|
||||||
|
}
|
||||||
|
buttonDone(true);
|
||||||
|
this.close();
|
||||||
|
} catch (e) {
|
||||||
|
this.errors = exceptionToErrorsArray(e);
|
||||||
|
buttonDone(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card :show-highlight-border="false">
|
||||||
|
<template #title>
|
||||||
|
<h4 class="text-default-text">
|
||||||
|
{{ t('dialog.confirmExecution.title') }}
|
||||||
|
</h4>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<div class="pl-10 pr-10">
|
||||||
|
<span
|
||||||
|
v-clean-html="t(warningMessageKey, { type, names: resourceNames }, true)"
|
||||||
|
></span>
|
||||||
|
<div class="text-info mt-20">
|
||||||
|
{{ protip }}
|
||||||
|
</div>
|
||||||
|
<Banner
|
||||||
|
v-for="(error, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
:label="error"
|
||||||
|
color="error"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<div class="actions">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
<AsyncButton
|
||||||
|
mode="apply"
|
||||||
|
class="btn bg-primary ml-10"
|
||||||
|
:disabled="applyDisabled"
|
||||||
|
@click="apply"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.modal-container {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
width: 100%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -42,8 +42,25 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState('action-menu', ['modalData']),
|
...mapState('action-menu', ['modalData']),
|
||||||
|
|
||||||
warningMessageKey() {
|
title() {
|
||||||
return this.modalData.warningMessageKey;
|
return this.modalData.title || 'dialog.promptRemove.title';
|
||||||
|
},
|
||||||
|
|
||||||
|
formattedType() {
|
||||||
|
return this.type.toLowerCase();
|
||||||
|
},
|
||||||
|
|
||||||
|
warningMessage() {
|
||||||
|
if (this.modalData.warningMessage) return this.modalData.warningMessage;
|
||||||
|
|
||||||
|
const isPlural = this.type.endsWith('s');
|
||||||
|
const thisOrThese = isPlural ? 'these' : 'this';
|
||||||
|
const defaultMessage = this.t('dialog.promptRemove.warningMessage', {
|
||||||
|
type: this.formattedType,
|
||||||
|
thisOrThese,
|
||||||
|
});
|
||||||
|
|
||||||
|
return defaultMessage;
|
||||||
},
|
},
|
||||||
|
|
||||||
names() {
|
names() {
|
||||||
@@ -70,6 +87,12 @@ export default {
|
|||||||
return this.resources[0].nameDisplay;
|
return this.resources[0].nameDisplay;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
needConfirmation() {
|
||||||
|
const { needConfirmation = true } = this.modalData ;
|
||||||
|
|
||||||
|
return needConfirmation === true;
|
||||||
|
},
|
||||||
|
|
||||||
plusMore() {
|
plusMore() {
|
||||||
const remaining = this.resources.length - this.names.length;
|
const remaining = this.resources.length - this.names.length;
|
||||||
|
|
||||||
@@ -97,11 +120,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
deleteDisabled() {
|
deleteDisabled() {
|
||||||
|
if (!this.needConfirmation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return this.confirmName !== this.nameToMatch;
|
return this.confirmName !== this.nameToMatch;
|
||||||
},
|
},
|
||||||
|
|
||||||
protip() {
|
protip() {
|
||||||
return this.t('promptRemove.protip', { alternateLabel });
|
return this.t('dialog.promptRemove.protip', { alternateLabel });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -137,35 +164,45 @@ export default {
|
|||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<h4 class="text-default-text">
|
<h4 class="text-default-text">
|
||||||
{{ t('promptRemove.title') }}
|
{{ t(title, { type }, true) }}
|
||||||
</h4>
|
</h4>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="pl-10 pr-10">
|
<div class="pl-10 pr-10">
|
||||||
<span
|
<span
|
||||||
v-clean-html="t(warningMessageKey, { type, names: resourceNames }, true)"
|
v-clean-html="warningMessage"
|
||||||
></span>
|
></span>
|
||||||
|
|
||||||
<div class="mt-10 mb-10">
|
<div
|
||||||
<span
|
v-if="needConfirmation"
|
||||||
v-clean-html="t('promptRemove.confirmName', { nameToMatch: escapeHtml(nameToMatch) }, true)"
|
class="mt-20"
|
||||||
></span>
|
>
|
||||||
</div>
|
<div class="mt-10 mb-10">
|
||||||
<div class="mb-10">
|
<span
|
||||||
<CopyToClipboardText :text="nameToMatch" />
|
v-clean-html="t('dialog.promptRemove.confirmName', {
|
||||||
</div>
|
type: formattedType,
|
||||||
<input
|
nameToMatch: escapeHtml(nameToMatch)
|
||||||
id="confirm"
|
}, true)"
|
||||||
v-model="confirmName"
|
></span>
|
||||||
type="text"
|
</div>
|
||||||
/>
|
<div class="mb-10">
|
||||||
<div class="text-info mt-20">
|
<CopyToClipboardText :text="nameToMatch" />
|
||||||
{{ protip }}
|
</div>
|
||||||
|
<input
|
||||||
|
id="confirm"
|
||||||
|
v-model="confirmName"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<div class="text-info mt-20">
|
||||||
|
{{ protip }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Banner
|
<Banner
|
||||||
v-for="(error, i) in errors"
|
v-for="(error, i) in errors"
|
||||||
:key="i"
|
:key="i"
|
||||||
|
:label="error"
|
||||||
|
color="error"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ export default {
|
|||||||
:label="t('harvester.modal.cloneVM.name')"
|
:label="t('harvester.modal.cloneVM.name')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
:label="err"
|
||||||
|
color="error"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -125,13 +131,6 @@ export default {
|
|||||||
@click="create"
|
@click="create"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -93,7 +93,10 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body>
|
<template #body>
|
||||||
{{ t('harvester.usb.enablePassthroughWarning') }}
|
<t
|
||||||
|
k="harvester.usb.enablePassthroughWarning"
|
||||||
|
:raw="true"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ export default {
|
|||||||
if (!!pvc.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID]) {
|
if (!!pvc.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// we won't show golden image volume in the hot plug volume modal
|
||||||
|
if (pvc.isGoldenImageVolume) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return !pvc.attachVM;
|
return !pvc.attachVM;
|
||||||
})
|
})
|
||||||
@@ -137,6 +141,12 @@ export default {
|
|||||||
class="mt-20"
|
class="mt-20"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
:label="err"
|
||||||
|
color="error"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -156,11 +166,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ export default {
|
|||||||
:label="t('generic.name')"
|
:label="t('generic.name')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -121,13 +127,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -120,6 +120,12 @@ export default {
|
|||||||
v-model:value="description"
|
v-model:value="description"
|
||||||
:label="t('harvester.modal.createTemplate.description')"
|
:label="t('harvester.modal.createTemplate.description')"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -138,11 +144,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ export default {
|
|||||||
<Banner color="warning">
|
<Banner color="warning">
|
||||||
<span>{{ t('harvester.modal.ejectCDROM.warnTip') }}</span>
|
<span>{{ t('harvester.modal.ejectCDROM.warnTip') }}</span>
|
||||||
</Banner>
|
</Banner>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -147,13 +153,6 @@ export default {
|
|||||||
@click="remove"
|
@click="remove"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -34,9 +34,14 @@ export default {
|
|||||||
|
|
||||||
await allHash(hash);
|
await allHash(hash);
|
||||||
|
|
||||||
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
|
const allStorages = this.allStorageClasses;
|
||||||
|
const defaultStorage = allStorages.find((s) => s.isDefault);
|
||||||
|
|
||||||
this['storageClassName'] = defaultStorage?.metadata?.name || 'longhorn';
|
if (this.isLonghornV1Volume) {
|
||||||
|
this['storageClassName'] = defaultStorage?.metadata?.name || 'longhorn';
|
||||||
|
} else {
|
||||||
|
this['storageClassName'] = this.nonLonghornV1StorageClasses[0]?.metadata?.name || '';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@@ -54,7 +59,7 @@ export default {
|
|||||||
...mapGetters({ t: 'i18n/t' }),
|
...mapGetters({ t: 'i18n/t' }),
|
||||||
|
|
||||||
actionResource() {
|
actionResource() {
|
||||||
return this.resources[0];
|
return this.resources[0] || {};
|
||||||
},
|
},
|
||||||
|
|
||||||
namespaces() {
|
namespaces() {
|
||||||
@@ -73,15 +78,33 @@ export default {
|
|||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isLonghornV1Volume() {
|
||||||
|
return this.actionResource?.isLonghornV1 === true;
|
||||||
|
},
|
||||||
|
|
||||||
disableSave() {
|
disableSave() {
|
||||||
return !(this.name && this.namespace && this.storageClassName);
|
return !(this.name && this.namespace && this.storageClassName);
|
||||||
},
|
},
|
||||||
|
|
||||||
storageClassOptions() {
|
allStorageClasses() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
|
||||||
|
|
||||||
const out = storages.filter((s) => !s.parameters?.backingImage).map((s) => {
|
return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS) || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
nonLonghornV1StorageClasses() {
|
||||||
|
return this.allStorageClasses.filter((s) => s.isLonghornV1 === false) || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
storageClassOptions() {
|
||||||
|
let storages = this.allStorageClasses;
|
||||||
|
|
||||||
|
// Volume with non-longhorn v1 sc can't be exported to longhorn v1 sc image
|
||||||
|
if (!this.isLonghornV1Volume) {
|
||||||
|
storages = this.allStorageClasses.filter((s) => s.isLonghornV1 === false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = storages.filter((s) => !s.parameters?.backingImage).map((s) => {
|
||||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -90,7 +113,7 @@ export default {
|
|||||||
};
|
};
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
return out;
|
return options;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -164,6 +187,12 @@ export default {
|
|||||||
class="mt-20"
|
class="mt-20"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -182,11 +211,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
|
import { HCI } from '../types';
|
||||||
|
import { Card } from '@components/Card';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import AppModal from '@shell/components/AppModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HarvesterImageDownloaderDialog',
|
||||||
|
|
||||||
|
emits: ['close'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
AsyncButton, Banner, Card, AppModal
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
resources: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return { errors: [], isOpen: false };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({ t: 'i18n/t' }),
|
||||||
|
|
||||||
|
downloadImageInProgress() {
|
||||||
|
return this.$store.getters['harvester-common/isDownloadImageInProgress'];
|
||||||
|
},
|
||||||
|
|
||||||
|
image() {
|
||||||
|
return this.resources[0] || {};
|
||||||
|
},
|
||||||
|
|
||||||
|
imageName() {
|
||||||
|
return this.image?.name || '';
|
||||||
|
},
|
||||||
|
|
||||||
|
imageVirtualSize() {
|
||||||
|
return this.image?.virtualSize || this.image?.downSize || '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async cancelDownload() {
|
||||||
|
const url = this.image?.links?.downloadcancel;
|
||||||
|
|
||||||
|
if (url) {
|
||||||
|
await this.$store.dispatch('harvester/request', { url });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
if (this.downloadImageInProgress) {
|
||||||
|
this.$store.commit('harvester-common/setDownloadImageCancel', true);
|
||||||
|
this.$store.commit('harvester-common/setDownloadImageInProgress', false);
|
||||||
|
await this.cancelDownload();
|
||||||
|
}
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
async startDownload(buttonCb) {
|
||||||
|
// clean the download image CRD first.
|
||||||
|
await this.cancelDownload();
|
||||||
|
this.$store.commit('harvester-common/setDownloadImageCancel', false);
|
||||||
|
this.$store.commit('harvester-common/setDownloadImageInProgress', false);
|
||||||
|
this.errors = [];
|
||||||
|
|
||||||
|
const name = this.image?.name || '';
|
||||||
|
const namespace = this.image?.namespace || '';
|
||||||
|
|
||||||
|
const imageCrd = {
|
||||||
|
apiVersion: 'harvesterhci.io/v1beta1',
|
||||||
|
type: HCI.VM_IMAGE_DOWNLOADER,
|
||||||
|
kind: 'VirtualMachineImageDownloader',
|
||||||
|
metadata: {
|
||||||
|
name,
|
||||||
|
namespace
|
||||||
|
},
|
||||||
|
spec: { imageName: name }
|
||||||
|
};
|
||||||
|
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const imageCreate = await this.$store.dispatch(`${ inStore }/create`, imageCrd);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await imageCreate.save();
|
||||||
|
this.$store.commit('harvester-common/setDownloadImageId', `${ namespace }/${ name }`, { root: true });
|
||||||
|
this.$store.dispatch('harvester-common/downloadImageProgress', { root: true });
|
||||||
|
} catch (err) {
|
||||||
|
this.errors = exceptionToErrorsArray(err);
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<app-modal
|
||||||
|
class="image-downloader-modal"
|
||||||
|
name="image-download-dialog"
|
||||||
|
height="auto"
|
||||||
|
:width="600"
|
||||||
|
:click-to-close="false"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<Card :show-highlight-border="false">
|
||||||
|
<template #title>
|
||||||
|
{{ t('harvester.modal.downloadImage.title') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<Banner color="info">
|
||||||
|
{{ t('harvester.modal.downloadImage.banner', { size: imageVirtualSize }) }}
|
||||||
|
</Banner>
|
||||||
|
{{ t('harvester.modal.downloadImage.startMessage') }}
|
||||||
|
<br /><br />
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary mr-10"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
<AsyncButton
|
||||||
|
type="submit"
|
||||||
|
mode="download"
|
||||||
|
:disabled="downloadImageInProgress"
|
||||||
|
@click="startDownload"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</app-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -161,6 +161,12 @@ export default {
|
|||||||
:placeholder="t('harvester.modal.migration.fields.nodeName.placeholder')"
|
:placeholder="t('harvester.modal.migration.fields.nodeName.placeholder')"
|
||||||
:options="nodeNameList"
|
:options="nodeNameList"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template
|
<template
|
||||||
@@ -181,12 +187,6 @@ export default {
|
|||||||
@click="apply"
|
@click="apply"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Banner } from '@components/Banner';
|
|||||||
import AsyncButton from '@shell/components/AsyncButton';
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
import UnitInput from '@shell/components/form/UnitInput';
|
import UnitInput from '@shell/components/form/UnitInput';
|
||||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
|
import { GIBIBYTE } from '../utils/unit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterVMQuotaDialog',
|
name: 'HarvesterVMQuotaDialog',
|
||||||
@@ -29,6 +30,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
GIBIBYTE,
|
||||||
totalSnapshotSize: '',
|
totalSnapshotSize: '',
|
||||||
errors: []
|
errors: []
|
||||||
};
|
};
|
||||||
@@ -44,7 +46,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.totalSnapshotSize = '';
|
this.totalSnapshotSize = '';
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
@@ -92,9 +93,15 @@ export default {
|
|||||||
:input-exponent="3"
|
:input-exponent="3"
|
||||||
:increment="1024"
|
:increment="1024"
|
||||||
:output-modifier="true"
|
:output-modifier="true"
|
||||||
suffix="GiB"
|
:suffix="GIBIBYTE"
|
||||||
class="mb-20"
|
class="mb-20"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -108,12 +115,6 @@ export default {
|
|||||||
</button>
|
</button>
|
||||||
<AsyncButton @click="save" />
|
<AsyncButton @click="save" />
|
||||||
</div>
|
</div>
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -136,6 +136,12 @@ export default {
|
|||||||
:options="backupOption"
|
:options="backupOption"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -154,11 +160,6 @@ export default {
|
|||||||
@click="saveRestore"
|
@click="saveRestore"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -90,6 +90,12 @@ export default {
|
|||||||
v-clean-html="t('harvester.virtualMachine.unplug.title', { name: diskName })"
|
v-clean-html="t('harvester.virtualMachine.unplug.title', { name: diskName })"
|
||||||
class="text-default-text"
|
class="text-default-text"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -111,13 +117,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ export default {
|
|||||||
:label="t('generic.name')"
|
:label="t('generic.name')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -150,11 +156,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -107,6 +107,12 @@ export default {
|
|||||||
:placeholder="t('harvester.harvesterVlanConfigMigrateDialog.targetClusterNetwork.placeholder')"
|
:placeholder="t('harvester.harvesterVlanConfigMigrateDialog.targetClusterNetwork.placeholder')"
|
||||||
:options="clusterNetworks"
|
:options="clusterNetworks"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -125,11 +131,6 @@ export default {
|
|||||||
@click="apply"
|
@click="apply"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -70,16 +70,16 @@ export default {
|
|||||||
v-clean-html="t('harvester.modal.restart.tip')"
|
v-clean-html="t('harvester.modal.restart.tip')"
|
||||||
class="pl-10 pr-10"
|
class="pl-10 pr-10"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button
|
<button
|
||||||
class="btn role-secondary mr-10"
|
class="btn role-secondary mr-10"
|
||||||
|
|||||||
@@ -145,6 +145,12 @@ export default {
|
|||||||
class="mt-20"
|
class="mt-20"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -162,10 +168,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ export default {
|
|||||||
:label="t('harvester.modal.snapshot.name')"
|
:label="t('harvester.modal.snapshot.name')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -96,12 +102,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
color="error"
|
|
||||||
:label="err"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ export default {
|
|||||||
:label="t('harvester.modal.volumeClone.name')"
|
:label="t('harvester.modal.volumeClone.name')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -115,10 +121,6 @@ export default {
|
|||||||
@click="save"
|
@click="save"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Banner
|
|
||||||
v-for="(err, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Banner } from '@components/Banner';
|
|||||||
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
|
|
||||||
const DEFAULT_VALUE = { image: { repo: 'rancher/harvester-nvidia-driver-toolkit' } };
|
const DEFAULT_VALUE = { image: { repository: 'rancher/harvester-nvidia-driver-toolkit' } };
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditAddonNvidiaDriverToolkit',
|
name: 'EditAddonNvidiaDriverToolkit',
|
||||||
@@ -122,7 +122,7 @@ export default {
|
|||||||
>
|
>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model:value="valuesContentJson.image.repo"
|
v-model:value="valuesContentJson.image.repository"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:required="true"
|
:required="true"
|
||||||
label-key="harvester.addons.nvidiaDriverToolkit.image.repository"
|
label-key="harvester.addons.nvidiaDriverToolkit.image.repository"
|
||||||
|
|||||||
@@ -1,34 +1,31 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import jsyaml from 'js-yaml';
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
import Tabbed from '@shell/components/Tabbed';
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
import Tab from '@shell/components/Tabbed/Tab';
|
||||||
import { RadioGroup } from '@components/Form/Radio';
|
import { RadioGroup } from '@components/Form/Radio';
|
||||||
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 { STORAGE_CLASS } from '@shell/config/types';
|
import { STORAGE_CLASS } from '@shell/config/types';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import { set, get, clone } from '@shell/utils/object';
|
|
||||||
|
|
||||||
const VALUES_YAML_KEYS = [
|
|
||||||
'resources.requests.cpu',
|
|
||||||
'resources.requests.memory',
|
|
||||||
'resources.limits.cpu',
|
|
||||||
'resources.limits.memory',
|
|
||||||
'pvcClaim.enabled',
|
|
||||||
'pvcClaim.size',
|
|
||||||
'pvcClaim.storageClassName',
|
|
||||||
];
|
|
||||||
|
|
||||||
const DEFAULT_VALUES = {
|
const DEFAULT_VALUES = {
|
||||||
'resources.requests.cpu': '0.5',
|
resources: {
|
||||||
'resources.requests.memory': '2Gi',
|
requests: {
|
||||||
'resources.limits.cpu': '2',
|
cpu: '0.5',
|
||||||
'resources.limits.memory': '4Gi',
|
memory: '2Gi'
|
||||||
'pvcClaim.enabled': false,
|
},
|
||||||
'pvcClaim.size': '200Gi',
|
limits: {
|
||||||
'pvcClaim.storageClassName': '',
|
cpu: '2',
|
||||||
|
memory: '4Gi'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pvcClaim: {
|
||||||
|
enabled: false,
|
||||||
|
size: '200Gi',
|
||||||
|
storageClassName: ''
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -64,19 +61,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
let valuesObj = {};
|
const valuesContent = this.parseValuesContent();
|
||||||
|
|
||||||
try {
|
|
||||||
valuesObj = JSON.parse(this.value?.spec?.valuesContent || '{}');
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
const valuesContent = clone(valuesObj);
|
|
||||||
|
|
||||||
VALUES_YAML_KEYS.map((key) => {
|
|
||||||
if (!get(valuesObj, key)) {
|
|
||||||
set(valuesContent, key, DEFAULT_VALUES[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { valuesContent };
|
return { valuesContent };
|
||||||
},
|
},
|
||||||
@@ -100,8 +85,21 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
parseValuesContent() {
|
||||||
|
try {
|
||||||
|
return merge({}, DEFAULT_VALUES, jsyaml.load(this.value.spec.valuesContent));
|
||||||
|
} catch (err) {
|
||||||
|
this.$store.dispatch('growl/fromError', {
|
||||||
|
title: this.$store.getters['i18n/t']('generic.notification.title.error'),
|
||||||
|
err: err.data || err,
|
||||||
|
}, { root: true });
|
||||||
|
|
||||||
|
return DEFAULT_VALUES;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
set(this.value, 'spec.valuesContent', JSON.stringify(this.valuesContent));
|
this.value.spec.valuesContent = jsyaml.dump(this.valuesContent);
|
||||||
},
|
},
|
||||||
|
|
||||||
setDefaultClassName() {
|
setDefaultClassName() {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import Loading from '@shell/components/Loading.vue';
|
|||||||
import MessageLink from '@shell/components/MessageLink';
|
import MessageLink from '@shell/components/MessageLink';
|
||||||
import { ADD_ONS } from '@pkg/harvester/config/harvester-map';
|
import { ADD_ONS } from '@pkg/harvester/config/harvester-map';
|
||||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
|
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
|
||||||
|
import { UNIT_SUFFIX } from '../../utils/unit';
|
||||||
import { _EDIT } from '@shell/config/query-params';
|
import { _EDIT } from '@shell/config/query-params';
|
||||||
import { sortBy } from '@shell/utils/sort';
|
import { sortBy } from '@shell/utils/sort';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
@@ -185,7 +185,7 @@ export default {
|
|||||||
minExponent: 3,
|
minExponent: 3,
|
||||||
maxExponent: 3,
|
maxExponent: 3,
|
||||||
maxPrecision: 2,
|
maxPrecision: 2,
|
||||||
suffix: 'iB',
|
suffix: UNIT_SUFFIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
const longhornDisks = Object.keys(diskStatus).map((key) => {
|
const longhornDisks = Object.keys(diskStatus).map((key) => {
|
||||||
@@ -457,7 +457,7 @@ export default {
|
|||||||
const devPath = d.spec?.devPath;
|
const devPath = d.spec?.devPath;
|
||||||
const deviceType = d.status?.deviceStatus?.details?.deviceType;
|
const deviceType = d.status?.deviceStatus?.details?.deviceType;
|
||||||
const sizeBytes = d.status?.deviceStatus?.capacity?.sizeBytes;
|
const sizeBytes = d.status?.deviceStatus?.capacity?.sizeBytes;
|
||||||
const size = formatSi(sizeBytes, { increment: 1024 });
|
const size = formatSi(sizeBytes, { increment: 1024, suffix: UNIT_SUFFIX });
|
||||||
const parentDevice = d.status?.deviceStatus?.parentDevice;
|
const parentDevice = d.status?.deviceStatus?.parentDevice;
|
||||||
const isChildAdded = this.newDisks.find((newDisk) => newDisk.blockDevice?.status?.deviceStatus?.parentDevice === devPath);
|
const isChildAdded = this.newDisks.find((newDisk) => newDisk.blockDevice?.status?.deviceStatus?.parentDevice === devPath);
|
||||||
const name = d.displayName;
|
const name = d.displayName;
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ export default {
|
|||||||
if (this.isCreate) {
|
if (this.isCreate) {
|
||||||
this.value.metadata.namespace = 'default';
|
this.value.metadata.namespace = 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
set(this.value, 'spec', this.value.spec || {});
|
set(this.value, 'spec', this.value.spec || {});
|
||||||
|
|
||||||
const providers = PROVIDERS.map((provider) => ({
|
const providers = PROVIDERS.map((provider) => ({
|
||||||
@@ -83,14 +82,6 @@ export default {
|
|||||||
label: this.t(provider.labelKey)
|
label: this.t(provider.labelKey)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (this.mode !== _VIEW) {
|
|
||||||
this.value['spec'] = this.value.spec || {};
|
|
||||||
|
|
||||||
providers.forEach((provider) => {
|
|
||||||
this.value.spec[provider.name] = this.value.spec[provider.name] || clone(provider.default);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedProviders = providers.filter((provider) => {
|
const selectedProviders = providers.filter((provider) => {
|
||||||
const specProvider = this.value.spec[provider.name];
|
const specProvider = this.value.spec[provider.name];
|
||||||
const correctedSpecProvider = provider.name === 'forward' ? specProvider?.servers?.[0] || {} : specProvider;
|
const correctedSpecProvider = provider.name === 'forward' ? specProvider?.servers?.[0] || {} : specProvider;
|
||||||
@@ -98,7 +89,12 @@ export default {
|
|||||||
return !isEmpty(correctedSpecProvider) && !isEqual(correctedSpecProvider, provider.default);
|
return !isEmpty(correctedSpecProvider) && !isEqual(correctedSpecProvider, provider.default);
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedProvider = selectedProviders?.[0]?.value || providers[0].value;
|
const selectedProvider = selectedProviders?.[0]?.value || providers[0].value; // selected provider name
|
||||||
|
const selectedProviderDefault = providers.find((p) => p.name === selectedProvider)?.default || providers[0].default;
|
||||||
|
|
||||||
|
if (this.mode !== _VIEW) {
|
||||||
|
set(this.value.spec, selectedProvider, this.value.spec[selectedProvider] || clone(selectedProviderDefault));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bufferYaml: '',
|
bufferYaml: '',
|
||||||
@@ -129,6 +125,18 @@ export default {
|
|||||||
outputTypeOptions() {
|
outputTypeOptions() {
|
||||||
return OUTPUT_TYPE;
|
return OUTPUT_TYPE;
|
||||||
},
|
},
|
||||||
|
outputProvider: {
|
||||||
|
get() {
|
||||||
|
return this.selectedProvider;
|
||||||
|
},
|
||||||
|
|
||||||
|
set(newProvider) {
|
||||||
|
this.selectedProvider = newProvider;
|
||||||
|
const providerDefaultSpec = this.providers.find((p) => p.name === newProvider)?.default || {};
|
||||||
|
|
||||||
|
this.value.spec = { [newProvider]: this.value.spec[newProvider] || clone(providerDefaultSpec) };
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@@ -142,8 +150,6 @@ export default {
|
|||||||
this.$refs.tabbed.select(provider.name);
|
this.$refs.tabbed.select(provider.name);
|
||||||
},
|
},
|
||||||
willSave() {
|
willSave() {
|
||||||
this.value.spec = { [this.selectedProvider]: this.value.spec[this.selectedProvider] };
|
|
||||||
|
|
||||||
const bufferJson = jsyaml.load(this.bufferYaml);
|
const bufferJson = jsyaml.load(this.bufferYaml);
|
||||||
|
|
||||||
if (!isEmpty(bufferJson)) {
|
if (!isEmpty(bufferJson)) {
|
||||||
@@ -236,7 +242,7 @@ export default {
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model:value="selectedProvider"
|
v-model:value="outputProvider"
|
||||||
label="Output"
|
label="Output"
|
||||||
:options="providers"
|
:options="providers"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
|
||||||
import { HCI, SCHEMA, CAPI, VIRTUAL_HARVESTER_PROVIDER } from '@shell/config/types';
|
|
||||||
import ClusterMembershipEditor from '@shell/components/form/Members/ClusterMembershipEditor';
|
|
||||||
import { Banner } from '@components/Banner';
|
|
||||||
import Labels from '@shell/edit/provisioning.cattle.io.cluster/Labels';
|
|
||||||
import AgentEnv from '@shell/edit/provisioning.cattle.io.cluster/AgentEnv';
|
|
||||||
import { set, get, clone } from '@shell/utils/object';
|
|
||||||
import { CAPI as CAPI_LABEL } from '@shell/config/labels-annotations';
|
|
||||||
|
|
||||||
import { createYaml } from '@shell/utils/create-yaml';
|
|
||||||
|
|
||||||
const REAL_TYPE = CAPI.RANCHER_CLUSTER;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
emits: ['input'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
Banner,
|
|
||||||
ClusterMembershipEditor,
|
|
||||||
NameNsDescription,
|
|
||||||
CruResource,
|
|
||||||
Tab,
|
|
||||||
Tabbed,
|
|
||||||
Labels,
|
|
||||||
AgentEnv
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { membershipUpdate: {} };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
generateYaml() {
|
|
||||||
return () => {
|
|
||||||
const resource = this.value;
|
|
||||||
|
|
||||||
const inStore = this.$store.getters['currentStore'](resource);
|
|
||||||
const schemas = this.$store.getters[`${ inStore }/all`](SCHEMA);
|
|
||||||
const clonedResource = clone(resource);
|
|
||||||
|
|
||||||
delete clonedResource.isSpoofed;
|
|
||||||
|
|
||||||
const out = createYaml(schemas, REAL_TYPE, clonedResource);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
done() {
|
|
||||||
return this.$router.replace({
|
|
||||||
name: 'c-cluster-product-resource-namespace-id',
|
|
||||||
params: {
|
|
||||||
resource: HCI.CLUSTER,
|
|
||||||
namespace: this.value.metadata.namespace,
|
|
||||||
id: this.value.metadata.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async saveOverride() {
|
|
||||||
set(this.value, 'metadata.labels', {
|
|
||||||
...(get(this.value, 'metadata.labels') || {}),
|
|
||||||
[CAPI_LABEL.PROVIDER]: VIRTUAL_HARVESTER_PROVIDER,
|
|
||||||
});
|
|
||||||
|
|
||||||
set(this.value, 'type', REAL_TYPE);
|
|
||||||
|
|
||||||
await this.save(...arguments);
|
|
||||||
|
|
||||||
this.value.waitForMgmt().then(() => {
|
|
||||||
if (this.membershipUpdate.save) {
|
|
||||||
this.membershipUpdate.save(this.value.mgmt.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onMembershipUpdate(update) {
|
|
||||||
this['membershipUpdate'] = update;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:mode="mode"
|
|
||||||
:resource="value"
|
|
||||||
:errors="errors"
|
|
||||||
:validation-passed="true"
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:generate-yaml="generateYaml"
|
|
||||||
@finish="saveOverride"
|
|
||||||
@error="e=>errors = e"
|
|
||||||
>
|
|
||||||
<Banner
|
|
||||||
v-if="isCreate"
|
|
||||||
color="warning"
|
|
||||||
>
|
|
||||||
{{ t('harvesterManager.cluster.supportMessage') }}
|
|
||||||
</Banner>
|
|
||||||
<div class="mt-20">
|
|
||||||
<NameNsDescription
|
|
||||||
v-if="!isView"
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
:namespaced="false"
|
|
||||||
name-label="cluster.name.label"
|
|
||||||
name-placeholder="cluster.name.placeholder"
|
|
||||||
description-label="cluster.description.label"
|
|
||||||
description-placeholder="cluster.description.placeholder"
|
|
||||||
@update:value="$emit('input', $event)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabbed :side-tabs="true">
|
|
||||||
<Tab
|
|
||||||
name="memberRoles"
|
|
||||||
label-key="cluster.tabs.memberRoles"
|
|
||||||
:weight="3"
|
|
||||||
>
|
|
||||||
<Banner
|
|
||||||
v-if="isEdit"
|
|
||||||
color="info"
|
|
||||||
>
|
|
||||||
{{ t('cluster.memberRoles.removeMessage') }}
|
|
||||||
</Banner>
|
|
||||||
<ClusterMembershipEditor
|
|
||||||
:mode="mode"
|
|
||||||
:parent-id="value.mgmt ? value.mgmt.id : null"
|
|
||||||
@membership-update="onMembershipUpdate"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
<AgentEnv
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
@update:value="$emit('input', $event)"
|
|
||||||
/>
|
|
||||||
<Labels
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
@update:value="$emit('input', $event)"
|
|
||||||
/>
|
|
||||||
</Tabbed>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
@@ -11,6 +11,9 @@ import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-anno
|
|||||||
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';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
|
import { NETWORK_TYPE } from '../config/types';
|
||||||
|
|
||||||
|
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
|
||||||
|
|
||||||
const AUTO = 'auto';
|
const AUTO = 'auto';
|
||||||
const MANUAL = 'manual';
|
const MANUAL = 'manual';
|
||||||
@@ -48,7 +51,7 @@ export default {
|
|||||||
config.bridge = config.bridge.slice(0, -3);
|
config.bridge = config.bridge.slice(0, -3);
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = this.value.vlanType || 'L2VlanNetwork' ;
|
const type = this.value.vlanType || L2VLAN ;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config,
|
config,
|
||||||
@@ -101,15 +104,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
networkType() {
|
networkType() {
|
||||||
return ['L2VlanNetwork', 'UntaggedNetwork'];
|
return [L2VLAN, UNTAGGED];
|
||||||
},
|
},
|
||||||
|
|
||||||
isUntaggedNetwork() {
|
isUntaggedNetwork() {
|
||||||
if (this.isView) {
|
if (this.isView) {
|
||||||
return this.value.vlanType === 'UntaggedNetwork';
|
return this.value.vlanType === UNTAGGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.type === 'UntaggedNetwork';
|
return this.type === UNTAGGED;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export default {
|
|||||||
:disabled="isEdit || isView"
|
:disabled="isEdit || isView"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
row
|
row
|
||||||
@input="onTypeChange"
|
@update:value="onTypeChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-30">
|
<div class="row mb-30">
|
||||||
|
|||||||
@@ -23,10 +23,9 @@ import Tags from '../../components/DiskTags';
|
|||||||
import { DATA_ENGINE_V1, DATA_ENGINE_V2 } from '../../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V1, DATA_ENGINE_V2 } from '../../models/harvester/persistentvolumeclaim';
|
||||||
import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass';
|
import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass';
|
||||||
|
|
||||||
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
|
||||||
|
|
||||||
export const LVM_TOPOLOGY_LABEL = 'topology.lvm.csi/node';
|
export const LVM_TOPOLOGY_LABEL = 'topology.lvm.csi/node';
|
||||||
|
|
||||||
|
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
||||||
const VOLUME_BINDING_MODE_IMMEDIATE = 'Immediate';
|
const VOLUME_BINDING_MODE_IMMEDIATE = 'Immediate';
|
||||||
const VOLUME_BINDING_MODE_WAIT = 'WaitForFirstConsumer';
|
const VOLUME_BINDING_MODE_WAIT = 'WaitForFirstConsumer';
|
||||||
|
|
||||||
|
|||||||
@@ -10,17 +10,6 @@ import { clone } from '@shell/utils/object';
|
|||||||
import { uniq } from '@shell/utils/array';
|
import { uniq } from '@shell/utils/array';
|
||||||
import { DATA_ENGINE_V1 } from '../../../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V1 } from '../../../models/harvester/persistentvolumeclaim';
|
||||||
|
|
||||||
// UI components for Longhorn storage class parameters
|
|
||||||
const DEFAULT_PARAMETERS = [
|
|
||||||
'numberOfReplicas',
|
|
||||||
'staleReplicaTimeout',
|
|
||||||
'diskSelector',
|
|
||||||
'nodeSelector',
|
|
||||||
'migratable',
|
|
||||||
'encrypted',
|
|
||||||
'dataEngine',
|
|
||||||
];
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
CSI_PROVISIONER_SECRET_NAME,
|
CSI_PROVISIONER_SECRET_NAME,
|
||||||
CSI_PROVISIONER_SECRET_NAMESPACE,
|
CSI_PROVISIONER_SECRET_NAMESPACE,
|
||||||
@@ -141,11 +130,17 @@ export default {
|
|||||||
get() {
|
get() {
|
||||||
const parameters = clone(this.value?.parameters) || {};
|
const parameters = clone(this.value?.parameters) || {};
|
||||||
|
|
||||||
DEFAULT_PARAMETERS.forEach((key) => {
|
// UI components for Longhorn storage class parameters
|
||||||
delete parameters[key];
|
const defaultParameters = [
|
||||||
});
|
'numberOfReplicas',
|
||||||
|
'staleReplicaTimeout',
|
||||||
|
'diskSelector',
|
||||||
|
'nodeSelector',
|
||||||
|
'migratable',
|
||||||
|
...(this.value.volumeEncryptionFeatureEnabled ? ['encrypted', 'dataEngine'] : []),
|
||||||
|
];
|
||||||
|
|
||||||
Object.values(CSI_SECRETS).forEach((key) => {
|
[...defaultParameters, ...Object.values(CSI_SECRETS)].forEach((key) => {
|
||||||
delete parameters[key];
|
delete parameters[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -311,16 +306,16 @@ export default {
|
|||||||
</LabeledSelect>
|
</LabeledSelect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mt-20">
|
||||||
|
<RadioGroup
|
||||||
|
v-model:value="value.parameters.migratable"
|
||||||
|
name="layer3NetworkMode"
|
||||||
|
:label="t('harvester.storage.parameters.migratable.label')"
|
||||||
|
:mode="mode"
|
||||||
|
:options="migratableOptions"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<template v-if="value.volumeEncryptionFeatureEnabled">
|
<template v-if="value.volumeEncryptionFeatureEnabled">
|
||||||
<div class="row mt-20">
|
|
||||||
<RadioGroup
|
|
||||||
v-model:value="value.parameters.migratable"
|
|
||||||
name="layer3NetworkMode"
|
|
||||||
:label="t('harvester.storage.parameters.migratable.label')"
|
|
||||||
:mode="mode"
|
|
||||||
:options="migratableOptions"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="row mt-20">
|
<div class="row mt-20">
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
v-model:value="volumeEncryption"
|
v-model:value="volumeEncryption"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default {
|
|||||||
diskSelector: null,
|
diskSelector: null,
|
||||||
nodeSelector: null,
|
nodeSelector: null,
|
||||||
encrypted: 'false',
|
encrypted: 'false',
|
||||||
migratable: 'false',
|
migratable: this.value.thirdPartyStorageFeatureEnabled ? 'true' : 'false',
|
||||||
dataEngine: DATA_ENGINE_V2
|
dataEngine: DATA_ENGINE_V2
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -318,7 +318,7 @@ export default {
|
|||||||
:label="t('harvester.storage.parameters.migratable.label')"
|
:label="t('harvester.storage.parameters.migratable.label')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:options="migratableOptions"
|
:options="migratableOptions"
|
||||||
:disabled="true"
|
:disabled="!value.thirdPartyStorageFeatureEnabled"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-20">
|
<div class="row mt-20">
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const CLONE = 'clone';
|
|||||||
const DOWNLOAD = 'download';
|
const DOWNLOAD = 'download';
|
||||||
const UPLOAD = 'upload';
|
const UPLOAD = 'upload';
|
||||||
const rawORqcow2 = 'raw_qcow2';
|
const rawORqcow2 = 'raw_qcow2';
|
||||||
|
const LONGHORN = 'longhorn';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditImage',
|
name: 'EditImage',
|
||||||
@@ -61,11 +62,11 @@ export default {
|
|||||||
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
|
this['storageClassName'] = this.storageClassName || this.defaultStorageClassName();
|
||||||
|
|
||||||
this['storageClassName'] = this.storageClassName || defaultStorage?.metadata?.name || 'longhorn';
|
|
||||||
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
||||||
|
|
||||||
|
this.storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||||
|
|
||||||
const { securityParameters } = this.value.spec;
|
const { securityParameters } = this.value.spec;
|
||||||
|
|
||||||
// edit and view mode should show the source image
|
// edit and view mode should show the source image
|
||||||
@@ -100,14 +101,15 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedImage: null,
|
selectedImage: null,
|
||||||
images: [],
|
storageClasses: [],
|
||||||
url: this.value.spec.url,
|
images: [],
|
||||||
files: [],
|
url: this.value.spec.url,
|
||||||
resource: '',
|
files: [],
|
||||||
headers: {},
|
resource: '',
|
||||||
fileUrl: '',
|
headers: {},
|
||||||
file: '',
|
fileUrl: '',
|
||||||
|
file: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -144,12 +146,26 @@ export default {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
storageClassOptions() {
|
encryptedStorageClasses() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||||
|
|
||||||
return storages
|
return storages.filter((s) => s.isEncrypted);
|
||||||
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) // Lvm storage is not supported.
|
},
|
||||||
|
|
||||||
|
nonEncryptedStorageClasses() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||||
|
|
||||||
|
return storages.filter((s) => !s.isEncrypted);
|
||||||
|
},
|
||||||
|
|
||||||
|
storageClassOptions() {
|
||||||
|
const storages = this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT ? this.encryptedStorageClasses : this.nonEncryptedStorageClasses;
|
||||||
|
const filteredStorages = this.value.thirdPartyStorageFeatureEnabled ? storages.filter((s) => !s.parameters?.backingImage) : storages
|
||||||
|
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) ;
|
||||||
|
|
||||||
|
return filteredStorages
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||||
|
|
||||||
@@ -167,6 +183,9 @@ export default {
|
|||||||
|
|
||||||
set(nue) {
|
set(nue) {
|
||||||
this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue;
|
this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue;
|
||||||
|
if (this.value.thirdPartyStorageFeatureEnabled) {
|
||||||
|
this.value.spec.targetStorageClassName = nue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sourceImageOptions() {
|
sourceImageOptions() {
|
||||||
@@ -239,12 +258,28 @@ export default {
|
|||||||
this.$refs.file.value = null;
|
this.$refs.file.value = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'value.spec.securityParameters.cryptoOperation'() {
|
||||||
|
if (this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT) {
|
||||||
|
this.storageClassName = this.encryptedStorageClasses[0]?.name || '';
|
||||||
|
} else { // URL / FILE / DECRYPT should use default storage class
|
||||||
|
this.storageClassName = this.defaultStorageClassName();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'storageClassName'(neu) {
|
||||||
|
const storageClass = this.storages.find((s) => s.id === neu);
|
||||||
|
|
||||||
|
if (storageClass && this.value.thirdPartyStorageFeatureEnabled) {
|
||||||
|
this.value.spec.backend = storageClass.isLonghornV1 ? 'backingimage' : 'cdi';
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async saveImage(buttonCb) {
|
async saveImage(buttonCb) {
|
||||||
this.value.spec.displayName = (this.value.spec.displayName || '').trim();
|
this.value.spec.displayName = (this.value.spec.displayName || '').trim();
|
||||||
|
|
||||||
|
if (this.isEdit) return await this.handleEditImage(buttonCb);
|
||||||
|
|
||||||
if (this.value.spec.sourceType === UPLOAD && this.isCreate) {
|
if (this.value.spec.sourceType === UPLOAD && this.isCreate) {
|
||||||
try {
|
try {
|
||||||
this.value.spec.url = '';
|
this.value.spec.url = '';
|
||||||
@@ -269,6 +304,23 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async handleEditImage(buttonCb) {
|
||||||
|
try {
|
||||||
|
const data = [{
|
||||||
|
op: 'replace', path: '/metadata/labels', value: this.value.metadata.labels
|
||||||
|
}, {
|
||||||
|
op: 'replace', path: '/metadata/annotations', value: this.value.metadata.annotations
|
||||||
|
}];
|
||||||
|
|
||||||
|
await this.value.patch(data);
|
||||||
|
buttonCb(true);
|
||||||
|
this.done();
|
||||||
|
} catch (e) {
|
||||||
|
this.errors = exceptionToErrorsArray(e);
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
setImageLabels(str) {
|
setImageLabels(str) {
|
||||||
const suffixName = str?.split('/')?.pop() || str;
|
const suffixName = str?.split('/')?.pop() || str;
|
||||||
|
|
||||||
@@ -366,6 +418,18 @@ export default {
|
|||||||
return str.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
|
return str.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultStorageClassName() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
|
||||||
|
|
||||||
|
if (!defaultStorage) {
|
||||||
|
return LONGHORN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if default sc is encrypted, use longhorn as default
|
||||||
|
return defaultStorage.isEncrypted ? LONGHORN : defaultStorage?.metadata?.name;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -530,6 +594,7 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
:pad-left="false"
|
:pad-left="false"
|
||||||
:read-allowed="false"
|
:read-allowed="false"
|
||||||
|
:value-can-be-empty="true"
|
||||||
@focusKey="focusKey"
|
@focusKey="focusKey"
|
||||||
@update:value="value.setLabels($event)"
|
@update:value="value.setLabels($event)"
|
||||||
>
|
>
|
||||||
@@ -552,7 +617,7 @@ export default {
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="off"
|
autocapitalize="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
@update:value="queueUpdate"
|
@input="queueUpdate"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</KeyValue>
|
</KeyValue>
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import { Checkbox } from '@components/Form/Checkbox';
|
|||||||
import CruResource from '@shell/components/CruResource';
|
import CruResource from '@shell/components/CruResource';
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import Labels from '@shell/components/form/Labels';
|
import { Banner } from '@components/Banner';
|
||||||
import KeyValue from '@shell/components/form/KeyValue';
|
import KeyValue from '@shell/components/form/KeyValue';
|
||||||
import NodeScheduling from '@shell/components/form/NodeScheduling';
|
import NodeScheduling from '@shell/components/form/NodeScheduling';
|
||||||
import PodAffinity from '@shell/components/form/PodAffinity';
|
import PodAffinity from '@shell/components/form/PodAffinity';
|
||||||
import UnitInput from '@shell/components/form/UnitInput';
|
import UnitInput from '@shell/components/form/UnitInput';
|
||||||
|
|
||||||
import { randomStr } from '@shell/utils/string';
|
import { randomStr } from '@shell/utils/string';
|
||||||
import { _CONFIG, _EDIT, _VIEW } from '@shell/config/query-params';
|
import { _CONFIG, _EDIT, _VIEW } from '@shell/config/query-params';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
@@ -29,7 +28,7 @@ import SSHKey from './kubevirt.io.virtualmachine/VirtualMachineSSHKey';
|
|||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditVMTemplate',
|
name: 'HarvesterEditVMTemplate',
|
||||||
|
|
||||||
emits: ['update:value'],
|
emits: ['update:templateValue'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Tab,
|
Tab,
|
||||||
@@ -47,7 +46,7 @@ export default {
|
|||||||
PodAffinity,
|
PodAffinity,
|
||||||
Reserved,
|
Reserved,
|
||||||
UnitInput,
|
UnitInput,
|
||||||
Labels,
|
Banner,
|
||||||
KeyValue,
|
KeyValue,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -205,7 +204,7 @@ export default {
|
|||||||
if (tab.name === 'advanced') {
|
if (tab.name === 'advanced') {
|
||||||
this.$refs.yamlEditor?.refresh();
|
this.$refs.yamlEditor?.refresh();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -222,11 +221,11 @@ export default {
|
|||||||
@finish="saveVMT"
|
@finish="saveVMT"
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="templateValue"
|
||||||
:mode="realTemplateMode"
|
:mode="realTemplateMode"
|
||||||
name-label="harvester.vmTemplate.nameNsDescription.name"
|
name-label="harvester.vmTemplate.nameNsDescription.name"
|
||||||
:namespaced="true"
|
:namespaced="true"
|
||||||
@update:value="$emit('update:value', $event)"
|
@update:value="$emit('update:templateValue', $event)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -321,34 +320,44 @@ export default {
|
|||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
:name="t('generic.labels')"
|
name="labels"
|
||||||
:label="t('harvester.tab.instanceLabel')"
|
:label="t('generic.labels')"
|
||||||
:weight="-5"
|
:weight="-9"
|
||||||
>
|
>
|
||||||
<Labels
|
<Banner color="info">
|
||||||
:default-container-class="'labels-and-annotations-container'"
|
<t k="harvester.virtualMachine.labels.banner" />
|
||||||
:value="value"
|
</Banner>
|
||||||
|
<KeyValue
|
||||||
|
key="labels"
|
||||||
|
:value="value.labels"
|
||||||
|
:add-label="t('labels.addLabel')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:display-side-by-side="false"
|
:read-allowed="false"
|
||||||
:show-annotations="false"
|
:value-can-be-empty="true"
|
||||||
:show-label-title="false"
|
@update:value="value.setLabels($event)"
|
||||||
>
|
/>
|
||||||
<template #labels="{toggler}">
|
|
||||||
<KeyValue
|
|
||||||
key="labels"
|
|
||||||
:value="value.instanceLabels"
|
|
||||||
:protected-keys="value.systemLabels || []"
|
|
||||||
:toggle-filter="toggler"
|
|
||||||
:add-label="t('labels.addLabel')"
|
|
||||||
:mode="mode"
|
|
||||||
:read-allowed="false"
|
|
||||||
:value-can-be-empty="true"
|
|
||||||
@input="value.setInstanceLabels($event)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Labels>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
name="instanceLabel"
|
||||||
|
:label="t('harvester.tab.instanceLabel')"
|
||||||
|
:weight="-10"
|
||||||
|
>
|
||||||
|
<Banner color="info">
|
||||||
|
<t k="harvester.virtualMachine.instanceLabels.banner" />
|
||||||
|
</Banner>
|
||||||
|
<KeyValue
|
||||||
|
key="instance-labels"
|
||||||
|
:value="value.instanceLabels"
|
||||||
|
:protected-keys="value.systemLabels || []"
|
||||||
|
:toggle-filter="toggler"
|
||||||
|
:add-label="t('labels.addLabel')"
|
||||||
|
:mode="mode"
|
||||||
|
:read-allowed="false"
|
||||||
|
:value-can-be-empty="true"
|
||||||
|
@update:value="value.setInstanceLabels($event)"
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
name="advanced"
|
name="advanced"
|
||||||
:label="t('harvester.tab.advanced')"
|
:label="t('harvester.tab.advanced')"
|
||||||
@@ -434,7 +443,7 @@ export default {
|
|||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="value.cpuPinningFeatureEnabled"
|
v-if="value.cpuPinningFeatureEnabled"
|
||||||
v-model="cpuPinning"
|
v-model:value="cpuPinning"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
tooltip-key="harvester.virtualMachine.cpuPinning.tooltip"
|
tooltip-key="harvester.virtualMachine.cpuPinning.tooltip"
|
||||||
@@ -465,11 +474,29 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.tpmPersistentStateFeatureEnabled && tpmEnabled"
|
||||||
|
v-model:value="tpmPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.tpmPersistentState')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model:value="efiEnabled"
|
v-model:value="efiEnabled"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.efiEnabled')"
|
:label="t('harvester.virtualMachine.advancedOptions.efiEnabled')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.efiPersistentStateFeatureEnabled && efiEnabled"
|
||||||
|
v-model:value="efiPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.efiPersistentState')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -478,7 +505,7 @@ export default {
|
|||||||
v-model:value="secureBoot"
|
v-model:value="secureBoot"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.secureBoot')"
|
:label="t('harvester.virtualMachine.advancedOptions.secureBoot')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { InterfaceOption, VOLUME_DATA_SOURCE_KIND } from '../config/harvester-ma
|
|||||||
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
||||||
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
||||||
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
|
||||||
|
import { GIBIBYTE } from '../utils/unit';
|
||||||
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterVolume',
|
name: 'HarvesterVolume',
|
||||||
@@ -72,7 +74,8 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
if (this.mode === _CREATE) {
|
if (this.mode === _CREATE) {
|
||||||
this.value.spec.volumeMode = 'Block';
|
// default volumeMode to Block
|
||||||
|
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
|
||||||
this.value.spec.accessModes = ['ReadWriteMany'];
|
this.value.spec.accessModes = ['ReadWriteMany'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +89,7 @@ export default {
|
|||||||
imageId,
|
imageId,
|
||||||
snapshots: [],
|
snapshots: [],
|
||||||
images: [],
|
images: [],
|
||||||
|
GIBIBYTE
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -124,6 +128,10 @@ export default {
|
|||||||
return InterfaceOption;
|
return InterfaceOption;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
volumeModeOptions() {
|
||||||
|
return Object.values(VOLUME_MODE);
|
||||||
|
},
|
||||||
|
|
||||||
imageOption() {
|
imageOption() {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
this.images
|
this.images
|
||||||
@@ -177,6 +185,25 @@ export default {
|
|||||||
return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isLonghornStorageClass() {
|
||||||
|
const selectedSC = this.storageClasses.find((sc) => sc.name === this.value?.spec?.storageClassName) || {};
|
||||||
|
|
||||||
|
return selectedSC && selectedSC.isLonghorn;
|
||||||
|
},
|
||||||
|
|
||||||
|
showVolumeMode() {
|
||||||
|
// we won't let user choose volumeMode if source = vmimage
|
||||||
|
if (!this.value.thirdPartyStorageFeatureEnabled || this.isVMImage || !!this.value?.spec?.storageClassName === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isLonghornStorageClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
storageClassOptions() {
|
storageClassOptions() {
|
||||||
return this.storageClasses.filter((s) => !s.parameters?.backingImage).map((s) => {
|
return this.storageClasses.filter((s) => !s.parameters?.backingImage).map((s) => {
|
||||||
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
|
||||||
@@ -235,7 +262,43 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'source'(neu) {
|
||||||
|
if (neu === 'url') {
|
||||||
|
this.setBlockVolumeMode();
|
||||||
|
this.deleteVolumeForVmAnnotation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'value.spec.storageClassName'() {
|
||||||
|
if (this.isLonghornStorageClass) {
|
||||||
|
this.setBlockVolumeMode();
|
||||||
|
this.deleteVolumeForVmAnnotation();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'value.spec.volumeMode'(neu) {
|
||||||
|
if (neu === VOLUME_MODE.FILE_SYSTEM) {
|
||||||
|
this.setVolumeForVmAnnotation();
|
||||||
|
} else if (neu === VOLUME_MODE.BLOCK ) {
|
||||||
|
this.deleteVolumeForVmAnnotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
setBlockVolumeMode() {
|
||||||
|
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
|
||||||
|
},
|
||||||
|
|
||||||
|
setVolumeForVmAnnotation() {
|
||||||
|
this.value.setAnnotation(HCI_ANNOTATIONS.VOLUME_FOR_VM, 'true');
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteVolumeForVmAnnotation() {
|
||||||
|
if (this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_FOR_VM]) {
|
||||||
|
delete this.value.metadata.annotations[HCI_ANNOTATIONS.VOLUME_FOR_VM];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getAccessMode() {
|
getAccessMode() {
|
||||||
if (!this.longhornV2LVMSupport) {
|
if (!this.longhornV2LVMSupport) {
|
||||||
return ['ReadWriteMany'];
|
return ['ReadWriteMany'];
|
||||||
@@ -244,10 +307,10 @@ export default {
|
|||||||
const storageClassName = this.value.spec.storageClassName;
|
const storageClassName = this.value.spec.storageClassName;
|
||||||
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
||||||
|
|
||||||
let readWriteOnce = this.value.isLvm || this.value.isLonghornV2;
|
let readWriteOnce = this.value.isLvm || (!this.value.thirdPartyStorageFeatureEnabled && this.value.isLonghornV2);
|
||||||
|
|
||||||
if (storageClass) {
|
if (storageClass) {
|
||||||
readWriteOnce = storageClass.provisioner === LVM_DRIVER || storageClass.parameters?.dataEngine === DATA_ENGINE_V2;
|
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.value.thirdPartyStorageFeatureEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
|
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
|
||||||
@@ -288,7 +351,7 @@ export default {
|
|||||||
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
|
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
|
||||||
|
|
||||||
if (imageSize) {
|
if (imageSize) {
|
||||||
this.storage = `${ Math.ceil(imageSize / 1024 / 1024 / 1024) }Gi`;
|
this.storage = `${ Math.ceil(imageSize / 1024 / 1024 / 1024) }${ GIBIBYTE }`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.update();
|
this.update();
|
||||||
@@ -357,7 +420,7 @@ export default {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-if="source === 'blank'"
|
v-if="isBlank"
|
||||||
v-model:value="value.spec.storageClassName"
|
v-model:value="value.spec.storageClassName"
|
||||||
:options="storageClassOptions"
|
:options="storageClassOptions"
|
||||||
:label="t('harvester.storage.storageClass.label')"
|
:label="t('harvester.storage.storageClass.label')"
|
||||||
@@ -367,6 +430,18 @@ export default {
|
|||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<LabeledSelect
|
||||||
|
v-if="showVolumeMode"
|
||||||
|
v-model:value="value.spec.volumeMode"
|
||||||
|
:label="t('harvester.volume.volumeMode')"
|
||||||
|
:options="volumeModeOptions"
|
||||||
|
required
|
||||||
|
:disabled="!isCreate"
|
||||||
|
:mode="mode"
|
||||||
|
class="mb-20"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
|
||||||
<UnitInput
|
<UnitInput
|
||||||
v-model:value="storage"
|
v-model:value="storage"
|
||||||
:label="t('harvester.volume.size')"
|
:label="t('harvester.volume.size')"
|
||||||
@@ -377,6 +452,7 @@ export default {
|
|||||||
:disabled="value?.isLonghornV2 && isEdit"
|
:disabled="value?.isLonghornV2 && isEdit"
|
||||||
required
|
required
|
||||||
class="mb-20"
|
class="mb-20"
|
||||||
|
:suffix="GIBIBYTE"
|
||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import UnitInput from '@shell/components/form/UnitInput';
|
import UnitInput from '@shell/components/form/UnitInput';
|
||||||
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
||||||
|
import { GIBIBYTE } from '../../utils/unit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditCpuMemory',
|
name: 'HarvesterEditCpuMemory',
|
||||||
@@ -30,6 +31,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
GIBIBYTE,
|
||||||
localCpu: this.cpu,
|
localCpu: this.cpu,
|
||||||
localMemory: this.memory
|
localMemory: this.memory
|
||||||
};
|
};
|
||||||
@@ -63,7 +65,7 @@ export default {
|
|||||||
if (String(this.localMemory).includes('Gi')) {
|
if (String(this.localMemory).includes('Gi')) {
|
||||||
memory = this.localMemory;
|
memory = this.localMemory;
|
||||||
} else {
|
} else {
|
||||||
memory = `${ this.localMemory }Gi`;
|
memory = `${ this.localMemory }${ GIBIBYTE }`;
|
||||||
}
|
}
|
||||||
if (memory.includes('null')) {
|
if (memory.includes('null')) {
|
||||||
memory = null;
|
memory = null;
|
||||||
@@ -113,6 +115,7 @@ export default {
|
|||||||
:output-modifier="true"
|
:output-modifier="true"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
required
|
required
|
||||||
|
:suffix="GIBIBYTE"
|
||||||
class="mb-20"
|
class="mb-20"
|
||||||
@update:value="change"
|
@update:value="change"
|
||||||
/>
|
/>
|
||||||
|
|||||||
+5
-1
@@ -95,7 +95,11 @@ export default {
|
|||||||
devices: {
|
devices: {
|
||||||
handler(v) {
|
handler(v) {
|
||||||
this.rows = v;
|
this.rows = v;
|
||||||
this.filterRows = this.rows;
|
if (this.parentSriov) {
|
||||||
|
this.filterRows = this.rows.filter((row) => row.labels[this.parentSriovLabel] === this.parentSriov);
|
||||||
|
} else {
|
||||||
|
this.filterRows = this.rows;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const devices = [
|
const devices = [
|
||||||
...this.otherDevices(this.value.domain.devices.hostDevices || []),
|
...this.otherDevices(this.value?.domain?.devices?.hostDevices || []),
|
||||||
...formatted,
|
...formatted,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -121,7 +121,9 @@ export default {
|
|||||||
return inUse;
|
return inUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.hostDevices.forEach((device) => {
|
const hostDevices = vm?.hostDevices || [];
|
||||||
|
|
||||||
|
hostDevices.forEach((device) => {
|
||||||
inUse[device.name] = { usedBy: [vm.metadata.name] };
|
inUse[device.name] = { usedBy: [vm.metadata.name] };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import UnitInput from '@shell/components/form/UnitInput';
|
import UnitInput from '@shell/components/form/UnitInput';
|
||||||
|
import { MEBIBYTE } from '../../utils/unit';
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterReserved',
|
name: 'HarvesterReserved',
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { memory: this.reservedMemory };
|
return { MEBIBYTE, memory: this.reservedMemory };
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -47,6 +47,7 @@ export default {
|
|||||||
:input-exponent="2"
|
:input-exponent="2"
|
||||||
:increment="1024"
|
:increment="1024"
|
||||||
:output-modifier="true"
|
:output-modifier="true"
|
||||||
|
:suffix="MEBIBYTE"
|
||||||
@update:value="change"
|
@update:value="change"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
|||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||||
|
|
||||||
import { clone } from '@shell/utils/object';
|
|
||||||
import { _VIEW } from '@shell/config/query-params';
|
import { _VIEW } from '@shell/config/query-params';
|
||||||
|
|
||||||
import { NAMESPACE } from '@shell/config/types';
|
import { NAMESPACE } from '@shell/config/types';
|
||||||
@@ -126,10 +125,9 @@ export default {
|
|||||||
this.checkedSsh = neu;
|
this.checkedSsh = neu;
|
||||||
},
|
},
|
||||||
|
|
||||||
checkedSsh(val, old) {
|
checkedSsh(val) {
|
||||||
if ( val.includes(_NEW)) {
|
// if click on Create a New...
|
||||||
this['checkedSsh'] = old;
|
if (val.includes(_NEW)) {
|
||||||
this.update();
|
|
||||||
this.show();
|
this.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,6 +173,7 @@ export default {
|
|||||||
|
|
||||||
if (res.id) {
|
if (res.id) {
|
||||||
this.checkedSsh.push(`${ this.namespace }/${ this.sshName }`);
|
this.checkedSsh.push(`${ this.namespace }/${ this.sshName }`);
|
||||||
|
this.update();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -232,7 +231,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.$emit('update:sshKey', clone(this.checkedSsh));
|
const sshKeys = this.checkedSsh.filter((key) => key !== _NEW);
|
||||||
|
|
||||||
|
this.$emit('update:sshKey', sshKeys);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -243,7 +244,7 @@ export default {
|
|||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model:value="checkedSsh"
|
v-model:value="checkedSsh"
|
||||||
:label="t('harvester.virtualMachine.input.sshKey')"
|
:label="t('harvester.virtualMachine.input.sshKey')"
|
||||||
:taggable="true"
|
:taggable="!disabled"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:searchable="searchable"
|
:searchable="searchable"
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const devices = [
|
const devices = [
|
||||||
...this.otherDevices(this.value.domain.devices.hostDevices || []),
|
...this.otherDevices(this.value?.domain?.devices?.hostDevices || []),
|
||||||
...formatted,
|
...formatted,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -121,7 +121,9 @@ export default {
|
|||||||
return inUse;
|
return inUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.hostDevices.forEach((device) => {
|
const hostDevices = vm?.hostDevices || [];
|
||||||
|
|
||||||
|
hostDevices.forEach((device) => {
|
||||||
inUse[device.name] = { usedBy: [vm.metadata.name] };
|
inUse[device.name] = { usedBy: [vm.metadata.name] };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -46,10 +46,13 @@ export default {
|
|||||||
this[key] = res[key];
|
this[key] = res[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
uniq([
|
const vGpus = this.vm.isOff ? [
|
||||||
...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name),
|
...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name),
|
||||||
|
] : [
|
||||||
...Object.values(this.vm?.provisionedVGpus).reduce((acc, gpus) => [...acc, ...gpus], [])
|
...Object.values(this.vm?.provisionedVGpus).reduce((acc, gpus) => [...acc, ...gpus], [])
|
||||||
]).forEach((name) => {
|
];
|
||||||
|
|
||||||
|
uniq(vGpus).forEach((name) => {
|
||||||
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
||||||
this.selectedDevices.push(name);
|
this.selectedDevices.push(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Sortable } from 'sortablejs-vue3';
|
import { VueDraggableNext } from 'vue-draggable-next';
|
||||||
import InfoBox from '@shell/components/InfoBox';
|
import InfoBox from '@shell/components/InfoBox';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import BadgeStateFormatter from '@shell/components/formatter/BadgeStateFormatter';
|
import BadgeStateFormatter from '@shell/components/formatter/BadgeStateFormatter';
|
||||||
@@ -7,8 +7,7 @@ import UnitInput from '@shell/components/form/UnitInput';
|
|||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||||
|
import { PVC, STORAGE_CLASS } from '@shell/config/types';
|
||||||
import { PVC } from '@shell/config/types';
|
|
||||||
import { clone } from '@shell/utils/object';
|
import { clone } from '@shell/utils/object';
|
||||||
import { ucFirst, randomStr } from '@shell/utils/string';
|
import { ucFirst, randomStr } from '@shell/utils/string';
|
||||||
import { removeObject } from '@shell/utils/array';
|
import { removeObject } from '@shell/utils/array';
|
||||||
@@ -17,12 +16,13 @@ import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
|
|||||||
import { SOURCE_TYPE } from '../../../config/harvester-map';
|
import { SOURCE_TYPE } from '../../../config/harvester-map';
|
||||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
|
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
|
||||||
import { HCI } from '../../../types';
|
import { HCI } from '../../../types';
|
||||||
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['update:value'],
|
emits: ['update:value'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Banner, BadgeStateFormatter, Sortable, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
|
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -62,7 +62,7 @@ export default {
|
|||||||
|
|
||||||
customVolumeMode: {
|
customVolumeMode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Block'
|
default: VOLUME_MODE.Block
|
||||||
},
|
},
|
||||||
|
|
||||||
customAccessMode: {
|
customAccessMode: {
|
||||||
@@ -116,6 +116,12 @@ export default {
|
|||||||
return this.mode === _CREATE;
|
return this.mode === _CREATE;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
defaultStorageClass() {
|
||||||
|
const defaultStorage = this.$store.getters['harvester/all'](STORAGE_CLASS).find((sc) => sc.isDefault);
|
||||||
|
|
||||||
|
return defaultStorage;
|
||||||
|
},
|
||||||
|
|
||||||
showVolumeTip() {
|
showVolumeTip() {
|
||||||
const imageName = this.getImageDisplayName(this.rows[0]?.image);
|
const imageName = this.getImageDisplayName(this.rows[0]?.image);
|
||||||
|
|
||||||
@@ -272,129 +278,130 @@ export default {
|
|||||||
color="info"
|
color="info"
|
||||||
label-key="harvester.virtualMachine.volume.dragTip"
|
label-key="harvester.virtualMachine.volume.dragTip"
|
||||||
/>
|
/>
|
||||||
<Sortable
|
<VueDraggableNext
|
||||||
:list="rows"
|
:list="rows"
|
||||||
:options="{disabled: isView}"
|
:disabled="isView"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
@end="update"
|
@end="update"
|
||||||
>
|
>
|
||||||
<template #item="{element: volume, index: i}">
|
<div
|
||||||
<div :key="volume.name">
|
v-for="(volume, i) in rows"
|
||||||
<InfoBox class="box">
|
:key="volume.id"
|
||||||
<button
|
>
|
||||||
|
<InfoBox class="box">
|
||||||
|
<button
|
||||||
|
v-if="!isView"
|
||||||
|
type="button"
|
||||||
|
class="role-link btn btn-sm remove"
|
||||||
|
@click="removeVolume(volume)"
|
||||||
|
>
|
||||||
|
<i class="icon icon-x" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="volume.hotpluggable && isView"
|
||||||
|
type="button"
|
||||||
|
class="role-link btn remove"
|
||||||
|
@click="unplugVolume(volume)"
|
||||||
|
>
|
||||||
|
{{ t('harvester.virtualMachine.unplug.detachVolume') }}
|
||||||
|
</button>
|
||||||
|
<h3>
|
||||||
|
<span
|
||||||
|
v-if="volume.to && isVirtualType"
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
<router-link :to="volume.to">
|
||||||
|
{{ t('harvester.virtualMachine.volume.edit') }} {{ headerFor(volume.source) }}
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<BadgeStateFormatter
|
||||||
|
v-if="volume.pvc"
|
||||||
|
class="ml-10 state"
|
||||||
|
:arbitrary="true"
|
||||||
|
:row="volume.pvc"
|
||||||
|
:value="volume.pvc.state"
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
v-if="dev && !!volume.pvc && !!volume.pvc.resourceExternalLink"
|
||||||
|
v-clean-tooltip="t(volume.pvc.resourceExternalLink.tipsKey || 'generic.resourceExternalLinkTips')"
|
||||||
|
class="ml-5 resource-external"
|
||||||
|
rel="nofollow noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
:href="volume.pvc.resourceExternalLink.url"
|
||||||
|
>
|
||||||
|
<i class="icon icon-external-link" />
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else>
|
||||||
|
{{ headerFor(volume.source, !!volume?.volumeBackups) }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<component
|
||||||
|
:is="componentFor(volume.source)"
|
||||||
|
:value="rows[i]"
|
||||||
|
:rows="rows"
|
||||||
|
:namespace="namespace"
|
||||||
|
:is-create="isCreate"
|
||||||
|
:is-edit="isEdit"
|
||||||
|
:is-view="isView"
|
||||||
|
:is-virtual-type="isVirtualType"
|
||||||
|
:mode="mode"
|
||||||
|
:idx="i"
|
||||||
|
:validate-required="validateRequired"
|
||||||
|
@update="update"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bootOrder">
|
||||||
|
<div
|
||||||
v-if="!isView"
|
v-if="!isView"
|
||||||
type="button"
|
class="mr-15"
|
||||||
class="role-link btn btn-sm remove"
|
|
||||||
@click="removeVolume(volume)"
|
|
||||||
>
|
>
|
||||||
<i class="icon icon-x" />
|
<button
|
||||||
</button>
|
:disabled="i === 0"
|
||||||
<button
|
class="btn btn-sm role-primary"
|
||||||
v-if="volume.hotpluggable && isView"
|
@click.prevent="changeSort(i, false)"
|
||||||
type="button"
|
|
||||||
class="role-link btn remove"
|
|
||||||
@click="unplugVolume(volume)"
|
|
||||||
>
|
|
||||||
{{ t('harvester.virtualMachine.unplug.detachVolume') }}
|
|
||||||
</button>
|
|
||||||
<h3>
|
|
||||||
<span
|
|
||||||
v-if="volume.to && isVirtualType"
|
|
||||||
class="title"
|
|
||||||
>
|
>
|
||||||
<router-link :to="volume.to">
|
<i class="icon icon-lg icon-chevron-up"></i>
|
||||||
{{ t('harvester.virtualMachine.volume.edit') }} {{ headerFor(volume.source) }}
|
</button>
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<BadgeStateFormatter
|
<button
|
||||||
v-if="volume.pvc"
|
:disabled="i === rows.length -1"
|
||||||
class="ml-10 state"
|
class="btn btn-sm role-primary"
|
||||||
:arbitrary="true"
|
@click.prevent="changeSort(i, true)"
|
||||||
:row="volume.pvc"
|
|
||||||
:value="volume.pvc.state"
|
|
||||||
/>
|
|
||||||
<a
|
|
||||||
v-if="dev && !!volume.pvc && !!volume.pvc.resourceExternalLink"
|
|
||||||
v-clean-tooltip="t(volume.pvc.resourceExternalLink.tipsKey || 'generic.resourceExternalLinkTips')"
|
|
||||||
class="ml-5 resource-external"
|
|
||||||
rel="nofollow noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
:href="volume.pvc.resourceExternalLink.url"
|
|
||||||
>
|
|
||||||
<i class="icon icon-external-link" />
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else>
|
|
||||||
{{ headerFor(volume.source, !!volume?.volumeBackups) }}
|
|
||||||
</span>
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
<component
|
|
||||||
:is="componentFor(volume.source)"
|
|
||||||
:value="rows[i]"
|
|
||||||
:rows="rows"
|
|
||||||
:namespace="namespace"
|
|
||||||
:is-create="isCreate"
|
|
||||||
:is-edit="isEdit"
|
|
||||||
:is-view="isView"
|
|
||||||
:is-virtual-type="isVirtualType"
|
|
||||||
:mode="mode"
|
|
||||||
:idx="i"
|
|
||||||
:validate-required="validateRequired"
|
|
||||||
@update="update"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bootOrder">
|
|
||||||
<div
|
|
||||||
v-if="!isView"
|
|
||||||
class="mr-15"
|
|
||||||
>
|
>
|
||||||
<button
|
<i class="icon icon-lg icon-chevron-down"></i>
|
||||||
:disabled="i === 0"
|
</button>
|
||||||
class="btn btn-sm role-primary"
|
|
||||||
@click.prevent="changeSort(i, false)"
|
|
||||||
>
|
|
||||||
<i class="icon icon-lg icon-chevron-up"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
:disabled="i === rows.length -1"
|
|
||||||
class="btn btn-sm role-primary"
|
|
||||||
@click.prevent="changeSort(i, true)"
|
|
||||||
>
|
|
||||||
<i class="icon icon-lg icon-chevron-down"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-muted">
|
|
||||||
bootOrder: {{ i + 1 }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-15">
|
<div class="text-muted">
|
||||||
<Banner
|
bootOrder: {{ i + 1 }}
|
||||||
v-if="volume.volumeStatus && !isCreate"
|
|
||||||
class="volume-status"
|
|
||||||
color="warning"
|
|
||||||
:label="ucFirst(volume.volumeStatus)"
|
|
||||||
/>
|
|
||||||
<Banner
|
|
||||||
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
|
|
||||||
color="error"
|
|
||||||
:label="ucFirst(value.volumeBackups.error.message)"
|
|
||||||
/>
|
|
||||||
<Banner
|
|
||||||
v-if="isLonghornV2(volume) && !isView"
|
|
||||||
color="warning"
|
|
||||||
:label="t('harvester.volume.longhorn.disableResize')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</InfoBox>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
<div class="mt-15">
|
||||||
</Sortable>
|
<Banner
|
||||||
|
v-if="volume.volumeStatus && !isCreate"
|
||||||
|
class="volume-status"
|
||||||
|
color="warning"
|
||||||
|
:label="ucFirst(volume.volumeStatus)"
|
||||||
|
/>
|
||||||
|
<Banner
|
||||||
|
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
|
||||||
|
color="error"
|
||||||
|
:label="ucFirst(value.volumeBackups.error.message)"
|
||||||
|
/>
|
||||||
|
<Banner
|
||||||
|
v-if="isLonghornV2(volume) && !isView"
|
||||||
|
color="warning"
|
||||||
|
:label="t('harvester.volume.longhorn.disableResize')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</InfoBox>
|
||||||
|
</div>
|
||||||
|
</VueDraggableNext>
|
||||||
<Banner
|
<Banner
|
||||||
v-if="showVolumeTip"
|
v-if="showVolumeTip"
|
||||||
color="warning"
|
color="warning"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { _CREATE } from '@shell/config/query-params';
|
|||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
import { HCI } from '../../../../types';
|
import { HCI } from '../../../../types';
|
||||||
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
|
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
|
||||||
|
import { GIBIBYTE } from '../../../../utils/unit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditExisting',
|
name: 'HarvesterEditExisting',
|
||||||
@@ -59,6 +60,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
GIBIBYTE,
|
||||||
VOLUME_TYPE,
|
VOLUME_TYPE,
|
||||||
InterfaceOption,
|
InterfaceOption,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -109,6 +111,10 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pvc.isGoldenImageVolume) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) {
|
if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) {
|
||||||
isBeingUsed = false;
|
isBeingUsed = false;
|
||||||
} else if (pvc.attachVM) {
|
} else if (pvc.attachVM) {
|
||||||
@@ -136,10 +142,12 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update this.value with existing volume spec, then update to upstream component
|
||||||
this.value.accessModes = pvcResource.spec.accessModes[0];
|
this.value.accessModes = pvcResource.spec.accessModes[0];
|
||||||
this.value.size = pvcResource.spec.resources.requests.storage;
|
this.value.size = pvcResource.spec.resources.requests.storage;
|
||||||
this.value.storageClassName = pvcResource.spec.storageClassName;
|
this.value.storageClassName = pvcResource.spec.storageClassName;
|
||||||
this.value.volumeMode = pvcResource.spec.volumeMode;
|
this.value.volumeMode = pvcResource.spec.volumeMode;
|
||||||
|
this.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
'value.type'(neu) {
|
'value.type'(neu) {
|
||||||
@@ -250,6 +258,7 @@ export default {
|
|||||||
:label="t('harvester.fields.size')"
|
:label="t('harvester.fields.size')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
|
:suffix="GIBIBYTE"
|
||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
</InputOrDisplay>
|
</InputOrDisplay>
|
||||||
|
|||||||
+59
-4
@@ -12,6 +12,7 @@ import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
|
|||||||
import { _VIEW } from '@shell/config/query-params';
|
import { _VIEW } from '@shell/config/query-params';
|
||||||
import LabelValue from '@shell/components/LabelValue';
|
import LabelValue from '@shell/components/LabelValue';
|
||||||
import { ucFirst } from '@shell/utils/string';
|
import { ucFirst } from '@shell/utils/string';
|
||||||
|
import { GIBIBYTE } from '../../../../utils/unit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditVMImage',
|
name: 'HarvesterEditVMImage',
|
||||||
@@ -67,6 +68,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
GIBIBYTE,
|
||||||
VOLUME_TYPE,
|
VOLUME_TYPE,
|
||||||
InterfaceOption,
|
InterfaceOption,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -90,7 +92,7 @@ export default {
|
|||||||
imagesOption() {
|
imagesOption() {
|
||||||
return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => {
|
return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => {
|
||||||
return {
|
return {
|
||||||
label: `${ I.metadata.namespace }/${ I.spec.displayName }`,
|
label: this.imageOptionLabel(I),
|
||||||
value: I.id
|
value: I.id
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -116,8 +118,46 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
thirdPartyStorageEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
|
},
|
||||||
|
|
||||||
isLonghornV2() {
|
isLonghornV2() {
|
||||||
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
|
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedImage() {
|
||||||
|
return this.$store.getters['harvester/all'](HCI.IMAGE)?.find( (I) => this.value.image === I.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
imageVirtualSize() {
|
||||||
|
if (this.selectedImage?.virtualSize) {
|
||||||
|
return this.selectedImage.virtualSize.replace(' ', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '0';
|
||||||
|
},
|
||||||
|
|
||||||
|
diskSize() {
|
||||||
|
const size = this.value?.size || '0';
|
||||||
|
|
||||||
|
return size;
|
||||||
|
},
|
||||||
|
|
||||||
|
imageVirtualSizeInByte() {
|
||||||
|
return Math.max(this.selectedImage?.status?.size, this.selectedImage?.status?.virtualSize);
|
||||||
|
},
|
||||||
|
|
||||||
|
diskSizeInByte() {
|
||||||
|
return parseSi(this.value?.size || '0');
|
||||||
|
},
|
||||||
|
|
||||||
|
showDiskTooSmallError() {
|
||||||
|
if (!this.thirdPartyStorageEnabled ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.imageVirtualSizeInByte > this.diskSizeInByte;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -149,7 +189,7 @@ export default {
|
|||||||
minExponent: 3,
|
minExponent: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.value.size = `${ formatSize }Gi`;
|
this.value.size = `${ formatSize }${ GIBIBYTE }`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
@@ -158,7 +198,17 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
imageOptionLabel(image) {
|
||||||
|
let label = `${ image.metadata.namespace }/${ image.spec.displayName }`;
|
||||||
|
|
||||||
|
if (this.thirdPartyStorageEnabled) {
|
||||||
|
label += ` (${ image.imageStorageClass } / ${ image.virtualSize })`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
},
|
||||||
update() {
|
update() {
|
||||||
|
this.value.hasDiskError = this.showDiskTooSmallError;
|
||||||
this.$emit('update');
|
this.$emit('update');
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -181,7 +231,7 @@ export default {
|
|||||||
if (!isIsoImage) {
|
if (!isIsoImage) {
|
||||||
imageSizeGiB = Math.max(imageSizeGiB, 10);
|
imageSizeGiB = Math.max(imageSizeGiB, 10);
|
||||||
}
|
}
|
||||||
this.value['size'] = `${ imageSizeGiB }Gi`;
|
this.value['size'] = `${ imageSizeGiB }${ GIBIBYTE }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update();
|
this.update();
|
||||||
@@ -288,7 +338,7 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
:required="validateRequired"
|
:required="validateRequired"
|
||||||
:disable="isLonghornV2"
|
:disable="isLonghornV2"
|
||||||
suffix="GiB"
|
:suffix="GIBIBYTE"
|
||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
</InputOrDisplay>
|
</InputOrDisplay>
|
||||||
@@ -341,5 +391,10 @@ export default {
|
|||||||
class="mb-20"
|
class="mb-20"
|
||||||
:label="value.volumeBackups.error.message"
|
:label="value.volumeBackups.error.message"
|
||||||
/>
|
/>
|
||||||
|
<Banner
|
||||||
|
v-if="!isView && showDiskTooSmallError"
|
||||||
|
color="error"
|
||||||
|
:label="t('harvester.virtualMachine.volume.vmImageVolumeTip', {diskSize: diskSize, imageVirtualSize: imageVirtualSize})"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import LabelValue from '@shell/components/LabelValue';
|
|||||||
import { ucFirst } from '@shell/utils/string';
|
import { ucFirst } from '@shell/utils/string';
|
||||||
import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storageclass';
|
import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storageclass';
|
||||||
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
|
||||||
|
import { GIBIBYTE } from '../../../../utils/unit';
|
||||||
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditVolume',
|
name: 'HarvesterEditVolume',
|
||||||
@@ -58,6 +60,7 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
GIBIBYTE,
|
||||||
VOLUME_TYPE,
|
VOLUME_TYPE,
|
||||||
InterfaceOption,
|
InterfaceOption,
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -69,6 +72,10 @@ export default {
|
|||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('longhornV2LVMSupport');
|
return this.$store.getters['harvester-common/getFeatureEnabled']('longhornV2LVMSupport');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
thirdPartyStorageClassEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
|
},
|
||||||
|
|
||||||
encryptionValue() {
|
encryptionValue() {
|
||||||
return ucFirst(String(this.value.isEncrypted));
|
return ucFirst(String(this.value.isEncrypted));
|
||||||
},
|
},
|
||||||
@@ -89,6 +96,18 @@ export default {
|
|||||||
return allPVCs.find((P) => P.id === `${ this.namespace }/${ this.value.volumeName }`);
|
return allPVCs.find((P) => P.id === `${ this.namespace }/${ this.value.volumeName }`);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showVolumeMode() {
|
||||||
|
if (!this.thirdPartyStorageClassEnabled || !!this.value?.storageClassName === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isLonghornStorageClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
isDisabled() {
|
isDisabled() {
|
||||||
return !this.value.newCreateId && this.isEdit && this.isVirtualType;
|
return !this.value.newCreateId && this.isEdit && this.isVirtualType;
|
||||||
},
|
},
|
||||||
@@ -110,7 +129,17 @@ export default {
|
|||||||
|
|
||||||
isLonghornV2() {
|
isLonghornV2() {
|
||||||
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
|
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
isLonghornStorageClass() {
|
||||||
|
const selectedSC = this.storageClasses.find((sc) => sc.name === this.value?.storageClassName) || {};
|
||||||
|
|
||||||
|
return selectedSC && selectedSC.isLonghorn;
|
||||||
|
},
|
||||||
|
|
||||||
|
volumeModeOptions() {
|
||||||
|
return Object.values(VOLUME_MODE);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -118,6 +147,8 @@ export default {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
handler(neu) {
|
handler(neu) {
|
||||||
this.value.accessMode = this.getAccessMode(neu);
|
this.value.accessMode = this.getAccessMode(neu);
|
||||||
|
this.value.volumeMode = this.getVolumeMode(neu, this.value.volumeMode);
|
||||||
|
this.update();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -140,7 +171,7 @@ export default {
|
|||||||
minExponent: 3,
|
minExponent: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.value.size = `${ formatSize }Gi`;
|
this.value.size = `${ formatSize }${ GIBIBYTE }`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
@@ -149,6 +180,20 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
getVolumeMode(storageClassName, originalVolumeMode) {
|
||||||
|
if (!this.thirdPartyStorageClassEnabled) {
|
||||||
|
return VOLUME_MODE.BLOCK;
|
||||||
|
}
|
||||||
|
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
||||||
|
|
||||||
|
// longhorn v1, v2 use block volumeMode
|
||||||
|
if (storageClass && storageClass.isLonghorn) {
|
||||||
|
return VOLUME_MODE.BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalVolumeMode;
|
||||||
|
},
|
||||||
|
|
||||||
getAccessMode(storageClassName) {
|
getAccessMode(storageClassName) {
|
||||||
if (!this.longhornV2LVMSupport) {
|
if (!this.longhornV2LVMSupport) {
|
||||||
return 'ReadWriteMany';
|
return 'ReadWriteMany';
|
||||||
@@ -156,10 +201,10 @@ export default {
|
|||||||
|
|
||||||
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
||||||
|
|
||||||
let readWriteOnce = this.value.pvc?.isLvm || this.value.pvc?.isLonghornV2;
|
let readWriteOnce = this.value.pvc?.isLvm || (!this.thirdPartyStorageClassEnabled && this.value.pvc?.isLonghornV2);
|
||||||
|
|
||||||
if (storageClass) {
|
if (storageClass) {
|
||||||
readWriteOnce = storageClass.provisioner === LVM_DRIVER || storageClass.parameters?.dataEngine === DATA_ENGINE_V2;
|
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.thirdPartyStorageClassEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readWriteOnce ? 'ReadWriteOnce' : 'ReadWriteMany';
|
return readWriteOnce ? 'ReadWriteOnce' : 'ReadWriteMany';
|
||||||
@@ -258,6 +303,7 @@ export default {
|
|||||||
:required="validateRequired"
|
:required="validateRequired"
|
||||||
:label="t('harvester.fields.size')"
|
:label="t('harvester.fields.size')"
|
||||||
:disabled="isLonghornV2"
|
:disabled="isLonghornV2"
|
||||||
|
:suffix="GIBIBYTE"
|
||||||
@update:value="update"
|
@update:value="update"
|
||||||
/>
|
/>
|
||||||
</InputOrDisplay>
|
</InputOrDisplay>
|
||||||
@@ -283,6 +329,29 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</InputOrDisplay>
|
</InputOrDisplay>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showVolumeMode"
|
||||||
|
data-testid="input-volume-mode"
|
||||||
|
class="col span-6"
|
||||||
|
>
|
||||||
|
<InputOrDisplay
|
||||||
|
:name="t('harvester.volume.volumeMode')"
|
||||||
|
:value="value.volumeMode"
|
||||||
|
:mode="mode"
|
||||||
|
>
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="value.volumeMode"
|
||||||
|
:label="t('harvester.volume.volumeMode')"
|
||||||
|
:mode="mode"
|
||||||
|
:options="volumeModeOptions"
|
||||||
|
:disabled="isEdit"
|
||||||
|
required
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
</InputOrDisplay>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-20">
|
||||||
<div
|
<div
|
||||||
v-if="value.volumeEncryptionFeatureEnabled && isView"
|
v-if="value.volumeEncryptionFeatureEnabled && isView"
|
||||||
class="col span-6"
|
class="col span-6"
|
||||||
@@ -292,11 +361,9 @@ export default {
|
|||||||
:value="encryptionValue"
|
:value="encryptionValue"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div
|
<div
|
||||||
v-if="value.volumeBackups && isView"
|
v-if="value.volumeBackups && isView"
|
||||||
class="col span-3"
|
class="col span-6"
|
||||||
>
|
>
|
||||||
<LabelValue
|
<LabelValue
|
||||||
:name="t('harvester.virtualMachine.volume.readyToUse')"
|
:name="t('harvester.virtualMachine.volume.readyToUse')"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
import Tabbed from '@shell/components/Tabbed';
|
||||||
|
import { clone, set } from '@shell/utils/object';
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
import Tab from '@shell/components/Tabbed/Tab';
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
import CruResource from '@shell/components/CruResource';
|
import CruResource from '@shell/components/CruResource';
|
||||||
@@ -10,16 +11,12 @@ import { LabeledInput } from '@components/Form/LabeledInput';
|
|||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
||||||
import UnitInput from '@shell/components/form/UnitInput';
|
import UnitInput from '@shell/components/form/UnitInput';
|
||||||
import Labels from '@shell/components/form/Labels';
|
|
||||||
|
|
||||||
import NodeScheduling from '@shell/components/form/NodeScheduling';
|
import NodeScheduling from '@shell/components/form/NodeScheduling';
|
||||||
import PodAffinity from '@shell/components/form/PodAffinity';
|
import PodAffinity from '@shell/components/form/PodAffinity';
|
||||||
import VGpuDevices from './VirtualMachineVGpuDevices/index';
|
import VGpuDevices from './VirtualMachineVGpuDevices/index';
|
||||||
import UsbDevices from './VirtualMachineUSBDevices/index';
|
import UsbDevices from './VirtualMachineUSBDevices/index';
|
||||||
import KeyValue from '@shell/components/form/KeyValue';
|
import KeyValue from '@shell/components/form/KeyValue';
|
||||||
|
|
||||||
import { clear } from '@shell/utils/array';
|
import { clear } from '@shell/utils/array';
|
||||||
import { clone } from '@shell/utils/object';
|
|
||||||
import { saferDump } from '@shell/utils/create-yaml';
|
import { saferDump } from '@shell/utils/create-yaml';
|
||||||
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';
|
||||||
@@ -65,7 +62,6 @@ export default {
|
|||||||
PodAffinity,
|
PodAffinity,
|
||||||
AccessCredentials,
|
AccessCredentials,
|
||||||
Reserved,
|
Reserved,
|
||||||
Labels,
|
|
||||||
PciDevices,
|
PciDevices,
|
||||||
RestartVMDialog,
|
RestartVMDialog,
|
||||||
UnitInput,
|
UnitInput,
|
||||||
@@ -195,7 +191,12 @@ export default {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
isValidationPassed() {
|
||||||
|
// check if any disk hasDiskError is true
|
||||||
|
const hasError = this.diskRows.some((disk) => disk.hasDiskError === true);
|
||||||
|
|
||||||
|
return !hasError;
|
||||||
|
},
|
||||||
showCpuPinningBanner() {
|
showCpuPinningBanner() {
|
||||||
if (!this.value.cpuPinningFeatureEnabled) {
|
if (!this.value.cpuPinningFeatureEnabled) {
|
||||||
return false;
|
return false;
|
||||||
@@ -210,7 +211,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
},
|
||||||
|
usbPassthroughEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -407,13 +411,26 @@ export default {
|
|||||||
}
|
}
|
||||||
const cloneDeepNewVM = clone(this.value);
|
const cloneDeepNewVM = clone(this.value);
|
||||||
|
|
||||||
|
// new VM
|
||||||
delete cloneDeepNewVM?.metadata;
|
delete cloneDeepNewVM?.metadata;
|
||||||
|
delete cloneDeepNewVM?.__clone;
|
||||||
|
|
||||||
|
// old VM
|
||||||
delete this.cloneVM?.metadata;
|
delete this.cloneVM?.metadata;
|
||||||
delete this.cloneVM?.__clone;
|
delete this.cloneVM?.__clone;
|
||||||
|
|
||||||
|
// add empty hostDevices to old VM as CRD does not have it.
|
||||||
|
const devicesObj = this.cloneVM?.spec?.template?.spec?.domain?.devices;
|
||||||
|
|
||||||
|
if (devicesObj && devicesObj.hostDevices === undefined) {
|
||||||
|
set(devicesObj, 'hostDevices', []);
|
||||||
|
}
|
||||||
|
|
||||||
const oldVM = JSON.parse(JSON.stringify(this.cloneVM));
|
const oldVM = JSON.parse(JSON.stringify(this.cloneVM));
|
||||||
const newVM = JSON.parse(JSON.stringify(cloneDeepNewVM));
|
const newVM = JSON.parse(JSON.stringify(cloneDeepNewVM));
|
||||||
|
|
||||||
|
// we won't show restart dialog in yaml page as we don't have a way to detect change in yaml editor.
|
||||||
|
// only check VM is changed in form page.
|
||||||
if (isEqual(oldVM, newVM)) {
|
if (isEqual(oldVM, newVM)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -422,7 +439,11 @@ export default {
|
|||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.restartDialog.resolve = resolve;
|
if (this?.$refs?.restartDialog) {
|
||||||
|
this.$refs.restartDialog.resolve = resolve;
|
||||||
|
} else {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -472,6 +493,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
generateYaml() {
|
generateYaml() {
|
||||||
|
this.showYaml = this?.$refs?.vmCruResource?.showYaml || false;
|
||||||
this.parseVM();
|
this.parseVM();
|
||||||
const out = saferDump(this.value);
|
const out = saferDump(this.value);
|
||||||
|
|
||||||
@@ -485,10 +507,12 @@ export default {
|
|||||||
<CruResource
|
<CruResource
|
||||||
v-if="spec"
|
v-if="spec"
|
||||||
id="vm"
|
id="vm"
|
||||||
|
ref="vmCruResource"
|
||||||
:done-route="doneRoute"
|
:done-route="doneRoute"
|
||||||
:resource="value"
|
:resource="value"
|
||||||
:cancel-event="true"
|
:cancel-event="true"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
:validation-passed="isValidationPassed"
|
||||||
:can-yaml="isSingle ? true : false"
|
:can-yaml="isSingle ? true : false"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
:generate-yaml="generateYaml"
|
:generate-yaml="generateYaml"
|
||||||
@@ -579,7 +603,7 @@ export default {
|
|||||||
:create-namespace="true"
|
:create-namespace="true"
|
||||||
:namespace="value.metadata.namespace"
|
:namespace="value.metadata.namespace"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:disabled="isWindows"
|
:disabled="isWindows || isEdit"
|
||||||
@update:sshKey="updateSSHKey"
|
@update:sshKey="updateSSHKey"
|
||||||
@register-after-hook="registerAfterHook"
|
@register-after-hook="registerAfterHook"
|
||||||
/>
|
/>
|
||||||
@@ -671,7 +695,7 @@ export default {
|
|||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
v-if="enabledPCI"
|
v-if="enabledPCI && usbPassthroughEnabled"
|
||||||
:label="t('harvester.tab.usbDevices')"
|
:label="t('harvester.tab.usbDevices')"
|
||||||
name="usbDevices"
|
name="usbDevices"
|
||||||
:weight="-7"
|
:weight="-7"
|
||||||
@@ -697,39 +721,50 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
|
<Tab
|
||||||
|
name="labels"
|
||||||
|
:label="t('generic.labels')"
|
||||||
|
:weight="-9"
|
||||||
|
>
|
||||||
|
<Banner color="info">
|
||||||
|
<t k="harvester.virtualMachine.labels.banner" />
|
||||||
|
</Banner>
|
||||||
|
<KeyValue
|
||||||
|
key="labels"
|
||||||
|
:value="value.labels"
|
||||||
|
:add-label="t('labels.addLabel')"
|
||||||
|
:mode="mode"
|
||||||
|
:read-allowed="false"
|
||||||
|
:value-can-be-empty="true"
|
||||||
|
@update:value="value.setLabels($event)"
|
||||||
|
/>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
name="instanceLabel"
|
name="instanceLabel"
|
||||||
:label="t('harvester.tab.instanceLabel')"
|
:label="t('harvester.tab.instanceLabel')"
|
||||||
:weight="-8"
|
:weight="-10"
|
||||||
>
|
>
|
||||||
<Labels
|
<Banner color="info">
|
||||||
:default-container-class="'labels-and-annotations-container'"
|
<t k="harvester.virtualMachine.instanceLabels.banner" />
|
||||||
:value="value"
|
</Banner>
|
||||||
|
<KeyValue
|
||||||
|
key="instance-labels"
|
||||||
|
:value="value.instanceLabels"
|
||||||
|
:protected-keys="value.systemLabels || []"
|
||||||
|
:toggle-filter="toggler"
|
||||||
|
:add-label="t('labels.addLabel')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:display-side-by-side="false"
|
:read-allowed="false"
|
||||||
:show-annotations="false"
|
:value-can-be-empty="true"
|
||||||
:show-label-title="false"
|
@update:value="value.setInstanceLabels($event)"
|
||||||
>
|
/>
|
||||||
<template #labels="{toggler}">
|
|
||||||
<KeyValue
|
|
||||||
key="labels"
|
|
||||||
:value="value.instanceLabels"
|
|
||||||
:protected-keys="value.systemLabels || []"
|
|
||||||
:toggle-filter="toggler"
|
|
||||||
:add-label="t('labels.addLabel')"
|
|
||||||
:mode="mode"
|
|
||||||
:read-allowed="false"
|
|
||||||
:value-can-be-empty="true"
|
|
||||||
@update:value="value.setInstanceLabels($event)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Labels>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
name="advanced"
|
name="advanced"
|
||||||
:label="t('harvester.tab.advanced')"
|
:label="t('harvester.tab.advanced')"
|
||||||
:weight="-9"
|
:weight="-11"
|
||||||
>
|
>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
@@ -873,11 +908,29 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.tpmPersistentStateFeatureEnabled && tpmEnabled"
|
||||||
|
v-model:value="tpmPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.tpmPersistentState')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model:value="efiEnabled"
|
v-model:value="efiEnabled"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.efiEnabled')"
|
:label="t('harvester.virtualMachine.advancedOptions.efiEnabled')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.efiPersistentStateFeatureEnabled && efiEnabled"
|
||||||
|
v-model:value="efiPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.efiPersistentState')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -886,9 +939,10 @@ export default {
|
|||||||
v-model:value="secureBoot"
|
v-model:value="secureBoot"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.secureBoot')"
|
:label="t('harvester.virtualMachine.advancedOptions.secureBoot')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Banner
|
<Banner
|
||||||
v-if="showCpuPinningBanner"
|
v-if="showCpuPinningBanner"
|
||||||
color="warning"
|
color="warning"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default {
|
|||||||
const rows = (this.value || []).map((row) => {
|
const rows = (this.value || []).map((row) => {
|
||||||
let type = 'cidr';
|
let type = 'cidr';
|
||||||
|
|
||||||
if (row.rangeStart && row.rangeEnd) {
|
if (row.rangeStart || row.rangeEnd) {
|
||||||
type = 'range';
|
type = 'range';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default {
|
|||||||
:labels="[t('generic.disabled'),t('generic.enabled')]"
|
:labels="[t('generic.disabled'),t('generic.enabled')]"
|
||||||
:options="[false, true]"
|
:options="[false, true]"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@input="onToggle"
|
@update:value="onToggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default {
|
|||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
blockDevices.map((b) => {
|
blockDevices.map((b) => {
|
||||||
if (b.metadata.state.error) {
|
if (b?.metadata?.state?.error) {
|
||||||
out.push(b.metadata.state.message);
|
out.push(b.metadata.state.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
|
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
|
||||||
import { METRIC, NODE } from '@shell/config/types';
|
import { METRIC, NODE } from '@shell/config/types';
|
||||||
import { formatSi, exponentNeeded, UNITS, parseSi } from '@shell/utils/units';
|
import { formatSi, exponentNeeded, UNITS, parseSi } from '@shell/utils/units';
|
||||||
|
import { UNIT_SUFFIX } from '../utils/unit';
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterMemoryUsed',
|
name: 'HarvesterMemoryUsed',
|
||||||
components: { ConsumptionGauge },
|
components: { ConsumptionGauge },
|
||||||
@@ -51,7 +51,7 @@ export default {
|
|||||||
memoryUnits() {
|
memoryUnits() {
|
||||||
const exponent = exponentNeeded(this.memoryTotal, 1024);
|
const exponent = exponentNeeded(this.memoryTotal, 1024);
|
||||||
|
|
||||||
return `${ UNITS[exponent] }iB`;
|
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
|
||||||
},
|
},
|
||||||
|
|
||||||
node() {
|
node() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
|
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
|
||||||
import { LONGHORN } from '@shell/config/types';
|
import { LONGHORN } from '@shell/config/types';
|
||||||
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
||||||
|
import { UNIT_SUFFIX } from '../utils/unit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterStorageUsed',
|
name: 'HarvesterStorageUsed',
|
||||||
@@ -72,17 +73,23 @@ export default {
|
|||||||
return stats;
|
return stats;
|
||||||
},
|
},
|
||||||
|
|
||||||
units() {
|
allocatedUnits() {
|
||||||
const exponent = exponentNeeded(this.storageStats.maximum, 1024);
|
const exponent = exponentNeeded(this.storageStats.total, 1024);
|
||||||
|
|
||||||
return `${ UNITS[exponent] }iB`;
|
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
|
||||||
},
|
},
|
||||||
|
|
||||||
used() {
|
usedUnits() {
|
||||||
|
const exponent = exponentNeeded(this.storageStats.maximum, 1024);
|
||||||
|
|
||||||
|
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
|
||||||
|
},
|
||||||
|
|
||||||
|
formatUsed() {
|
||||||
let out = this.formatter(this.storageStats.used);
|
let out = this.formatter(this.storageStats.used);
|
||||||
|
|
||||||
if (!Number.parseFloat(out) > 0) {
|
if (!Number.parseFloat(out) > 0) {
|
||||||
out = this.formatter(this.storageStats.used, { canRoundToZero: false });
|
out = this.formatter(this.storageStats.used, { canRoundToZero: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
@@ -92,7 +99,7 @@ export default {
|
|||||||
let out = this.formatter(this.storageStats.scheduled);
|
let out = this.formatter(this.storageStats.scheduled);
|
||||||
|
|
||||||
if (!Number.parseFloat(out) > 0) {
|
if (!Number.parseFloat(out) > 0) {
|
||||||
out = this.formatter(this.storageStats.scheduled, { canRoundToZero: false });
|
out = this.formatter(this.storageStats.scheduled, { canRoundToZero: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
@@ -100,9 +107,9 @@ export default {
|
|||||||
|
|
||||||
usedAmountTemplateValues() {
|
usedAmountTemplateValues() {
|
||||||
return {
|
return {
|
||||||
used: this.used,
|
used: this.formatUsed,
|
||||||
total: this.formatter(this.storageStats.maximum),
|
total: this.formatter(this.storageStats.maximum),
|
||||||
unit: this.units,
|
unit: this.usedUnits,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -110,7 +117,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
used: this.formatAllocated,
|
used: this.formatAllocated,
|
||||||
total: this.formatter(this.storageStats.total),
|
total: this.formatter(this.storageStats.total),
|
||||||
unit: this.units,
|
unit: this.allocatedUnits,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -141,7 +148,7 @@ export default {
|
|||||||
<ConsumptionGauge
|
<ConsumptionGauge
|
||||||
:capacity="storageStats.total"
|
:capacity="storageStats.total"
|
||||||
:used="storageStats.scheduled"
|
:used="storageStats.scheduled"
|
||||||
:units="units"
|
:units="allocatedUnits"
|
||||||
:number-formatter="formatter"
|
:number-formatter="formatter"
|
||||||
:resource-name="resourceName"
|
:resource-name="resourceName"
|
||||||
>
|
>
|
||||||
@@ -161,7 +168,7 @@ export default {
|
|||||||
<ConsumptionGauge
|
<ConsumptionGauge
|
||||||
:capacity="storageStats.maximum"
|
:capacity="storageStats.maximum"
|
||||||
:used="storageStats.used"
|
:used="storageStats.used"
|
||||||
:units="units"
|
:units="usedUnits"
|
||||||
:number-formatter="formatter"
|
:number-formatter="formatter"
|
||||||
:resource-name="showAllocated ? '' : resourceName"
|
:resource-name="showAllocated ? '' : resourceName"
|
||||||
:class="{
|
:class="{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import harvesterCommonStore from './store/harvester-common';
|
|||||||
import harvesterStore from './store/harvester-store';
|
import harvesterStore from './store/harvester-store';
|
||||||
import customValidators from './validators';
|
import customValidators from './validators';
|
||||||
import { PRODUCT_NAME } from './config/harvester';
|
import { PRODUCT_NAME } from './config/harvester';
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
|
||||||
// Init the package
|
// Init the package
|
||||||
export default function (plugin: IPlugin) {
|
export default function (plugin: IPlugin) {
|
||||||
@@ -21,10 +22,6 @@ export default function (plugin: IPlugin) {
|
|||||||
// Built-in icon
|
// Built-in icon
|
||||||
plugin.metadata.icon = require('./icon.svg');
|
plugin.metadata.icon = require('./icon.svg');
|
||||||
|
|
||||||
if (isDev && !isSingleVirtualCluster) {
|
|
||||||
plugin.addProduct(require('./config/harvester-manager'));
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.addProduct(require('./config/harvester-cluster'));
|
plugin.addProduct(require('./config/harvester-cluster'));
|
||||||
|
|
||||||
plugin.addDashboardStore(harvesterCommonStore.config.namespace, harvesterCommonStore.specifics, harvesterCommonStore.config);
|
plugin.addDashboardStore(harvesterCommonStore.config.namespace, harvesterCommonStore.specifics, harvesterCommonStore.config);
|
||||||
@@ -32,4 +29,8 @@ export default function (plugin: IPlugin) {
|
|||||||
plugin.validators = customValidators;
|
plugin.validators = customValidators;
|
||||||
|
|
||||||
plugin.addRoutes(extensionRoutes);
|
plugin.addRoutes(extensionRoutes);
|
||||||
|
|
||||||
|
plugin.register('component', 'NavHeaderRight', defineAsyncComponent(() =>
|
||||||
|
import('./components/HarvesterUpgradeHeader.vue')
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,3 @@
|
|||||||
harvesterManager:
|
|
||||||
manage: Manage
|
|
||||||
tableHeaders:
|
|
||||||
kubernetesVersion: Kubernetes Version
|
|
||||||
harvesterVersion: Harvester Version
|
|
||||||
cluster:
|
|
||||||
label: Harvester Clusters
|
|
||||||
none: There are no Harvester Clusters
|
|
||||||
learnMore: Learn more about Harvester from the <a target="_blank" href="https://harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Web Site</a> or read the the <a target="_blank" href="https://docs.harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Docs</a>
|
|
||||||
description: Harvester is a modern Hyperconverged infrastructure (HCI) solution built for bare metal servers using enterprise-grade open source technologies including Kubernetes, Kubevirt and Longhorn.
|
|
||||||
supportMessage: Harvester ui extension only supports Harvester cluster version greater or equal to 1.3.0
|
|
||||||
plugins:
|
|
||||||
loadError: Error loading harvester plugin
|
|
||||||
rke:
|
|
||||||
templateError: Incorrect template format
|
|
||||||
affinity:
|
|
||||||
thisPodNamespace: This VM's namespace
|
|
||||||
matchExpressions:
|
|
||||||
inNamespaces: "Workloads in these namespaces"
|
|
||||||
vmAffinityTitle: VM Scheduling
|
|
||||||
namespaces:
|
|
||||||
placeholder: e.g. default,system,base
|
|
||||||
label: Namespaces
|
|
||||||
addLabel: Add Workload Selector
|
|
||||||
topologyKey:
|
|
||||||
placeholder: 'topology.kubernetes.io/zone'
|
|
||||||
vGpu:
|
|
||||||
title: VGPUs
|
|
||||||
label: VGPU type
|
|
||||||
placeholder: 'Please select a VGPU'
|
|
||||||
|
|
||||||
generic:
|
generic:
|
||||||
tip: Tip
|
tip: Tip
|
||||||
resourceExternalLinkTips: 'External Link'
|
resourceExternalLinkTips: 'External Link'
|
||||||
@@ -39,6 +8,11 @@ generic:
|
|||||||
basic: Basic
|
basic: Basic
|
||||||
loading: Loading...
|
loading: Loading...
|
||||||
|
|
||||||
|
unsupported:
|
||||||
|
serverVersion: 'Current version: <code>{serverVersion}</code>'
|
||||||
|
description: Harvester ui only supports Harvester cluster version greater or equal to 1.3.0.
|
||||||
|
learnMore: Learn more about Harvester from the <a target="_blank" href="https://harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Web Site</a> or read the <a target="_blank" href="https://docs.harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Docs</a>
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
group:
|
group:
|
||||||
networks: Networks
|
networks: Networks
|
||||||
@@ -64,9 +38,35 @@ asyncButton:
|
|||||||
success: Restarted
|
success: Restarted
|
||||||
waiting: Restarting…
|
waiting: Restarting…
|
||||||
|
|
||||||
|
dialog:
|
||||||
|
confirmExecution:
|
||||||
|
title: Are you sure?
|
||||||
|
andOthers: |-
|
||||||
|
{count, plural,
|
||||||
|
=0 {}
|
||||||
|
=1 { and <b>one other </b>}
|
||||||
|
other { and <b>{count} other </b>}
|
||||||
|
}
|
||||||
|
protip: "Tip: Hold the {alternateLabel} key while clicking action to bypass this confirmation"
|
||||||
|
stop:
|
||||||
|
message: "Are you sure you want to continue stop the {type} {names}?"
|
||||||
|
pause:
|
||||||
|
message: "Are you sure you want to continue pause the {type} {names}?"
|
||||||
|
promptRemove:
|
||||||
|
title: "Delete {type}"
|
||||||
|
warningMessage: "Deleting the selected {type} permanently removes all resources on {thisOrThese} {type}. This action is irreversible. Do you want to continue?"
|
||||||
|
confirmName: "Type <b>{nameToMatch}</b> to delete the {type}:"
|
||||||
|
protip: "Tip: Hold the {alternateLabel} key while clicking Delete to bypass the confirmation step."
|
||||||
|
|
||||||
harvester:
|
harvester:
|
||||||
|
branding:
|
||||||
|
logos:
|
||||||
|
tip: 'Upload a logo to replace the Harvester logo in the top-level navigation header. Image height should be 21 pixels with a max width of 200 pixels. Max file size is 20KB. Accepted formats: JPEG, PNG, SVG.'
|
||||||
|
favicon:
|
||||||
|
tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB'
|
||||||
productLabel: 'Harvester'
|
productLabel: 'Harvester'
|
||||||
modal:
|
modal:
|
||||||
|
|
||||||
backup:
|
backup:
|
||||||
success: 'Backup { backUpName } has been initiated.'
|
success: 'Backup { backUpName } has been initiated.'
|
||||||
addBackup: Add Backup
|
addBackup: Add Backup
|
||||||
@@ -105,6 +105,14 @@ harvester:
|
|||||||
tip: Please enter a virtual machine name!
|
tip: Please enter a virtual machine name!
|
||||||
success: 'Virtual machine { name } cloned successfully.'
|
success: 'Virtual machine { name } cloned successfully.'
|
||||||
failed: 'Failed clone virtual machine!'
|
failed: 'Failed clone virtual machine!'
|
||||||
|
osImage:
|
||||||
|
title: Delete Image
|
||||||
|
message: Are you sure you want to delete the image { name } ?
|
||||||
|
downloadImage:
|
||||||
|
title: Download Image
|
||||||
|
banner: 'This action takes a while depending on the image size ({ size }). Please be patient.'
|
||||||
|
startMessage : 'The download process will auto start once the conversion is complete.'
|
||||||
|
download: Download
|
||||||
exportImage:
|
exportImage:
|
||||||
title: Export to Image
|
title: Export to Image
|
||||||
name: Name
|
name: Name
|
||||||
@@ -605,15 +613,17 @@ harvester:
|
|||||||
enableUsb: Enable USB Tablet
|
enableUsb: Enable USB Tablet
|
||||||
advancedOptions:
|
advancedOptions:
|
||||||
tpm: Enable TPM
|
tpm: Enable TPM
|
||||||
|
tpmPersistentState: TPM Persistent State
|
||||||
cpuManager:
|
cpuManager:
|
||||||
prefix: You must enable CPU Manager for at least one node in
|
prefix: You must enable CPU Manager for at least one node in
|
||||||
middle: 'host page'
|
middle: 'host page'
|
||||||
suffix: to enable CPU Pinning for VM
|
suffix: to enable CPU Pinning for VM
|
||||||
|
efiEnabled: Booting in EFI mode
|
||||||
|
efiPersistentState: EFI Persistent State
|
||||||
|
secureBoot: Secure Boot
|
||||||
usbTip: Provides an absolute pointer device which often helps with getting a consistent mouse cursor position in VNC.
|
usbTip: Provides an absolute pointer device which often helps with getting a consistent mouse cursor position in VNC.
|
||||||
sshTitle: Add Public SSH Key
|
sshTitle: Add Public SSH Key
|
||||||
imageTip: An external URL to the .iso, .img, .qcow2 or .raw that the virtual machine should be created from.
|
imageTip: An external URL to the .iso, .img, .qcow2 or .raw that the virtual machine should be created from.
|
||||||
efiEnabled: Booting in EFI mode
|
|
||||||
secureBoot: Secure Boot
|
|
||||||
volume:
|
volume:
|
||||||
dragTip: Drag and drop volumes, or use the volume's arrows, to change the boot order.
|
dragTip: Drag and drop volumes, or use the volume's arrows, to change the boot order.
|
||||||
volumeTip: The virtual machine only contains a CD-ROM volume. You may want to add additional disk volumes.
|
volumeTip: The virtual machine only contains a CD-ROM volume. You may want to add additional disk volumes.
|
||||||
@@ -634,6 +644,7 @@ harvester:
|
|||||||
setFirst: Set as root volume
|
setFirst: Set as root volume
|
||||||
saveVolume: Update Volume
|
saveVolume: Update Volume
|
||||||
encryption: Encryption
|
encryption: Encryption
|
||||||
|
vmImageVolumeTip: Disk size ({diskSize}) should greater than selected image virtual size ({imageVirtualSize})
|
||||||
lockTooltip:
|
lockTooltip:
|
||||||
all: All volumes are encrypted.
|
all: All volumes are encrypted.
|
||||||
partial: Some volumes are encrypted.
|
partial: Some volumes are encrypted.
|
||||||
@@ -768,11 +779,18 @@ harvester:
|
|||||||
terminationGracePeriodSeconds:
|
terminationGracePeriodSeconds:
|
||||||
label: Termination Grace Period
|
label: Termination Grace Period
|
||||||
affinity:
|
affinity:
|
||||||
|
addLabel: Add Workload Selector
|
||||||
|
topologyKey:
|
||||||
|
placeholder: 'topology.kubernetes.io/zone'
|
||||||
thisPodNamespace: This virtual machine's namespace
|
thisPodNamespace: This virtual machine's namespace
|
||||||
matchExpressions:
|
matchExpressions:
|
||||||
inNamespaces: "Workloads in these namespaces"
|
inNamespaces: "Workloads in these namespaces"
|
||||||
namespaces:
|
namespaces:
|
||||||
label: Namespaces
|
label: Namespaces
|
||||||
|
instanceLabels:
|
||||||
|
banner: These labels are automatically synchronized to the virtual machine instance.
|
||||||
|
labels:
|
||||||
|
banner: These key values are added as labels to the virtual machine.
|
||||||
|
|
||||||
volume:
|
volume:
|
||||||
label: Volumes
|
label: Volumes
|
||||||
@@ -782,6 +800,7 @@ harvester:
|
|||||||
datasource: Data Source
|
datasource: Data Source
|
||||||
details: Details
|
details: Details
|
||||||
size: Size
|
size: Size
|
||||||
|
volumeMode: Volume Mode
|
||||||
source: Source
|
source: Source
|
||||||
kind: Kind
|
kind: Kind
|
||||||
sourceOptions:
|
sourceOptions:
|
||||||
@@ -832,6 +851,8 @@ harvester:
|
|||||||
=1 {1 image is uploading, please do not refresh or close the page.}
|
=1 {1 image is uploading, please do not refresh or close the page.}
|
||||||
other {{count} images are uploading, please do not refresh or close the page.}
|
other {{count} images are uploading, please do not refresh or close the page.}
|
||||||
}
|
}
|
||||||
|
osUpgrade:
|
||||||
|
uploading: "{name} is uploading, please do not refresh or close the page."
|
||||||
checksum: Checksum
|
checksum: Checksum
|
||||||
checksumTip: Validate the image using the SHA512 checksum, if specified.
|
checksumTip: Validate the image using the SHA512 checksum, if specified.
|
||||||
|
|
||||||
@@ -856,17 +877,22 @@ harvester:
|
|||||||
currentVersion: CURRENT VERSION
|
currentVersion: CURRENT VERSION
|
||||||
versionLabel: AVAILABLE COMPATIBLE VERSIONS
|
versionLabel: AVAILABLE COMPATIBLE VERSIONS
|
||||||
enableLogging: Enable Logging
|
enableLogging: Enable Logging
|
||||||
|
skipSingleReplicaDetachedVol: Skip checking single-replica detached volumes
|
||||||
upgradeNode: Upgrading Node
|
upgradeNode: Upgrading Node
|
||||||
upgradeSysService: Upgrading System Service
|
upgradeSysService: Upgrading System Service
|
||||||
upgradeImage: Download Upgrade Image
|
upgradeImage: Download Upgrade Image
|
||||||
osUpgrade: OS Upgrade
|
osUpgrade: Cluster Upgrade
|
||||||
uploadNew: Upload New Image
|
uploadNew: Upload New Image
|
||||||
|
deleteHeader: Please select an image to delete.
|
||||||
|
deleteExisting: Delete Existing Image
|
||||||
selectExisting: Select Existing Image
|
selectExisting: Select Existing Image
|
||||||
createRepository: Creating Upgrade Repository
|
createRepository: Creating Upgrade Repository
|
||||||
succeeded: Succeeded
|
succeeded: Succeeded
|
||||||
releaseTip: Please read the upgrade documentation carefully. You can view details on the <a href="{url}" target="_blank">Harvester Release Notes</a>.
|
releaseTip: Please read the upgrade documentation carefully. You can view details on the <a href="{url}" target="_blank">Harvester Release Notes</a>.
|
||||||
checkReady: I have read and understood the upgrade instructions related to this Harvester version.
|
checkReady: I have read and understood the upgrade instructions related to this Harvester version.
|
||||||
pending: Pending
|
pending: Pending
|
||||||
|
upload:
|
||||||
|
duplicatedFile: The file you are trying to upload already exists.
|
||||||
repoInfo:
|
repoInfo:
|
||||||
upgradeStatus: Upgrade Status
|
upgradeStatus: Upgrade Status
|
||||||
os: OS
|
os: OS
|
||||||
@@ -884,14 +910,14 @@ harvester:
|
|||||||
doc: Read the <a href="{url}" target="_blank">documentation</a> before starting the upgrade process. Ensure that you complete procedures that are relevant to your environment and the version you are upgrading to.
|
doc: Read the <a href="{url}" target="_blank">documentation</a> before starting the upgrade process. Ensure that you complete procedures that are relevant to your environment and the version you are upgrading to.
|
||||||
tip: Unmet system requirements and incorrectly performed procedures may cause complete upgrade failure and other issues that require manual workarounds.
|
tip: Unmet system requirements and incorrectly performed procedures may cause complete upgrade failure and other issues that require manual workarounds.
|
||||||
moreNotes: For more details about the release notes, please visit -
|
moreNotes: For more details about the release notes, please visit -
|
||||||
|
|
||||||
schedule:
|
schedule:
|
||||||
label: Virtual Machine Schedules
|
label: Virtual Machine Schedules
|
||||||
createTitle: Create Schedule
|
createTitle: Create Schedule
|
||||||
createButtonText: Create Schedule
|
createButtonText: Create Schedule
|
||||||
scheduleType: Virtual Machine Schedule Type
|
scheduleType: Virtual Machine Schedule Type
|
||||||
cron: Cron Schedule
|
cron: Cron Schedule
|
||||||
detail:
|
detail:
|
||||||
namespace: Namespace
|
namespace: Namespace
|
||||||
sourceVM: Source Virtual Machine
|
sourceVM: Source Virtual Machine
|
||||||
tabs:
|
tabs:
|
||||||
@@ -901,12 +927,12 @@ harvester:
|
|||||||
message:
|
message:
|
||||||
noSetting:
|
noSetting:
|
||||||
suffix: before creating a backup schedule
|
suffix: before creating a backup schedule
|
||||||
retain:
|
retain:
|
||||||
label: Retain
|
label: Retain
|
||||||
count: Count
|
count: Count
|
||||||
tooltip: Number of up-to-date VM backups to retain. Maximum to 250, minimum to 2.
|
tooltip: Number of up-to-date VM backups to retain. Maximum to 250, minimum to 2.
|
||||||
maxFailure:
|
maxFailure:
|
||||||
label: Max Failure
|
label: Max Failure
|
||||||
count: Count
|
count: Count
|
||||||
tooltip: Max number of consecutive failed backups that could be tolerated. If reach this threshold, Harvester controller will suspend the schedule job. This value should less than retain count
|
tooltip: Max number of consecutive failed backups that could be tolerated. If reach this threshold, Harvester controller will suspend the schedule job. This value should less than retain count
|
||||||
virtualMachine:
|
virtualMachine:
|
||||||
@@ -920,6 +946,9 @@ harvester:
|
|||||||
label: Virtual Machine Backups
|
label: Virtual Machine Backups
|
||||||
createText: Restore Backup
|
createText: Restore Backup
|
||||||
title: Restore Virtual Machine
|
title: Restore Virtual Machine
|
||||||
|
refreshInterval:
|
||||||
|
label: Refresh Interval
|
||||||
|
tip: The interval for the backup target to sync data to harvester cluster. Set to 0 to disable refresh mechanism.
|
||||||
backupTargetTip: The endpoint used to access the backupstore. NFS and S3 are supported.
|
backupTargetTip: The endpoint used to access the backupstore. NFS and S3 are supported.
|
||||||
message:
|
message:
|
||||||
noSetting:
|
noSetting:
|
||||||
@@ -1029,7 +1058,7 @@ harvester:
|
|||||||
invalid: '"Exclude list" is invalid.'
|
invalid: '"Exclude list" is invalid.'
|
||||||
addIp: Add Exclude IP
|
addIp: Add Exclude IP
|
||||||
warning: 'WARNING: <br/> Any change to storage-network requires shutting down all virtual machines before applying this setting. <br/> Users have to ensure the cluster network is configured and VLAN Configuration will cover all nodes and ensure the network connectivity is working and expected in all nodes.'
|
warning: 'WARNING: <br/> Any change to storage-network requires shutting down all virtual machines before applying this setting. <br/> Users have to ensure the cluster network is configured and VLAN Configuration will cover all nodes and ensure the network connectivity is working and expected in all nodes.'
|
||||||
tip: 'Specify an IP range in the IPv4 CIDR format. <code>Number of IPs Required = Number of Nodes * 4 + Number of Disks * 2 + Number of Images to Download/Upload </code>. For more information about storage network settings, see the <a href="{url}" target="_blank">documentation</a>.'
|
tip: 'Specify an IP range in the IPv4 CIDR format. <code>Number of IPs Required = Number of Nodes * 2 + Number of Disks * 2 + Number of Images to Download/Upload </code>. For more information about storage network settings, see the <a href="{url}" target="_blank">documentation</a>.'
|
||||||
vmForceDeletionPolicy:
|
vmForceDeletionPolicy:
|
||||||
period: Period
|
period: Period
|
||||||
ratio : Ratio
|
ratio : Ratio
|
||||||
@@ -1066,10 +1095,15 @@ harvester:
|
|||||||
addConfig: Add Configuration
|
addConfig: Add Configuration
|
||||||
|
|
||||||
upgrade:
|
upgrade:
|
||||||
selectExitImage: Please select the OS image to upgrade.
|
unknownImageName: Image name is not found.
|
||||||
imageUrl: Please input a valid image URL.
|
imageUrl: Please input a valid image URL.
|
||||||
chooseFile: Please select to upload an image.
|
chooseFile: Please select to upload an image.
|
||||||
checksum: Checksum
|
checksum: Checksum
|
||||||
|
networkError: Unable to upload the image. Resolve network issues that may have occurred and try again.
|
||||||
|
cancelUpload: Cancelled the image upload.
|
||||||
|
uploadSuccess: "{name} uploaded successfully. Press Upgrade button to start the cluster upgrade process."
|
||||||
|
deleteImage: Please select an image to delete.
|
||||||
|
deleteSuccess: "{name} deleted successfully."
|
||||||
harvesterMonitoring:
|
harvesterMonitoring:
|
||||||
label: Harvester Monitoring
|
label: Harvester Monitoring
|
||||||
section:
|
section:
|
||||||
@@ -1520,17 +1554,13 @@ harvester:
|
|||||||
pollingInterval:
|
pollingInterval:
|
||||||
label: Polling Interval
|
label: Polling Interval
|
||||||
|
|
||||||
affinity:
|
typeDescription:
|
||||||
thisPodNamespace: This virtual machine's namespace
|
# Map of
|
||||||
matchExpressions:
|
# type: Description to be shown on the top of list view describing the type.
|
||||||
inNamespaces: "Workloads in these namespaces"
|
# Should fit on one line.
|
||||||
vmAffinityTitle: Virtual Machine Scheduling
|
# If you link to anything external, it MUST have
|
||||||
namespaces:
|
# target="_blank" rel="noopener noreferrer nofollow"
|
||||||
placeholder: e.g. default,system,base
|
harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme."
|
||||||
label: Namespaces
|
|
||||||
addLabel: Add Workload Selector
|
|
||||||
topologyKey:
|
|
||||||
placeholder: 'topology.kubernetes.io/zone'
|
|
||||||
|
|
||||||
advancedSettings:
|
advancedSettings:
|
||||||
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
|
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { HCI } from '../types';
|
|||||||
export default {
|
export default {
|
||||||
name: 'ListSriovGpuDevices',
|
name: 'ListSriovGpuDevices',
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Banner,
|
Banner,
|
||||||
Loading,
|
Loading,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import DeviceList from '../edit/kubevirt.io.virtualmachine/VirtualMachineUSBDevi
|
|||||||
export default {
|
export default {
|
||||||
name: 'ListUsbDevicePage',
|
name: 'ListUsbDevicePage',
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Banner,
|
Banner,
|
||||||
DeviceList,
|
DeviceList,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { ADD_ONS } from '../config/harvester-map';
|
|||||||
export default {
|
export default {
|
||||||
name: 'ListVGpuDevices',
|
name: 'ListVGpuDevices',
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Banner,
|
Banner,
|
||||||
Loading,
|
Loading,
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import Banner from '@components/Banner/Banner.vue';
|
|||||||
import MessageLink from '@shell/components/MessageLink';
|
import MessageLink from '@shell/components/MessageLink';
|
||||||
import SortableTable from '@shell/components/SortableTable';
|
import SortableTable from '@shell/components/SortableTable';
|
||||||
import { allHash, setPromiseResult } from '@shell/utils/promise';
|
import { allHash, setPromiseResult } from '@shell/utils/promise';
|
||||||
import {
|
import { parseSi, formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
||||||
parseSi, formatSi, exponentNeeded, UNITS, createMemoryValues
|
|
||||||
} from '@shell/utils/units';
|
|
||||||
import { REASON } from '@shell/config/table-headers';
|
import { REASON } from '@shell/config/table-headers';
|
||||||
import {
|
import {
|
||||||
EVENT, METRIC, NODE, SERVICE, PVC, LONGHORN, POD, COUNT, NETWORK_ATTACHMENT
|
EVENT, METRIC, NODE, SERVICE, PVC, LONGHORN, POD, COUNT, NETWORK_ATTACHMENT
|
||||||
@@ -27,22 +25,21 @@ import { isEmpty } from '@shell/utils/object';
|
|||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
import HarvesterUpgrade from '../components/HarvesterUpgrade';
|
import HarvesterUpgrade from '../components/HarvesterUpgrade';
|
||||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../config/harvester';
|
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../config/harvester';
|
||||||
|
import { UNIT_SUFFIX } from '../utils/unit';
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(minMax);
|
dayjs.extend(minMax);
|
||||||
|
|
||||||
const PARSE_RULES = {
|
const PARSE_RULES = {
|
||||||
memory: {
|
format: {
|
||||||
format: {
|
addSuffix: true,
|
||||||
addSuffix: true,
|
firstSuffix: UNIT_SUFFIX,
|
||||||
firstSuffix: 'B',
|
increment: 1024,
|
||||||
increment: 1024,
|
maxExponent: 99,
|
||||||
maxExponent: 99,
|
maxPrecision: 2,
|
||||||
maxPrecision: 2,
|
minExponent: 0,
|
||||||
minExponent: 0,
|
startingExponent: 0,
|
||||||
startingExponent: 0,
|
suffix: UNIT_SUFFIX,
|
||||||
suffix: 'iB',
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,6 +132,7 @@ export default {
|
|||||||
|
|
||||||
const hash = {
|
const hash = {
|
||||||
vms: this.fetchClusterResources(HCI.VM),
|
vms: this.fetchClusterResources(HCI.VM),
|
||||||
|
pvcs: this.fetchClusterResources(PVC),
|
||||||
nodes: this.fetchClusterResources(NODE),
|
nodes: this.fetchClusterResources(NODE),
|
||||||
events: this.fetchClusterResources(EVENT),
|
events: this.fetchClusterResources(EVENT),
|
||||||
metricNodes: this.fetchClusterResources(METRIC.NODE),
|
metricNodes: this.fetchClusterResources(METRIC.NODE),
|
||||||
@@ -224,6 +222,7 @@ export default {
|
|||||||
nodes: [],
|
nodes: [],
|
||||||
metricNodes: [],
|
metricNodes: [],
|
||||||
vms: [],
|
vms: [],
|
||||||
|
pvcs: [],
|
||||||
monitoring: {},
|
monitoring: {},
|
||||||
VM_DASHBOARD_METRICS_URL,
|
VM_DASHBOARD_METRICS_URL,
|
||||||
CLUSTER_METRICS_SUMMARY_URL,
|
CLUSTER_METRICS_SUMMARY_URL,
|
||||||
@@ -288,6 +287,14 @@ export default {
|
|||||||
out[resource.type].name = this.t(`typeLabel."${ resource.spoofed.name }"`, { count: out[resource.type].total });
|
out[resource.type].name = this.t(`typeLabel."${ resource.spoofed.name }"`, { count: out[resource.type].total });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resource.type === PVC) {
|
||||||
|
// filter out the golden image volumes
|
||||||
|
const goldenImageVolumeCount = (this.pvcs || []).filter((pvc) => pvc.isGoldenImageVolume).length;
|
||||||
|
|
||||||
|
out[resource.type].useful = out[resource.type].useful - goldenImageVolumeCount;
|
||||||
|
out[resource.type].total = out[resource.type].total - goldenImageVolumeCount;
|
||||||
|
}
|
||||||
|
|
||||||
if (resource.type === HCI.BLOCK_DEVICE) {
|
if (resource.type === HCI.BLOCK_DEVICE) {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
@@ -402,13 +409,13 @@ export default {
|
|||||||
storageUsed() {
|
storageUsed() {
|
||||||
const stats = this.storageStats;
|
const stats = this.storageStats;
|
||||||
|
|
||||||
return this.createMemoryValues(stats.maximum, stats.used);
|
return this.createDisplayValues(stats.maximum, stats.used);
|
||||||
},
|
},
|
||||||
|
|
||||||
storageAllocated() {
|
storageAllocated() {
|
||||||
const stats = this.storageStats;
|
const stats = this.storageStats;
|
||||||
|
|
||||||
return this.createMemoryValues(stats.total, stats.scheduled);
|
return this.createDisplayValues(stats.total, stats.scheduled);
|
||||||
},
|
},
|
||||||
|
|
||||||
vmEvents() {
|
vmEvents() {
|
||||||
@@ -454,7 +461,7 @@ export default {
|
|||||||
return total + node.memoryReserved;
|
return total + node.memoryReserved;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return createMemoryValues(this.memoryTotal, useful);
|
return this.createDisplayValues(this.memoryTotal, useful);
|
||||||
},
|
},
|
||||||
|
|
||||||
availableNodes() {
|
availableNodes() {
|
||||||
@@ -494,7 +501,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
ramUsed() {
|
ramUsed() {
|
||||||
return createMemoryValues(this.memoryTotal, this.metricAggregations?.memory);
|
return this.createDisplayValues(this.memoryTotal, this.metricAggregations?.memory);
|
||||||
},
|
},
|
||||||
|
|
||||||
hasMetricNodeSchema() {
|
hasMetricNodeSchema() {
|
||||||
@@ -516,10 +523,12 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
createMemoryValues(total, useful) {
|
createDisplayValues(total, useful) {
|
||||||
const parsedTotal = parseSi((total || '0').toString());
|
const parsedTotal = parseSi((total || '0').toString());
|
||||||
|
|
||||||
const parsedUseful = parseSi((useful || '0').toString());
|
const parsedUseful = parseSi((useful || '0').toString());
|
||||||
const format = this.createMemoryFormat(parsedTotal);
|
const format = this.createFormat(parsedTotal);
|
||||||
|
|
||||||
const formattedTotal = formatSi(parsedTotal, format);
|
const formattedTotal = formatSi(parsedTotal, format);
|
||||||
let formattedUseful = formatSi(parsedUseful, {
|
let formattedUseful = formatSi(parsedUseful, {
|
||||||
...format,
|
...format,
|
||||||
@@ -538,24 +547,24 @@ export default {
|
|||||||
useful: Number(parsedUseful),
|
useful: Number(parsedUseful),
|
||||||
formattedTotal,
|
formattedTotal,
|
||||||
formattedUseful,
|
formattedUseful,
|
||||||
units: this.createMemoryUnits(parsedTotal),
|
units: this.createUnits(parsedTotal),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
createMemoryFormat(n) {
|
createFormat(n) {
|
||||||
const exponent = exponentNeeded(n, PARSE_RULES.memory.format.increment);
|
const exponent = exponentNeeded(n, PARSE_RULES.format.increment);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...PARSE_RULES.memory.format,
|
...PARSE_RULES.format,
|
||||||
maxExponent: exponent,
|
maxExponent: exponent,
|
||||||
minExponent: exponent,
|
minExponent: exponent,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
createMemoryUnits(n) {
|
createUnits(n) {
|
||||||
const exponent = exponentNeeded(n, PARSE_RULES.memory.format.increment);
|
const exponent = exponentNeeded(n, PARSE_RULES.format.increment);
|
||||||
|
|
||||||
return `${ UNITS[exponent] }${ PARSE_RULES.memory.format.suffix }`;
|
return `${ UNITS[exponent] }${ PARSE_RULES.format.suffix }`;
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchClusterResources(type, opt = {}, store) {
|
async fetchClusterResources(type, opt = {}, store) {
|
||||||
|
|||||||
@@ -101,14 +101,14 @@ export default {
|
|||||||
const metricCol = [
|
const metricCol = [
|
||||||
{
|
{
|
||||||
name: 'cpu',
|
name: 'cpu',
|
||||||
labelKey: 'node.detail.glance.consumptionGauge.cpu',
|
labelKey: 'harvester.dashboard.hardwareResourceGauge.cpu',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
formatter: 'HarvesterCPUUsed',
|
formatter: 'HarvesterCPUUsed',
|
||||||
formatterOpts: { showUsed: true },
|
formatterOpts: { showUsed: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'memory',
|
name: 'memory',
|
||||||
labelKey: 'node.detail.glance.consumptionGauge.memory',
|
labelKey: 'harvester.dashboard.hardwareResourceGauge.memory',
|
||||||
value: 'id',
|
value: 'id',
|
||||||
formatter: 'HarvesterMemoryUsed',
|
formatter: 'HarvesterMemoryUsed',
|
||||||
formatterOpts: { showUsed: true },
|
formatterOpts: { showUsed: true },
|
||||||
|
|||||||
@@ -1,279 +0,0 @@
|
|||||||
<script>
|
|
||||||
import BrandImage from '@shell/components/BrandImage';
|
|
||||||
import TypeDescription from '@shell/components/TypeDescription';
|
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
|
||||||
import Masthead from '@shell/components/ResourceList/Masthead';
|
|
||||||
import Loading from '@shell/components/Loading';
|
|
||||||
import { HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
|
|
||||||
import { CAPI, HCI, MANAGEMENT } from '@shell/config/types';
|
|
||||||
import { isHarvesterCluster } from '@shell/utils/cluster';
|
|
||||||
import { allHash } from '@shell/utils/promise';
|
|
||||||
import { STATE, NAME as NAME_COL, AGE, VERSION } from '@shell/config/table-headers';
|
|
||||||
import { MACHINE_POOLS } from '../config/table-headers';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
BrandImage,
|
|
||||||
ResourceTable,
|
|
||||||
Masthead,
|
|
||||||
TypeDescription,
|
|
||||||
Loading
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
|
||||||
schema: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
useQueryParamsForSimpleFiltering: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
const hash = await allHash({
|
|
||||||
hciClusters: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER }),
|
|
||||||
mgmtClusters: this.$store.dispatch(`${ inStore }/findAll`, { type: MANAGEMENT.CLUSTER })
|
|
||||||
});
|
|
||||||
|
|
||||||
this.hciClusters = hash.hciClusters;
|
|
||||||
this.mgmtClusters = hash.mgmtClusters;
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
const resource = CAPI.RANCHER_CLUSTER;
|
|
||||||
|
|
||||||
return {
|
|
||||||
navigating: false,
|
|
||||||
VIRTUAL,
|
|
||||||
hciDashboard: HCI.DASHBOARD,
|
|
||||||
resource,
|
|
||||||
hResource: HCI.CLUSTER,
|
|
||||||
realSchema: this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER),
|
|
||||||
hciClusters: [],
|
|
||||||
mgmtClusters: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
headers() {
|
|
||||||
return [
|
|
||||||
STATE,
|
|
||||||
NAME_COL,
|
|
||||||
{
|
|
||||||
name: 'harvesterVersion',
|
|
||||||
sort: 'harvesterVersion',
|
|
||||||
labelKey: 'harvesterManager.tableHeaders.harvesterVersion',
|
|
||||||
value: 'harvesterVersion',
|
|
||||||
getValue: (row) => row.harvesterVersion
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...VERSION,
|
|
||||||
labelKey: 'harvesterManager.tableHeaders.kubernetesVersion',
|
|
||||||
value: 'kubernetesVersion',
|
|
||||||
getValue: (row) => row.kubernetesVersion
|
|
||||||
},
|
|
||||||
MACHINE_POOLS,
|
|
||||||
AGE,
|
|
||||||
{
|
|
||||||
name: 'harvester',
|
|
||||||
label: ' ',
|
|
||||||
align: 'right',
|
|
||||||
width: 65,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
importLocation() {
|
|
||||||
return {
|
|
||||||
name: 'c-cluster-product-resource-create',
|
|
||||||
params: {
|
|
||||||
product: this.$store.getters['currentProduct'].name,
|
|
||||||
resource: this.schema.id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
canCreateCluster() {
|
|
||||||
const schema = this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER);
|
|
||||||
|
|
||||||
return !!schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
|
||||||
},
|
|
||||||
|
|
||||||
rows() {
|
|
||||||
return this.hciClusters
|
|
||||||
.filter((c) => {
|
|
||||||
const cluster = this.mgmtClusters.find((cluster) => cluster?.metadata?.name === c?.status?.clusterName);
|
|
||||||
|
|
||||||
return isHarvesterCluster(cluster);
|
|
||||||
})
|
|
||||||
.map((row) => {
|
|
||||||
if (row.isReady) {
|
|
||||||
row.setSupportedHarvesterVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
typeDisplay() {
|
|
||||||
return this.t(`typeLabel."${ HCI.CLUSTER }"`, { count: this.rows?.length || 0 });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async goToCluster(row) {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
// Don't show loading indicator for quickly fetched plugins
|
|
||||||
this.navigating = row.id;
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await row.goToCluster();
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
|
||||||
this.navigating = false;
|
|
||||||
} catch {
|
|
||||||
// The error handling is carried out within goToCluster, but just in case something happens before the promise chain can catch it...
|
|
||||||
clearTimeout(timeout);
|
|
||||||
this.navigating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Loading v-if="$fetchState.pending" />
|
|
||||||
<div v-else>
|
|
||||||
<Masthead
|
|
||||||
:schema="realSchema"
|
|
||||||
:resource="resource"
|
|
||||||
:is-creatable="false"
|
|
||||||
:type-display="typeDisplay"
|
|
||||||
>
|
|
||||||
<template #typeDescription>
|
|
||||||
<TypeDescription :resource="hResource" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template
|
|
||||||
v-if="canCreateCluster"
|
|
||||||
#extraActions
|
|
||||||
>
|
|
||||||
<router-link
|
|
||||||
:to="importLocation"
|
|
||||||
class="btn role-primary"
|
|
||||||
>
|
|
||||||
{{ t('cluster.importAction') }}
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
</Masthead>
|
|
||||||
|
|
||||||
<ResourceTable
|
|
||||||
v-if="rows && rows.length"
|
|
||||||
:schema="schema"
|
|
||||||
:headers="headers"
|
|
||||||
:rows="rows"
|
|
||||||
:is-creatable="true"
|
|
||||||
:namespaced="false"
|
|
||||||
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
|
||||||
>
|
|
||||||
<template #col:name="{row}">
|
|
||||||
<td>
|
|
||||||
<span class="cluster-link">
|
|
||||||
<a
|
|
||||||
v-if="row.isReady && row.isSupportedHarvester"
|
|
||||||
class="link"
|
|
||||||
:disabled="navigating ? true : null"
|
|
||||||
@click="goToCluster(row)"
|
|
||||||
>{{ row.nameDisplay }}</a>
|
|
||||||
<span v-else>
|
|
||||||
{{ row.nameDisplay }}
|
|
||||||
</span>
|
|
||||||
<i
|
|
||||||
class="icon icon-spinner icon-spin ml-5"
|
|
||||||
:class="{'navigating': navigating === row.id}"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #cell:harvester="{row}">
|
|
||||||
<router-link
|
|
||||||
class="btn btn-sm role-primary"
|
|
||||||
:to="row.detailLocation"
|
|
||||||
>
|
|
||||||
{{ t('harvesterManager.manage') }}
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
</ResourceTable>
|
|
||||||
<div v-else>
|
|
||||||
<div class="no-clusters">
|
|
||||||
{{ t('harvesterManager.cluster.none') }}
|
|
||||||
</div>
|
|
||||||
<hr class="info-section">
|
|
||||||
<div class="logo">
|
|
||||||
<BrandImage
|
|
||||||
file-name="harvester.png"
|
|
||||||
height="64"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="tagline">
|
|
||||||
<div>{{ t('harvesterManager.cluster.description') }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="tagline sub-tagline">
|
|
||||||
<div v-clean-html="t('harvesterManager.cluster.learnMore', {}, true)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.cluster-link {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
// Use visibility to avoid the columns re-adjusting when the icon is shown
|
|
||||||
visibility: hidden;
|
|
||||||
|
|
||||||
&.navigating {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
.no-clusters {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-section {
|
|
||||||
margin-top: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 60px 0 40px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tagline {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 30px;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 22px;
|
|
||||||
max-width: 80%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -2,13 +2,12 @@
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import Loading from '@shell/components/Loading';
|
import Loading from '@shell/components/Loading';
|
||||||
import { VIEW_IN_API, DEV } from '@shell/store/prefs';
|
|
||||||
import { MANAGEMENT } from '@shell/config/types';
|
import { MANAGEMENT } from '@shell/config/types';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import Tabbed from '@shell/components/Tabbed/index.vue';
|
import Tabbed from '@shell/components/Tabbed/index.vue';
|
||||||
import Tab from '@shell/components/Tabbed/Tab.vue';
|
import Tab from '@shell/components/Tabbed/Tab.vue';
|
||||||
import Settings from '@pkg/harvester/components/SettingList.vue';
|
import Settings from '@pkg/harvester/components/SettingList.vue';
|
||||||
import { HCI_ALLOWED_SETTINGS, HCI_SINGLE_CLUSTER_ALLOWED_SETTING } from '../config/settings';
|
import { HCI_ALLOWED_SETTINGS, HCI_SINGLE_CLUSTER_ALLOWED_SETTING, HCI_SETTING } from '../config/settings';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -21,14 +20,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
let isDev;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isDev = this.$store.getters['prefs/get'](VIEW_IN_API);
|
|
||||||
} catch {
|
|
||||||
isDev = this.$store.getters['prefs/get'](DEV);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSingleProduct = !!this.$store.getters['isSingleProduct'];
|
const isSingleProduct = !!this.$store.getters['isSingleProduct'];
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
@@ -77,7 +68,7 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
s.hide = s.canHide = (s.kind === 'json' || s.kind === 'multiline' || s.customFormatter === 'json' || s.data.customFormatter === 'json');
|
s.hide = s.canHide = (s.kind === 'json' || s.kind === 'multiline' || s.customFormatter === 'json' || s.data.customFormatter === 'json');
|
||||||
s.hasActions = !s.readOnly || isDev;
|
s.hasActions = s.id === HCI_SETTING.SERVER_VERSION ? true : !s.readOnly;
|
||||||
initSettings.push(s);
|
initSettings.push(s);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export default {
|
|||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:sub-rows="true"
|
:sub-rows="true"
|
||||||
:groupable="false"
|
:groupable="true"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:group-title-by="groupTitleBy"
|
:group-title-by="groupTitleBy"
|
||||||
:group-by="groupBy"
|
:group-by="groupBy"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
} from '@shell/config/types';
|
} from '@shell/config/types';
|
||||||
import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
|
import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
|
||||||
import HarvesterVolumeState from '../formatters/HarvesterVolumeState';
|
import HarvesterVolumeState from '../formatters/HarvesterVolumeState';
|
||||||
|
|
||||||
import { allSettled } from '../utils/promise';
|
import { allSettled } from '../utils/promise';
|
||||||
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
import { HCI, VOLUME_SNAPSHOT } from '../types';
|
||||||
|
|
||||||
@@ -58,7 +57,6 @@ export default {
|
|||||||
if (!pvcSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
if (!pvcSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
||||||
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
|
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rows = hash.pvcs;
|
this.rows = hash.pvcs;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -70,7 +68,10 @@ export default {
|
|||||||
schema() {
|
schema() {
|
||||||
return schema;
|
return schema;
|
||||||
},
|
},
|
||||||
|
filterRows() {
|
||||||
|
// we only show the non golden image PVCs in the list
|
||||||
|
return this.rows.filter((pvc) => !pvc?.isGoldenImageVolume);
|
||||||
|
},
|
||||||
headers() {
|
headers() {
|
||||||
return [
|
return [
|
||||||
STATE,
|
STATE,
|
||||||
@@ -92,7 +93,8 @@ export default {
|
|||||||
{
|
{
|
||||||
name: 'storageClass',
|
name: 'storageClass',
|
||||||
labelKey: 'tableHeaders.storageClass',
|
labelKey: 'tableHeaders.storageClass',
|
||||||
value: 'spec.storageClassName'
|
value: 'spec.storageClassName',
|
||||||
|
sort: 'spec.storageClassName'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'AttachedVM',
|
name: 'AttachedVM',
|
||||||
@@ -146,7 +148,7 @@ export default {
|
|||||||
:groupable="true"
|
:groupable="true"
|
||||||
default-sort-by="age"
|
default-sort-by="age"
|
||||||
:namespaced="true"
|
:namespaced="true"
|
||||||
:rows="rows"
|
:rows="filterRows"
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
key-field="_key"
|
key-field="_key"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -137,7 +137,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isTpmEnabled(spec) {
|
isTpmEnabled(spec) {
|
||||||
return !!spec?.template?.spec?.domain?.devices?.tpm ;
|
return !!spec?.template?.spec?.domain?.devices?.tpm;
|
||||||
|
},
|
||||||
|
|
||||||
|
isTPMPersistentStateEnabled(spec) {
|
||||||
|
return !!spec?.template?.spec?.domain?.devices?.tpm?.persistent;
|
||||||
|
},
|
||||||
|
|
||||||
|
isEFIPersistentStateEnabled(spec) {
|
||||||
|
return !!spec?.template?.spec?.domain?.firmware?.bootloader?.efi?.persistent;
|
||||||
},
|
},
|
||||||
|
|
||||||
isSecureBoot(spec) {
|
isSecureBoot(spec) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user