mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-06 14:16:05 +08:00
Compare commits
234
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e0cea8986 | ||
|
|
af80479bd9 | ||
|
|
4caecb48a7 | ||
|
|
8d1d370197 | ||
|
|
df9992146c | ||
|
|
acbc120dc1 | ||
|
|
59c63c8d9e | ||
|
|
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 |
@@ -1,7 +0,0 @@
|
|||||||
addAssignees: author
|
|
||||||
addReviewers: true
|
|
||||||
numberOfReviewers: 0
|
|
||||||
reviewers:
|
|
||||||
- a110605
|
|
||||||
- houhoucoop
|
|
||||||
- NickChungSUSE
|
|
||||||
@@ -12,19 +12,6 @@ pull_request_rules:
|
|||||||
labels:
|
labels:
|
||||||
- "v1.5 backport PR"
|
- "v1.5 backport PR"
|
||||||
|
|
||||||
- name: Automatically open backport PR to release-harvester-v1.0
|
|
||||||
conditions:
|
|
||||||
- base=main
|
|
||||||
- label="require backport 1.0"
|
|
||||||
actions:
|
|
||||||
backport:
|
|
||||||
branches:
|
|
||||||
- "release-harvester-v1.0"
|
|
||||||
assignees:
|
|
||||||
- "{{ author }}"
|
|
||||||
labels:
|
|
||||||
- "v1.0 backport PR"
|
|
||||||
|
|
||||||
- name: Ask to resolve conflict
|
- name: Ask to resolve conflict
|
||||||
conditions:
|
conditions:
|
||||||
- conflict
|
- conflict
|
||||||
|
|||||||
@@ -2,13 +2,18 @@
|
|||||||
### Summary
|
### Summary
|
||||||
|
|
||||||
### PR Checklists
|
### PR Checklists
|
||||||
- Are backend engineers aware of UI changes ?
|
- Do we need to backport this PR change to the [Harvester Dashboard](https://github.com/harvester/dashboard)?
|
||||||
|
- [ ] Yes, the relevant PR is at:
|
||||||
|
- Are backend engineers aware of UI changes?
|
||||||
- [ ] Yes, the backend owner is:
|
- [ ] Yes, the backend owner is:
|
||||||
|
|
||||||
### Related Issue #
|
### Related Issue #
|
||||||
<!-- Define findings related to the feature or bug issue. -->
|
<!-- Define findings related to the feature or bug issue. -->
|
||||||
|
|
||||||
### Test screenshot or video
|
### Test screenshot/video
|
||||||
<!-- Attach screenshot or video of the changes and eventual comparison if you find it necessary -->
|
<!-- Attach screenshot or video of the changes and eventual comparison if you find it necessary -->
|
||||||
|
|
||||||
|
### Extra technical notes summary
|
||||||
|
<!-- Outline technical changes which may pass unobserved or may help to understand the process of solving the issue -->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": ["config:recommended"],
|
|
||||||
"baseBranches": [
|
|
||||||
"master",
|
|
||||||
"main",
|
|
||||||
"/^release-harvester-v\\d+\\.\\d+$/"
|
|
||||||
],
|
|
||||||
"automergeMajor": false,
|
|
||||||
"semanticCommits": "enabled",
|
|
||||||
"semanticCommitType": "chore",
|
|
||||||
"semanticCommitScope": "deps",
|
|
||||||
"prHourlyLimit": 12,
|
|
||||||
"timezone": "Asia/Taipei",
|
|
||||||
"schedule": ["after 10am on sunday"],
|
|
||||||
"postUpdateOptions": ["yarnDedupeFewer"],
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchUpdateTypes": ["major"],
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchPackagePatterns": ["vue", "vue-router", "vuex"],
|
|
||||||
"matchUpdateTypes": ["major"],
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchPackageNames": ["@rancher/shell"],
|
|
||||||
"enabled": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchUpdateTypes": ["minor"],
|
|
||||||
"groupName": "minor dependencies",
|
|
||||||
"labels": ["minor-update"],
|
|
||||||
"reviewers": ["a110605", "houhoucoop"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchUpdateTypes": ["patch", "digest"],
|
|
||||||
"automerge": true,
|
|
||||||
"groupName": "patch digest dependencies",
|
|
||||||
"labels": ["patch-update", "automerge"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
name: "[PR Management] Auto Assign Reviewer & Assignee"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, ready_for_review]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
auto-assign:
|
|
||||||
if: github.event.pull_request.draft == false
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: rancher/gh-issue-mgr/auto-assign-action@main
|
|
||||||
with:
|
|
||||||
configuration-path: .github/auto-assign-config.yaml
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
name: "[PR Management] Add Labels"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, reopened]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- 'release-harvester-v*'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
add-require-backport-label:
|
|
||||||
if: github.event.pull_request.draft == false &&
|
|
||||||
github.event.pull_request.base.ref == 'main'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.base_ref }}
|
|
||||||
|
|
||||||
- name: Fetch release branches and PR labels
|
|
||||||
id: fetch_info
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
repo="${{ github.repository }}"
|
|
||||||
pr_number=${{ github.event.pull_request.number }}
|
|
||||||
|
|
||||||
release_branches=$(gh api "repos/${repo}/branches" --paginate --jq '.[].name' | grep -E '^release-harvester-v[0-9]+\.[0-9]+$' || true)
|
|
||||||
|
|
||||||
if [[ -z "$release_branches" ]]; then
|
|
||||||
echo "should_label=false" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
latest_branch=$(echo "$release_branches" | sort -Vr | head -n1)
|
|
||||||
version="${latest_branch#release-harvester-v}"
|
|
||||||
release_tag="v${version}.0"
|
|
||||||
|
|
||||||
tags=$(gh api "repos/${repo}/releases" --paginate --jq '.[].tag_name')
|
|
||||||
if echo "$tags" | grep -Fxq "$release_tag"; then
|
|
||||||
echo "should_label=false" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
label="require backport/v${version}"
|
|
||||||
echo "should_label=true" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "backport_label=$label" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
pr_labels=$(gh pr view "$pr_number" --repo "$repo" --json labels --jq '.labels[].name' || echo "")
|
|
||||||
pr_labels_csv=$(echo "$pr_labels" | tr '\n' ',' | sed 's/,$//')
|
|
||||||
echo "pr_labels=$pr_labels_csv" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Add label if needed
|
|
||||||
if: steps.fetch_info.outputs.should_label == 'true' && !contains(steps.fetch_info.outputs.pr_labels, steps.fetch_info.outputs.backport_label)
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
echo "Adding label: ${{ steps.fetch_info.outputs.backport_label }}"
|
|
||||||
gh pr edit ${{ github.event.pull_request.number }} \
|
|
||||||
--repo ${{ github.repository }} \
|
|
||||||
--add-label "${{ steps.fetch_info.outputs.backport_label }}"
|
|
||||||
|
|
||||||
add-backport-label:
|
|
||||||
if: github.event.pull_request.draft == false &&
|
|
||||||
startsWith(github.event.pull_request.base.ref, 'release-harvester-v')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check conditions for backport label
|
|
||||||
id: check
|
|
||||||
run: |
|
|
||||||
IS_MERGIFY=$(echo '${{ github.event.pull_request.user.login }}' | grep -iq 'mergify' && echo true || echo false)
|
|
||||||
TARGET_BRANCH=${{ github.event.pull_request.base.ref }}
|
|
||||||
|
|
||||||
echo "IS_MERGIFY=$IS_MERGIFY" >> $GITHUB_OUTPUT
|
|
||||||
echo "TARGET_BRANCH=$TARGET_BRANCH" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Add label if needed
|
|
||||||
if: steps.check.outputs.IS_MERGIFY == 'true' && startsWith(steps.check.outputs.TARGET_BRANCH, 'release-harvester-v')
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
TARGET_BRANCH="${{ steps.check.outputs.TARGET_BRANCH }}"
|
|
||||||
version="${TARGET_BRANCH#release-harvester-v}"
|
|
||||||
label="backport/v${version}"
|
|
||||||
echo "Adding label $label"
|
|
||||||
gh pr edit ${{ github.event.pull_request.number }} \
|
|
||||||
--repo ${{ github.repository }} \
|
|
||||||
--add-label "$label"
|
|
||||||
@@ -11,18 +11,19 @@ defaults:
|
|||||||
working-directory: ./
|
working-directory: ./
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
check-version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repository
|
||||||
with:
|
uses: actions/checkout@v4
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Check package version
|
||||||
uses: ./.github/actions/lint
|
env:
|
||||||
|
TAG_VERSION: ${{ github.event.release.tag_name }}
|
||||||
|
run: ./scripts/check-version.sh $TAG_VERSION
|
||||||
|
|
||||||
build-and-push-extension-catalog:
|
build-and-push-extension-catalog:
|
||||||
needs: lint
|
needs: check-version
|
||||||
uses: ./.github/workflows/build-extension-catalog.yml
|
uses: ./.github/workflows/build-extension-catalog.yml
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'release-harvester-v*'
|
- 'release-harvester-v*'
|
||||||
- 'main'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup-target-branch:
|
setup-target-branch:
|
||||||
@@ -18,17 +17,14 @@ jobs:
|
|||||||
- name: Determine target branch
|
- name: Determine target branch
|
||||||
id: get-version
|
id: get-version
|
||||||
run: |
|
run: |
|
||||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
if [[ "${{ github.ref_name }}" =~ ^release-harvester-v([0-9]+\.[0-9]+)$ ]]; then
|
||||||
TARGET_BRANCH="main-head"
|
|
||||||
elif [[ "${{ github.ref_name }}" =~ ^release-harvester-v([0-9]+\.[0-9]+)$ ]]; then
|
|
||||||
TARGET_BRANCH="v${BASH_REMATCH[1]}-head"
|
TARGET_BRANCH="v${BASH_REMATCH[1]}-head"
|
||||||
|
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_ENV
|
||||||
|
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_OUTPUT
|
||||||
else
|
else
|
||||||
echo "Error: invalid branch format." && exit 1
|
echo "Error: invalid branch format." && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_ENV
|
|
||||||
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Ensure target branch exists
|
- name: Ensure target branch exists
|
||||||
run: |
|
run: |
|
||||||
git fetch --all
|
git fetch --all
|
||||||
@@ -132,7 +128,7 @@ jobs:
|
|||||||
git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
|
git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
|
||||||
|
|
||||||
- name: Run Helm chart releaser
|
- name: Run Helm chart releaser
|
||||||
uses: helm/chart-releaser-action@v1.4.1
|
uses: helm/chart-releaser-action@v1.7.0
|
||||||
with:
|
with:
|
||||||
charts_dir: ./charts
|
charts_dir: ./charts
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -29,19 +29,20 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "Error: invalid tag format." && exit 1
|
echo "Error: invalid tag format." && exit 1
|
||||||
fi
|
fi
|
||||||
lint:
|
check-version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout repository
|
||||||
with:
|
uses: actions/checkout@v4
|
||||||
fetch-depth: 1
|
- name: Check package version
|
||||||
|
env:
|
||||||
|
TAG_VERSION: ${{ github.event.release.tag_name }}
|
||||||
|
run: ./scripts/check-version.sh $TAG_VERSION
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
uses: ./.github/actions/lint
|
|
||||||
build-extension-charts:
|
build-extension-charts:
|
||||||
needs:
|
needs:
|
||||||
- setup-release-tag
|
- setup-release-tag
|
||||||
- lint
|
- check-version
|
||||||
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
|
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
|
|||||||
@@ -5,9 +5,20 @@ on:
|
|||||||
- v[1-9].*
|
- v[1-9].*
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
check-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Check package version
|
||||||
|
env:
|
||||||
|
TAG_VERSION: ${{github.ref_name}}
|
||||||
|
run: ./scripts/check-version.sh $TAG_VERSION
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build and Upload Package
|
name: Build and Upload Package
|
||||||
uses: ./.github/workflows/build-and-publish-standalone.yaml
|
uses: ./.github/workflows/build-and-publish-standalone.yaml
|
||||||
|
needs: check-version
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|||||||
@@ -17,37 +17,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Need full history for commit-lint
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
uses: ./.github/actions/lint
|
uses: ./.github/actions/lint
|
||||||
|
|
||||||
- name: Validate commit messages
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME"
|
|
||||||
echo "GITHUB_BASE_SHA=$GITHUB_BASE_SHA"
|
|
||||||
echo "GITHUB_HEAD_SHA=$GITHUB_HEAD_SHA"
|
|
||||||
echo "GITHUB_BEFORE=$GITHUB_BEFORE"
|
|
||||||
echo "GITHUB_AFTER=$GITHUB_AFTER"
|
|
||||||
|
|
||||||
if [ "$GITHUB_EVENT_NAME" = "pull_request" ] && [ -n "$GITHUB_BASE_SHA" ] && [ -n "$GITHUB_HEAD_SHA" ]; then
|
|
||||||
FROM="$GITHUB_BASE_SHA"
|
|
||||||
TO="$GITHUB_HEAD_SHA"
|
|
||||||
elif [ -n "$GITHUB_BEFORE" ] && [ -n "$GITHUB_AFTER" ]; then
|
|
||||||
FROM="$GITHUB_BEFORE"
|
|
||||||
TO="$GITHUB_AFTER"
|
|
||||||
else
|
|
||||||
echo "No valid commit range found, skipping commitlint."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "FROM=$FROM"
|
|
||||||
echo "TO=$TO"
|
|
||||||
npx commitlint --from "$FROM" --to "$TO" --verbose
|
|
||||||
env:
|
|
||||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
|
||||||
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
||||||
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
||||||
GITHUB_BEFORE: ${{ github.event.before }}
|
|
||||||
GITHUB_AFTER: ${{ github.event.after }}
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
npx --no -- commitlint --edit $1
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
yarn lint
|
|
||||||
@@ -1,169 +1,62 @@
|
|||||||
# harvester-ui-extension
|
# harvester-ui-extension
|
||||||
|
|
||||||
The Harvester UI Extension is a Rancher extension that provides the user interface for [Harvester](https://harvesterhci.io) within the [Rancher Dashboard](https://github.com/rancher/dashboard).
|
Rancher Extension used in [rancher/dashboard](https://github.com/rancher/dashboard) for [Harvester](https://harvesterhci.io) Dashboard UI.
|
||||||
|
|
||||||
> **Note:**
|
`
|
||||||
> This extension is available starting from **Rancher 2.10.0**. Ensure your Rancher version is **2.10.0 or later** to access Harvester integration.
|
From Rancher 2.10.0, Harvester integration is provided by this new Harvester UI Extension.
|
||||||
|
`
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
For detailed installation instructions, please refer to the [official Harvester documentation](https://docs.harvesterhci.io/v1.5/rancher/harvester-ui-extension#installation-on-rancher-210).
|
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
|
||||||
|
|
||||||
## Development Setup
|
This is what you probably want to get started.
|
||||||
|
|
||||||
|
### Prerequisite
|
||||||
|
|
||||||
|
Nodejs version >= 20
|
||||||
|
|
||||||
Ensure **Node.js v20 or later** is installed for development and debugging.
|
|
||||||
|
|
||||||
### Standalone Mode
|
### Standalone Mode
|
||||||
|
|
||||||
Run the extension standalone with hot reload at `https://localhost:8005`.
|
For development, serve with hot reload at https://localhost:8005
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
# Start the development server
|
# Use RANCHER_ENV to specify the endpoint for your Harvester API
|
||||||
RANCHER_ENV=harvester API=https://your-harvester-ip yarn dev
|
RANCHER_ENV=harvester API=https://your-harvester-ip yarn dev
|
||||||
|
|
||||||
# Example with specific server version
|
# Use VUE_APP_SERVER_VERSION to specify the Harvester server version
|
||||||
RANCHER_ENV=harvester VUE_APP_SERVER_VERSION=v1.5.0 API=https://192.168.1.123 yarn dev
|
# 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
|
||||||
|
|
||||||
You may also define environment variables in a `.env` file:
|
# or put your env variables into a .env file
|
||||||
|
|
||||||
```env
|
# Example
|
||||||
RANCHER_ENV=harvester
|
RANCHER_ENV=harvester VUE_APP_SERVER_VERSION=v1.4.0 API=https://192.168.1.123 yarn dev
|
||||||
VUE_APP_SERVER_VERSION=v1.5.0
|
|
||||||
API=https://192.168.1.123
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Rancher Integration Mode
|
### Rancher Integration Mode
|
||||||
|
|
||||||
To run as a Rancher extension, follow the [Rancher UI Extension Guide](https://extensions.rancher.io/extensions/next/extensions-getting-started#running-the-app).
|
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
|
```bash
|
||||||
API=https://your-rancher-ip yarn dev
|
API=https://your-rancher-ip yarn dev
|
||||||
|
|
||||||
|
# Example
|
||||||
|
API=https://192.168.1.123 yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commit Message Guidelines
|
|
||||||
|
|
||||||
This project uses [commit-lint](https://commitlint.js.org/) with [Conventional Commits](https://www.conventionalcommits.org/) to ensure consistent and meaningful commit messages.
|
|
||||||
|
|
||||||
### Commit Message Format
|
|
||||||
|
|
||||||
All commit messages must follow the conventional commit format:
|
|
||||||
|
|
||||||
```
|
|
||||||
<type>[optional scope]: <description>
|
|
||||||
|
|
||||||
[optional body]
|
|
||||||
|
|
||||||
[optional footer(s)]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Supported Types
|
|
||||||
|
|
||||||
- **feat**: New features
|
|
||||||
- **fix**: Bug fixes
|
|
||||||
- **docs**: Documentation changes
|
|
||||||
- **style**: Code style changes (formatting, missing semicolons, etc.)
|
|
||||||
- **refactor**: Code refactoring
|
|
||||||
- **perf**: Performance improvements
|
|
||||||
- **test**: Adding or updating tests
|
|
||||||
- **build**: Build system or external dependencies
|
|
||||||
- **ci**: CI/CD changes
|
|
||||||
- **chore**: Other changes that don't modify src or test files
|
|
||||||
- **revert**: Reverts a previous commit
|
|
||||||
- **wip**: Work in progress
|
|
||||||
- **deps**: Dependency updates
|
|
||||||
- **security**: Security fixes
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Feature
|
|
||||||
git commit -m "feat: add new virtual machine creation wizard"
|
|
||||||
|
|
||||||
# Bug fix
|
|
||||||
git commit -m "fix: resolve memory leak in VM console"
|
|
||||||
|
|
||||||
# Documentation
|
|
||||||
git commit -m "docs: update installation instructions"
|
|
||||||
|
|
||||||
# Breaking change
|
|
||||||
git commit -m "feat!: change API endpoint structure
|
|
||||||
|
|
||||||
BREAKING CHANGE: The /api/v1/vms endpoint has been replaced with /api/v2/vms"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Git Hooks
|
|
||||||
|
|
||||||
The project uses [Husky](https://typicode.github.io/husky/) to automatically validate commit messages and run linting before commits:
|
|
||||||
|
|
||||||
- **pre-commit**: Runs ESLint to ensure code quality
|
|
||||||
- **commit-msg**: Validates commit message format using commit-lint
|
|
||||||
|
|
||||||
These hooks are automatically installed when you run `yarn install`.
|
|
||||||
|
|
||||||
### Manual Validation
|
|
||||||
|
|
||||||
You can manually validate commit messages:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Validate the last commit
|
|
||||||
yarn commitlint
|
|
||||||
|
|
||||||
# Validate a specific commit
|
|
||||||
npx commitlint --from <commit-hash>
|
|
||||||
|
|
||||||
# Validate a range of commits
|
|
||||||
npx commitlint --from <start-hash> --to <end-hash>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Branch Structure
|
|
||||||
|
|
||||||
- **`main`** – Main development branch
|
|
||||||
- **`release-harvester-vX.Y`** – Stable release branches per version series
|
|
||||||
- **`vX.Y-head`** – Testing branches for ongoing changes to extension builds in each release series
|
|
||||||
|
|
||||||
> **Note:**
|
|
||||||
> The `vX.Y-head` branches are auto-generated and kept in sync with release branches. Use these for testing the latest changes in each version series.
|
|
||||||
|
|
||||||
## Testing Guidelines
|
|
||||||
|
|
||||||
### UI Extension Testing
|
|
||||||
|
|
||||||
To validate changes in a release series, switch to the appropriate `vX.Y-head` branch. For main branch testing, use `main-head`.
|
|
||||||
|
|
||||||
- Examples:
|
|
||||||
- Test `1.0.x` series → `v1.0-head`
|
|
||||||
- Test `1.5.x` series → `v1.5-head`
|
|
||||||
- Test `1.6.x` series → `v1.6-head`
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
1. Navigate to **Rancher UI** → **Local** → **App** → **Repositories**
|
|
||||||
2. Refresh the Harvester repository using the target `vX.Y-head` branch
|
|
||||||
3. Go to the **Extensions** page and install the desired version
|
|
||||||
|
|
||||||
### Standalone Mode Testing
|
|
||||||
|
|
||||||
To test the standalone UI, configure Harvester to load the UI from an external source.
|
|
||||||
|
|
||||||
- Examples of `ui-index`:
|
|
||||||
- Main branch → `https://releases.rancher.com/harvester-ui/dashboard/latest/index.html`
|
|
||||||
- Release series `1.5.x` → `https://releases.rancher.com/harvester-ui/dashboard/release-harvester-v1.5/index.html`
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
1. Go to **Harvester UI** → **Advanced** → **Settings** → **UI**
|
|
||||||
2. Set **ui-source** to `External`
|
|
||||||
3. Set **ui-index** to the desired URL
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
If you want to contribute, start by reading this document, then visit our [Getting Started guide](https://extensions.rancher.io/extensions/next/extensions-getting-started) to learn how to develop and submit changes.
|
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
|
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
Copyright (c) 2014-2025 [SUSE, LLC.](https://www.suse.com/)
|
Copyright (c) 2014-2025 [SUSE, LLC.](https://www.suse.com/)
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['@commitlint/config-conventional'],
|
|
||||||
rules: {
|
|
||||||
// Enforce conventional commit format
|
|
||||||
'type-enum': [
|
|
||||||
2,
|
|
||||||
'always',
|
|
||||||
[
|
|
||||||
'feat', // New features
|
|
||||||
'fix', // Bug fixes
|
|
||||||
'docs', // Documentation changes
|
|
||||||
'style', // Code style changes (formatting, missing semicolons, etc.)
|
|
||||||
'refactor', // Code refactoring
|
|
||||||
'perf', // Performance improvements
|
|
||||||
'test', // Adding or updating tests
|
|
||||||
'build', // Build system or external dependencies
|
|
||||||
'ci', // CI/CD changes
|
|
||||||
'chore', // Other changes that don't modify src or test files
|
|
||||||
'revert', // Reverts a previous commit
|
|
||||||
'wip', // Work in progress
|
|
||||||
'deps', // Dependency updates
|
|
||||||
'security', // Security fixes
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'type-case': [2, 'always', 'lower-case'],
|
|
||||||
'type-empty': [2, 'never'],
|
|
||||||
'subject-case': [0, 'never'],
|
|
||||||
'subject-empty': [2, 'never'],
|
|
||||||
'subject-full-stop': [2, 'never', '.'],
|
|
||||||
'subject-max-length': [2, 'always', 72],
|
|
||||||
'body-leading-blank': [2, 'always'],
|
|
||||||
'body-max-line-length': [2, 'always', 100],
|
|
||||||
'footer-leading-blank': [2, 'always'],
|
|
||||||
'footer-max-line-length': [2, 'always', 100],
|
|
||||||
},
|
|
||||||
// Ignore merge commits and revert commits
|
|
||||||
ignores: [
|
|
||||||
(commit) => commit.includes('Merge'),
|
|
||||||
(commit) => commit.includes('Revert'),
|
|
||||||
(commit) => commit.includes('merge'),
|
|
||||||
(commit) => commit.includes('revert'),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
+13
-18
@@ -1,36 +1,36 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester-ui-extension",
|
"name": "harvester-ui-extension",
|
||||||
"version": "1.6.0-rc1",
|
"version": "1.5.2-rc2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-transform-class-static-block": "7.26.0",
|
"@rancher/shell": "3.0.2-rc.6",
|
||||||
"@rancher/shell": "3.0.5-rc.3",
|
|
||||||
"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",
|
||||||
"vue-draggable-next": "^2.2.1",
|
"vue-draggable-next": "^2.2.1",
|
||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1",
|
||||||
|
"@babel/plugin-transform-class-static-block": "7.28.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "~20.10.0",
|
"@types/node": "~20.19.0",
|
||||||
"cronstrue": "2.50.0",
|
"cronstrue": "2.59.0",
|
||||||
"d3-color": "3.1.0",
|
"d3-color": "3.1.0",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"follow-redirects": "1.15.6",
|
"follow-redirects": "1.15.11",
|
||||||
"glob": "7.2.3",
|
"glob": "7.2.3",
|
||||||
"glob-parent": "6.0.2",
|
"glob-parent": "6.0.2",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"@types/lodash": "4.17.5",
|
"@types/lodash": "4.17.20",
|
||||||
"merge": "2.1.1",
|
"merge": "2.1.1",
|
||||||
"node-forge": "1.3.1",
|
"node-forge": "1.3.1",
|
||||||
"nth-check": "2.1.1",
|
"nth-check": "2.1.1",
|
||||||
"qs": "6.11.1",
|
"qs": "6.14.0",
|
||||||
"roarr": "7.0.4",
|
"roarr": "7.21.1",
|
||||||
"semver": "7.5.4",
|
"semver": "7.7.2",
|
||||||
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -41,15 +41,10 @@
|
|||||||
"build-pkg": "./node_modules/@rancher/shell/scripts/build-pkg.sh",
|
"build-pkg": "./node_modules/@rancher/shell/scripts/build-pkg.sh",
|
||||||
"serve-pkgs": "./node_modules/@rancher/shell/scripts/serve-pkgs",
|
"serve-pkgs": "./node_modules/@rancher/shell/scripts/serve-pkgs",
|
||||||
"publish-pkgs": "./node_modules/@rancher/shell/scripts/extension/publish",
|
"publish-pkgs": "./node_modules/@rancher/shell/scripts/extension/publish",
|
||||||
"parse-tag-name": "./node_modules/@rancher/shell/scripts/extension/parse-tag-name",
|
"parse-tag-name": "./node_modules/@rancher/shell/scripts/extension/parse-tag-name"
|
||||||
"commitlint": "commitlint --edit",
|
|
||||||
"prepare": "husky"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^19.8.1",
|
|
||||||
"@commitlint/config-conventional": "^19.8.1",
|
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^7.1.0",
|
"eslint-plugin-promise": "^7.1.0"
|
||||||
"husky": "^9.1.7"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import ActionMenu from '@shell/components/ActionMenuShell';
|
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import AsyncButton from '@shell/components/AsyncButton';
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
import { HCI_ALLOWED_SETTINGS, HCI_SINGLE_CLUSTER_ALLOWED_SETTING, HCI_SETTING } from '../config/settings';
|
import { HCI_ALLOWED_SETTINGS, HCI_SETTING } from '../config/settings';
|
||||||
import { DOC } from '../config/doc-links';
|
|
||||||
import { docLink } from '../utils/feature-flags';
|
|
||||||
|
|
||||||
const CATEGORY = {
|
const CATEGORY = {
|
||||||
ui: [
|
ui: [
|
||||||
@@ -22,7 +19,6 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
AsyncButton,
|
AsyncButton,
|
||||||
Banner,
|
Banner,
|
||||||
ActionMenu
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -34,23 +30,15 @@ export default {
|
|||||||
category: {
|
category: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
|
|
||||||
searchQuery: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
const categorySettings = this.filterCategorySettings();
|
const categorySettings = this.filterCategorySettings();
|
||||||
const filteredSettings = this.filterSearchSettings(categorySettings, this.searchQuery);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
HCI_SETTING,
|
HCI_SETTING,
|
||||||
categorySettings,
|
categorySettings,
|
||||||
filteredSettings,
|
|
||||||
originalHideMap: this.createHideMap(categorySettings)
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -60,81 +48,12 @@ export default {
|
|||||||
settings: {
|
settings: {
|
||||||
deep: true,
|
deep: true,
|
||||||
handler() {
|
handler() {
|
||||||
this.categorySettings = this.filterCategorySettings();
|
this['categorySettings'] = this.filterCategorySettings();
|
||||||
this.filteredSettings = this.filterSearchSettings(this.categorySettings, this.searchQuery);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
searchQuery: {
|
|
||||||
immediate: true,
|
|
||||||
handler(newQuery) {
|
|
||||||
const filtered = this.filterSearchSettings(this.categorySettings, newQuery);
|
|
||||||
|
|
||||||
this.filteredSettings = newQuery ? this.openJsonSettings(filtered) : filtered.map((s) => ({ ...s, hide: this.originalHideMap[s.id] ?? false }));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
createHideMap(settings = []) {
|
|
||||||
const map = settings.reduce((acc, s) => {
|
|
||||||
acc[s.id] = s.hide ?? false;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {} );
|
|
||||||
|
|
||||||
return map;
|
|
||||||
},
|
|
||||||
filterSearchSettings(settings, searchKey) {
|
|
||||||
if (!searchKey) {
|
|
||||||
return this.filterCategorySettings();
|
|
||||||
}
|
|
||||||
const searchQuery = searchKey.toLowerCase();
|
|
||||||
|
|
||||||
return settings.filter((setting) => {
|
|
||||||
const id = setting.id?.toLowerCase() || '';
|
|
||||||
|
|
||||||
// filter by id
|
|
||||||
if (id.includes(searchQuery) ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const description = this.t(setting.description, this.getDocLinkParams(setting) || {}, true)?.toLowerCase() || '';
|
|
||||||
|
|
||||||
// filter by description
|
|
||||||
if (description.includes(searchQuery)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter by customized value
|
|
||||||
if (setting.customized === true && setting.data?.value) {
|
|
||||||
const value = setting.data.value?.toLowerCase() || '';
|
|
||||||
|
|
||||||
return value.includes(searchQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter by json value
|
|
||||||
if (setting.kind === 'json' && setting.json) {
|
|
||||||
try {
|
|
||||||
const json = JSON.parse(setting.json);
|
|
||||||
const jsonString = JSON.stringify(json).toLowerCase();
|
|
||||||
|
|
||||||
return jsonString.includes(searchQuery);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`${ setting.id }: wrong format`, e); // eslint-disable-line no-console
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter by default value
|
|
||||||
if (setting.data?.default) {
|
|
||||||
return setting.data?.default.includes(searchQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
filterCategorySettings() {
|
filterCategorySettings() {
|
||||||
return this.settings.filter((s) => {
|
return this.settings.filter((s) => {
|
||||||
if (!this.getFeatureEnabled(s.featureFlag)) {
|
if (!this.getFeatureEnabled(s.featureFlag)) {
|
||||||
@@ -155,21 +74,25 @@ export default {
|
|||||||
return id ? this.$store.getters['harvester-common/getFeatureEnabled'](id) : true;
|
return id ? this.$store.getters['harvester-common/getFeatureEnabled'](id) : true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showActionMenu(e, setting) {
|
||||||
|
const actionElement = e.srcElement;
|
||||||
|
|
||||||
|
this.$store.commit(`action-menu/show`, {
|
||||||
|
resources: setting.data,
|
||||||
|
elem: actionElement
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getSettingOption(id) {
|
getSettingOption(id) {
|
||||||
return HCI_ALLOWED_SETTINGS.find((setting) => setting.id === id);
|
return HCI_ALLOWED_SETTINGS.find((setting) => setting.id === id);
|
||||||
},
|
},
|
||||||
|
|
||||||
openJsonSettings(settings) {
|
|
||||||
return settings.map((s) => s.hide ? { ...s, hide: false } : s);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleHide(s) {
|
toggleHide(s) {
|
||||||
const setting = this.filteredSettings.find((setting) => setting.id === s.id);
|
this.categorySettings.find((setting) => {
|
||||||
|
if (setting.id === s.id) {
|
||||||
if (setting) {
|
setting.hide = !setting.hide;
|
||||||
setting.hide = !setting.hide;
|
}
|
||||||
this.originalHideMap[setting.id] = setting.hide;
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async testConnect(buttonDone, value) {
|
async testConnect(buttonDone, value) {
|
||||||
@@ -195,19 +118,6 @@ export default {
|
|||||||
}
|
}
|
||||||
buttonDone(false);
|
buttonDone(false);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
getDocLinkParams(setting) {
|
|
||||||
const settingConfig = HCI_ALLOWED_SETTINGS[setting.id] || HCI_SINGLE_CLUSTER_ALLOWED_SETTING[setting.id];
|
|
||||||
|
|
||||||
if (settingConfig?.docPath) {
|
|
||||||
const version = this.$store.getters['harvester-common/getServerVersion']();
|
|
||||||
const url = docLink(DOC[settingConfig.docPath], version);
|
|
||||||
|
|
||||||
return { url };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -216,7 +126,7 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-for="(setting, i) in filteredSettings"
|
v-for="(setting, i) in categorySettings"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="advanced-setting mb-20"
|
class="advanced-setting mb-20"
|
||||||
>
|
>
|
||||||
@@ -238,7 +148,7 @@ export default {
|
|||||||
Experimental
|
Experimental
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<h2 v-clean-html="t(setting.description, getDocLinkParams(setting) || {}, true)">
|
<h2 v-clean-html="t(setting.description, {}, true)">
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -246,12 +156,15 @@ export default {
|
|||||||
:id="setting.id"
|
:id="setting.id"
|
||||||
class="action"
|
class="action"
|
||||||
>
|
>
|
||||||
<ActionMenu
|
<button
|
||||||
:resource="setting.data"
|
aria-haspopup="true"
|
||||||
:button-aria-label="t('advancedSettings.edit.label')"
|
aria-expanded="false"
|
||||||
data-testid="action-button"
|
type="button"
|
||||||
button-role="tertiary"
|
class="btn btn-sm role-multi-action actions"
|
||||||
/>
|
@click="showActionMenu($event, setting)"
|
||||||
|
>
|
||||||
|
<i class="icon icon-actions" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div value>
|
<div value>
|
||||||
@@ -308,12 +221,6 @@ export default {
|
|||||||
{{ setting.data.errMessage }}
|
{{ setting.data.errMessage }}
|
||||||
</Banner>
|
</Banner>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
v-if="filteredSettings.length === 0"
|
|
||||||
class="advanced-setting mb-20 no-search-match"
|
|
||||||
>
|
|
||||||
<p> {{ t('harvester.setting.noSearchMatch') }} </p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -364,8 +271,4 @@ export default {
|
|||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-search-match {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { NAMESPACE } from '@shell/config/types';
|
|||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
|
|
||||||
const SELECT_ALL = 'select_all';
|
|
||||||
const UNSELECT_ALL = 'unselect_all';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterBundleNamespaces',
|
name: 'HarvesterBundleNamespaces',
|
||||||
|
|
||||||
@@ -14,76 +11,47 @@ export default {
|
|||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView],
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
await this.$store.dispatch('harvester/findAll', { type: NAMESPACE });
|
await this.$store.dispatch('harvester/findAll', { type: NAMESPACE });
|
||||||
|
|
||||||
try {
|
|
||||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl']('v1/harvester/namespaces?link=supportbundle');
|
|
||||||
const response = await this.$store.dispatch('harvester/request', { url });
|
|
||||||
|
|
||||||
this.defaultNamespaces = response.data || [];
|
|
||||||
} catch (error) {
|
|
||||||
this.defaultNamespaces = [];
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
let namespaces = [];
|
||||||
const namespacesStr = this.value?.value || this.value?.default || '';
|
const namespacesStr = this.value?.value || this.value?.default || '';
|
||||||
const namespaces = namespacesStr ? namespacesStr.split(',') : [];
|
|
||||||
|
|
||||||
return {
|
if (namespacesStr) {
|
||||||
namespaces,
|
namespaces = namespacesStr.split(',');
|
||||||
defaultNamespaces: [],
|
}
|
||||||
loading: true
|
|
||||||
};
|
return { namespaces };
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
allNamespaces() {
|
|
||||||
return this.$store.getters['harvester/all'](NAMESPACE).map((ns) => ns.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
filteredNamespaces() {
|
|
||||||
const defaultIds = this.defaultNamespaces.map((ns) => ns.id);
|
|
||||||
|
|
||||||
return this.allNamespaces.filter((ns) => !defaultIds.includes(ns));
|
|
||||||
},
|
|
||||||
|
|
||||||
namespaceOptions() {
|
namespaceOptions() {
|
||||||
if (this.availableNamespaces.length === 0) return [];
|
return this.$store.getters['harvester/all'](NAMESPACE).map((N) => {
|
||||||
|
return {
|
||||||
const allSelected =
|
label: N.id,
|
||||||
this.namespaces.length === this.filteredNamespaces.length &&
|
value: N.id
|
||||||
this.filteredNamespaces.every((ns) => this.namespaces.includes(ns));
|
};
|
||||||
|
});
|
||||||
const controlOption = allSelected ? { label: this.t('harvester.modal.bundle.namespaces.unselectAll'), value: UNSELECT_ALL } : { label: this.t('harvester.modal.bundle.namespaces.selectAll'), value: SELECT_ALL };
|
|
||||||
|
|
||||||
return [controlOption, ...this.filteredNamespaces];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
update(selected) {
|
update() {
|
||||||
if (selected.includes(SELECT_ALL)) {
|
const namespaceStr = this.namespaces.join(',');
|
||||||
this.namespaces = [...this.filteredNamespaces];
|
|
||||||
} else if (selected.includes(UNSELECT_ALL)) {
|
|
||||||
this.namespaces = [];
|
|
||||||
} else {
|
|
||||||
this.namespaces = selected.filter((val) => val !== SELECT_ALL && val !== UNSELECT_ALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value.value = this.namespaces.join(',');
|
this.value['value'] = namespaceStr;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'value.value'(newVal) {
|
'value.value': {
|
||||||
const raw = newVal || this.value.default || '';
|
handler(neu) {
|
||||||
|
if (neu === this.value.default || !neu) {
|
||||||
this.namespaces = raw ? raw.split(',') : [];
|
this.namespaces = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -94,7 +62,6 @@ export default {
|
|||||||
<div class="col span-12">
|
<div class="col span-12">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model:value="namespaces"
|
v-model:value="namespaces"
|
||||||
:loading="loading"
|
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
label-key="nameNsDescription.namespace.label"
|
label-key="nameNsDescription.namespace.label"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import { RadioGroup } from '@components/Form/Radio';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterUpgradeConfig',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
LabeledInput,
|
|
||||||
LabeledSelect,
|
|
||||||
RadioGroup
|
|
||||||
},
|
|
||||||
mixins: [CreateEditView],
|
|
||||||
|
|
||||||
data() {
|
|
||||||
let parseDefaultValue = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
parseDefaultValue = this.value.value ? JSON.parse(this.value.value) : JSON.parse(this.value.default);
|
|
||||||
} catch (error) {
|
|
||||||
parseDefaultValue = JSON.parse(this.value.default);
|
|
||||||
}
|
|
||||||
parseDefaultValue = this.normalizeValue(parseDefaultValue);
|
|
||||||
|
|
||||||
return {
|
|
||||||
parseDefaultValue,
|
|
||||||
errors: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
strategyOptions() {
|
|
||||||
return [
|
|
||||||
{ value: 'sequential', label: 'sequential' },
|
|
||||||
{ value: 'skip', label: 'skip' },
|
|
||||||
{ value: 'parallel', label: 'parallel' }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created() {
|
|
||||||
this.update();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
normalizeValue(obj) {
|
|
||||||
if (!obj.imagePreloadOption) {
|
|
||||||
obj.imagePreloadOption = { strategy: { type: 'sequential' } };
|
|
||||||
}
|
|
||||||
if (!obj.imagePreloadOption.strategy) {
|
|
||||||
obj.imagePreloadOption.strategy = { type: 'sequential' };
|
|
||||||
}
|
|
||||||
if (!obj.imagePreloadOption.strategy.type) {
|
|
||||||
obj.imagePreloadOption.strategy.type = 'sequential';
|
|
||||||
}
|
|
||||||
// Only set concurrency if type is 'parallel'
|
|
||||||
if (obj.imagePreloadOption.strategy.type === 'parallel') {
|
|
||||||
if (typeof obj.imagePreloadOption.strategy.concurrency !== 'number') {
|
|
||||||
obj.imagePreloadOption.strategy.concurrency = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete obj.imagePreloadOption.strategy.concurrency;
|
|
||||||
}
|
|
||||||
if (typeof obj.restoreVM !== 'boolean') {
|
|
||||||
obj.restoreVM = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
update() {
|
|
||||||
try {
|
|
||||||
// Clone to avoid mutating the form state
|
|
||||||
const valueToSave = JSON.parse(JSON.stringify(this.parseDefaultValue));
|
|
||||||
|
|
||||||
if (valueToSave.imagePreloadOption && valueToSave.imagePreloadOption.strategy) {
|
|
||||||
if (valueToSave.imagePreloadOption.strategy.type !== 'parallel') {
|
|
||||||
delete valueToSave.imagePreloadOption.strategy.concurrency;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value['value'] = JSON.stringify(valueToSave, null, 2);
|
|
||||||
this.errors = [];
|
|
||||||
} catch (e) {
|
|
||||||
this.errors = ['Invalid JSON'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
value: {
|
|
||||||
handler(neu) {
|
|
||||||
let parseDefaultValue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
parseDefaultValue = JSON.parse(neu.value);
|
|
||||||
} catch (err) {
|
|
||||||
parseDefaultValue = JSON.parse(this.value.default);
|
|
||||||
}
|
|
||||||
parseDefaultValue = this.normalizeValue(parseDefaultValue);
|
|
||||||
this['parseDefaultValue'] = parseDefaultValue;
|
|
||||||
this.update();
|
|
||||||
},
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="row"
|
|
||||||
@input="update"
|
|
||||||
>
|
|
||||||
<div class="col span-12">
|
|
||||||
<label class="mb-5"><b>{{ t('harvester.setting.upgrade.imagePreloadStrategy') }}</b></label>
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="parseDefaultValue.imagePreloadOption.strategy.type"
|
|
||||||
class="mb-20"
|
|
||||||
:mode="mode"
|
|
||||||
:label="t('harvester.setting.upgrade.strategyType')"
|
|
||||||
:options="strategyOptions"
|
|
||||||
@update:value="update"
|
|
||||||
/>
|
|
||||||
<LabeledInput
|
|
||||||
v-if="parseDefaultValue.imagePreloadOption.strategy.type === 'parallel'"
|
|
||||||
v-model:value.number="parseDefaultValue.imagePreloadOption.strategy.concurrency"
|
|
||||||
class="mb-20"
|
|
||||||
:mode="mode"
|
|
||||||
:label="t('harvester.setting.upgrade.concurrency')"
|
|
||||||
min="0"
|
|
||||||
type="number"
|
|
||||||
/>
|
|
||||||
<label class="mb-5"><b>{{ t('harvester.setting.upgrade.restoreVM') }}</b></label>
|
|
||||||
<RadioGroup
|
|
||||||
v-model:value="parseDefaultValue.restoreVM"
|
|
||||||
class="mb-20"
|
|
||||||
name="restoreVM"
|
|
||||||
:options="[true, false]"
|
|
||||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
|
||||||
@update:value="update"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-if="errors.length"
|
|
||||||
class="error"
|
|
||||||
>
|
|
||||||
{{ errors[0] }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.error {
|
|
||||||
color: #d9534f;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
// suffix of doc link, see utils/feature-flags.js how to get complete doc link
|
// suffix of doc link, see utils/feature-flags.js how to get complete doc link
|
||||||
export const DOC = {
|
export const DOC = {
|
||||||
CONSOLE_URL: `/host/#remote-console`,
|
CONSOLE_URL: `/host/#remote-console`,
|
||||||
RANCHER_INTEGRATION_URL: `/rancher/rancher-integration`,
|
RANCHER_INTEGRATION_URL: `/rancher/rancher-integration`,
|
||||||
KSMTUNED_MODE: `/host/#ksmtuned-mode`,
|
STORAGE_NETWORK_EXAMPLE: `/advanced/storagenetwork#configuration-example`,
|
||||||
UPGRADE_URL: `/upgrade/index`,
|
KSMTUNED_MODE: `/host/#ksmtuned-mode`,
|
||||||
UPGRADE_CONFIG_URL: `/advanced/index#upgrade-config`,
|
UPGRADE_URL: `/upgrade/index`
|
||||||
STORAGE_NETWORK_EXAMPLE: `/advanced/storagenetwork#configuration-example`,
|
|
||||||
SUPPORT_BUNDLE_NAMESPACES: `/advanced/index/#support-bundle-namespaces`,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ const FEATURE_FLAGS = {
|
|||||||
'vmSnapshotQuota',
|
'vmSnapshotQuota',
|
||||||
'longhornV2LVMSupport',
|
'longhornV2LVMSupport',
|
||||||
'improveMaintenanceMode',
|
'improveMaintenanceMode',
|
||||||
'upgradeConfigSetting'
|
|
||||||
],
|
],
|
||||||
'v1.4.1': [],
|
'v1.4.1': [],
|
||||||
'v1.4.2': [
|
'v1.4.2': [
|
||||||
@@ -34,14 +33,9 @@ const FEATURE_FLAGS = {
|
|||||||
'efiPersistentState',
|
'efiPersistentState',
|
||||||
'untaggedNetworkSetting',
|
'untaggedNetworkSetting',
|
||||||
'skipSingleReplicaDetachedVol',
|
'skipSingleReplicaDetachedVol',
|
||||||
'thirdPartyStorage',
|
'thirdPartyStorage'
|
||||||
'liveMigrationProgress'
|
|
||||||
],
|
],
|
||||||
'v1.5.1': [],
|
'v1.5.1': [],
|
||||||
'v1.6.0': [
|
|
||||||
'vmMachineTypes',
|
|
||||||
'customSupportBundle'
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateFeatureFlags = () => {
|
const generateFeatureFlags = () => {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
CONFIGURED_PROVIDERS,
|
CONFIGURED_PROVIDERS,
|
||||||
SUB_TYPE,
|
SUB_TYPE,
|
||||||
ADDRESS,
|
ADDRESS,
|
||||||
DESCRIPTION,
|
|
||||||
} from '@shell/config/table-headers';
|
} from '@shell/config/table-headers';
|
||||||
import { IF_HAVE } from '@shell/store/type-map';
|
import { IF_HAVE } from '@shell/store/type-map';
|
||||||
import {
|
import {
|
||||||
@@ -33,8 +32,7 @@ import {
|
|||||||
IMAGE_PROGRESS,
|
IMAGE_PROGRESS,
|
||||||
SNAPSHOT_TARGET_VOLUME,
|
SNAPSHOT_TARGET_VOLUME,
|
||||||
IMAGE_VIRTUAL_SIZE,
|
IMAGE_VIRTUAL_SIZE,
|
||||||
IMAGE_STORAGE_CLASS,
|
IMAGE_STORAGE_CLASS
|
||||||
HARVESTER_DESCRIPTION
|
|
||||||
} from './table-headers';
|
} from './table-headers';
|
||||||
|
|
||||||
const TEMPLATE = HCI.VM_VERSION;
|
const TEMPLATE = HCI.VM_VERSION;
|
||||||
@@ -224,7 +222,6 @@ export function init($plugin, store) {
|
|||||||
STATE,
|
STATE,
|
||||||
NAME_COL,
|
NAME_COL,
|
||||||
NAMESPACE_COL,
|
NAMESPACE_COL,
|
||||||
HARVESTER_DESCRIPTION,
|
|
||||||
IMAGE_STORAGE_CLASS,
|
IMAGE_STORAGE_CLASS,
|
||||||
IMAGE_PROGRESS,
|
IMAGE_PROGRESS,
|
||||||
IMAGE_DOWNLOAD_SIZE,
|
IMAGE_DOWNLOAD_SIZE,
|
||||||
@@ -258,7 +255,7 @@ export function init($plugin, store) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isSingleVirtualCluster) {
|
if (isSingleVirtualCluster) {
|
||||||
headers(NAMESPACE, [STATE, NAME_UNLINKED, DESCRIPTION, AGE]);
|
headers(NAMESPACE, [STATE, NAME_UNLINKED, AGE]);
|
||||||
basicType([NAMESPACE]);
|
basicType([NAMESPACE]);
|
||||||
virtualType({
|
virtualType({
|
||||||
labelKey: 'harvester.namespace.label',
|
labelKey: 'harvester.namespace.label',
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export const HCI = {
|
|||||||
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
|
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
|
||||||
NETWORK_ROUTE: 'network.harvesterhci.io/route',
|
NETWORK_ROUTE: 'network.harvesterhci.io/route',
|
||||||
MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
|
MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
|
||||||
UPGRADE: 'harvesterhci.io/upgrade',
|
|
||||||
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
|
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
|
||||||
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
|
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
|
||||||
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
|
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
|
||||||
@@ -67,8 +66,5 @@ export const HCI = {
|
|||||||
VM_DEVICE_ALLOCATION_DETAILS: 'harvesterhci.io/deviceAllocationDetails',
|
VM_DEVICE_ALLOCATION_DETAILS: 'harvesterhci.io/deviceAllocationDetails',
|
||||||
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
|
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
|
||||||
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine',
|
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine',
|
||||||
K8S_ARCH: 'kubernetes.io/arch',
|
K8S_ARCH: 'kubernetes.io/arch'
|
||||||
IMAGE_DISPLAY_NAME: 'harvesterhci.io/imageDisplayName',
|
|
||||||
CUSTOM_IP: 'harvesterhci.io/custom-ip',
|
|
||||||
IMPORTED_IMAGE: 'migration.harvesterhci.io/imported'
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export const HCI_SETTING = {
|
|||||||
KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES: 'kubeconfig-default-token-ttl-minutes',
|
KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES: 'kubeconfig-default-token-ttl-minutes',
|
||||||
LONGHORN_V2_DATA_ENGINE_ENABLED: 'longhorn-v2-data-engine-enabled',
|
LONGHORN_V2_DATA_ENGINE_ENABLED: 'longhorn-v2-data-engine-enabled',
|
||||||
ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO: 'additional-guest-memory-overhead-ratio',
|
ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO: 'additional-guest-memory-overhead-ratio',
|
||||||
UPGRADE_CONFIG: 'upgrade-config',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HCI_ALLOWED_SETTINGS = {
|
export const HCI_ALLOWED_SETTINGS = {
|
||||||
@@ -62,9 +61,9 @@ export const HCI_ALLOWED_SETTINGS = {
|
|||||||
kind: 'multiline', canReset: true, from: 'import'
|
kind: 'multiline', canReset: true, from: 'import'
|
||||||
},
|
},
|
||||||
[HCI_SETTING.OVERCOMMIT_CONFIG]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.OVERCOMMIT_CONFIG]: { kind: 'json', from: 'import' },
|
||||||
[HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT]: { kind: 'number' },
|
[HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT]: {},
|
||||||
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: { kind: 'number' },
|
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
|
||||||
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: { kind: 'number', 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]: {
|
[HCI_SETTING.STORAGE_NETWORK]: {
|
||||||
kind: 'custom', from: 'import', canReset: true
|
kind: 'custom', from: 'import', canReset: true
|
||||||
@@ -90,19 +89,13 @@ export const HCI_ALLOWED_SETTINGS = {
|
|||||||
[HCI_SETTING.NTP_SERVERS]: {
|
[HCI_SETTING.NTP_SERVERS]: {
|
||||||
kind: 'json', from: 'import', canReset: true
|
kind: 'json', from: 'import', canReset: true
|
||||||
},
|
},
|
||||||
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { kind: 'number', featureFlag: 'kubeconfigDefaultTokenTTLMinutesSetting' },
|
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { featureFlag: 'kubeconfigDefaultTokenTTLMinutesSetting' },
|
||||||
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: {
|
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: {
|
||||||
kind: 'boolean',
|
kind: 'boolean',
|
||||||
experimental: true,
|
experimental: true,
|
||||||
featureFlag: 'longhornV2LVMSupport'
|
featureFlag: 'longhornV2LVMSupport'
|
||||||
},
|
},
|
||||||
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
|
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
|
||||||
[HCI_SETTING.UPGRADE_CONFIG]: {
|
|
||||||
kind: 'json',
|
|
||||||
from: 'import',
|
|
||||||
featureFlag: 'upgradeConfigSetting',
|
|
||||||
docPath: 'UPGRADE_CONFIG_URL'
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HCI_SINGLE_CLUSTER_ALLOWED_SETTING = {
|
export const HCI_SINGLE_CLUSTER_ALLOWED_SETTING = {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Harvester
|
* Harvester
|
||||||
*/
|
*/
|
||||||
import { DESCRIPTION } from '@shell/config/table-headers';
|
|
||||||
|
|
||||||
// image
|
// image
|
||||||
export const IMAGE_DOWNLOAD_SIZE = {
|
export const IMAGE_DOWNLOAD_SIZE = {
|
||||||
@@ -97,10 +96,5 @@ export const IMAGE_STORAGE_CLASS = {
|
|||||||
sort: 'imageStorageClass',
|
sort: 'imageStorageClass',
|
||||||
value: 'imageStorageClass',
|
value: 'imageStorageClass',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 150,
|
width: 200,
|
||||||
};
|
|
||||||
|
|
||||||
export const HARVESTER_DESCRIPTION = {
|
|
||||||
...DESCRIPTION,
|
|
||||||
width: 150,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ export default {
|
|||||||
:resource="value"
|
:resource="value"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<Tabbed
|
<Tabbed
|
||||||
v-if="spec"
|
v-if="spec"
|
||||||
|
|||||||
@@ -118,10 +118,6 @@ export default {
|
|||||||
imageName() {
|
imageName() {
|
||||||
return this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-';
|
return this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_NAME] || '-';
|
||||||
},
|
},
|
||||||
|
|
||||||
sourceType() {
|
|
||||||
return this.value?.spec?.sourceType;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -256,16 +252,6 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col span-12">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.image.source')"
|
|
||||||
:value="sourceType"
|
|
||||||
class="mb-20"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="errorMessage !== '-'"
|
v-if="errorMessage !== '-'"
|
||||||
class="row"
|
class="row"
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ export default {
|
|||||||
:resource="value"
|
:resource="value"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<Tabbed
|
<Tabbed
|
||||||
v-if="spec"
|
v-if="spec"
|
||||||
|
|||||||
-25
@@ -17,13 +17,6 @@ export default {
|
|||||||
default: () => {
|
default: () => {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
},
|
|
||||||
vmimResource: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
default: () => {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -32,12 +25,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
liveMigrationProgressEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('liveMigrationProgress');
|
|
||||||
},
|
|
||||||
migrationPhase() {
|
|
||||||
return this.vmimResource?.status?.phase || 'N/A';
|
|
||||||
},
|
|
||||||
migrationState() {
|
migrationState() {
|
||||||
return this.localResource?.status?.migrationState;
|
return this.localResource?.status?.migrationState;
|
||||||
},
|
},
|
||||||
@@ -71,18 +58,6 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
|
||||||
v-if="liveMigrationProgressEnabled"
|
|
||||||
class="row mb-20"
|
|
||||||
>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.virtualMachine.detail.details.phase')"
|
|
||||||
:value="migrationPhase"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabelValue
|
<LabelValue
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import { formatSi } from '@shell/utils/units';
|
|||||||
|
|
||||||
const VM_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/harvester-vm-detail-1/vm-info-detail?orgId=1';
|
const VM_METRICS_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/harvester-vm-detail-1/vm-info-detail?orgId=1';
|
||||||
|
|
||||||
const VM_MIGRATION_DETAIL_URL = '/api/v1/namespaces/cattle-monitoring-system/services/http:rancher-monitoring-grafana:80/proxy/d/harvester-vm-migration-details-1/harvester-vm-migration-details?orgId=1';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VMIDetailsPage',
|
name: 'VMIDetailsPage',
|
||||||
|
|
||||||
@@ -65,7 +63,6 @@ export default {
|
|||||||
hasResourceQuotaSchema: false,
|
hasResourceQuotaSchema: false,
|
||||||
switchToCloud: false,
|
switchToCloud: false,
|
||||||
VM_METRICS_DETAIL_URL,
|
VM_METRICS_DETAIL_URL,
|
||||||
VM_MIGRATION_DETAIL_URL,
|
|
||||||
showVmMetrics: false,
|
showVmMetrics: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -81,7 +78,6 @@ export default {
|
|||||||
events: this.$store.dispatch(`${ inStore }/findAll`, { type: EVENT }),
|
events: this.$store.dispatch(`${ inStore }/findAll`, { type: EVENT }),
|
||||||
allSSHs: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.SSH }),
|
allSSHs: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.SSH }),
|
||||||
vmis: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMI }),
|
vmis: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMI }),
|
||||||
vmims: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VMIM }),
|
|
||||||
restore: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESTORE }),
|
restore: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.RESTORE }),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,31 +131,6 @@ export default {
|
|||||||
return this.$store.getters[`${ inStore }/all`](EVENT);
|
return this.$store.getters[`${ inStore }/all`](EVENT);
|
||||||
},
|
},
|
||||||
|
|
||||||
vmim() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
const vmimList = this.$store.getters[`${ inStore }/all`](HCI.VMIM) || [];
|
|
||||||
|
|
||||||
const migrationUid = this.vmi?.status?.migrationState?.migrationUid;
|
|
||||||
const vmim = vmimList.find((VMIM) => VMIM?.metadata?.uid === migrationUid);
|
|
||||||
|
|
||||||
return vmim;
|
|
||||||
},
|
|
||||||
|
|
||||||
migrationEvents() {
|
|
||||||
const migrationVMName = this.vmim?.metadata.name || '';
|
|
||||||
|
|
||||||
if (migrationVMName === '') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.allEvents.filter((e) => {
|
|
||||||
const { creationTimestamp } = this.value?.metadata || {};
|
|
||||||
const involvedName = e?.involvedObject?.name;
|
|
||||||
|
|
||||||
return involvedName === migrationVMName && e.firstTimestamp >= creationTimestamp;
|
|
||||||
}).sort((a, b) => a.lastTimestamp > b.lastTimestamp);
|
|
||||||
},
|
|
||||||
|
|
||||||
events() {
|
events() {
|
||||||
return this.allEvents.filter((e) => {
|
return this.allEvents.filter((e) => {
|
||||||
const { name, creationTimestamp } = this.value?.metadata || {};
|
const { name, creationTimestamp } = this.value?.metadata || {};
|
||||||
@@ -167,6 +138,7 @@ export default {
|
|||||||
const pvcName = this.value.persistentVolumeClaimName || [];
|
const pvcName = this.value.persistentVolumeClaimName || [];
|
||||||
|
|
||||||
const involvedName = e?.involvedObject?.name;
|
const involvedName = e?.involvedObject?.name;
|
||||||
|
|
||||||
const matchPVC = pvcName.find((name) => name === involvedName);
|
const matchPVC = pvcName.find((name) => name === involvedName);
|
||||||
|
|
||||||
return (involvedName === name || involvedName === podName || matchPVC) && e.firstTimestamp >= creationTimestamp;
|
return (involvedName === name || involvedName === podName || matchPVC) && e.firstTimestamp >= creationTimestamp;
|
||||||
@@ -185,10 +157,6 @@ export default {
|
|||||||
vm: this.value.name
|
vm: this.value.name
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
liveMigrationProgressEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('liveMigrationProgress');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -378,20 +346,6 @@ export default {
|
|||||||
<Migration
|
<Migration
|
||||||
:value="value"
|
:value="value"
|
||||||
:vmi-resource="vmi"
|
:vmi-resource="vmi"
|
||||||
:vmim-resource="vmim"
|
|
||||||
/>
|
|
||||||
<DashboardMetrics
|
|
||||||
v-if="showVmMetrics && liveMigrationProgressEnabled"
|
|
||||||
:detail-url="VM_MIGRATION_DETAIL_URL"
|
|
||||||
graph-height="640px"
|
|
||||||
:has-summary-and-detail="false"
|
|
||||||
:vars="graphVars"
|
|
||||||
class="mb-30"
|
|
||||||
/>
|
|
||||||
<Events
|
|
||||||
v-if="liveMigrationProgressEnabled"
|
|
||||||
:resource="vmi"
|
|
||||||
:events="migrationEvents"
|
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import { NAMESPACE } from '@shell/config/types';
|
|
||||||
import { randomStr } from '@shell/utils/string';
|
import { randomStr } from '@shell/utils/string';
|
||||||
import { exceptionToErrorsArray, stringify } from '@shell/utils/error';
|
import { exceptionToErrorsArray, stringify } from '@shell/utils/error';
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
@@ -7,15 +6,7 @@ import AsyncButton from '@shell/components/AsyncButton';
|
|||||||
import GraphCircle from '@shell/components/graph/Circle';
|
import GraphCircle from '@shell/components/graph/Circle';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import AppModal from '@shell/components/AppModal';
|
import AppModal from '@shell/components/AppModal';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import UnitInput from '@shell/components/form/UnitInput';
|
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
import { HCI_SETTING } from '../config/settings';
|
|
||||||
import { DOC } from '../config/doc-links';
|
|
||||||
import { docLink } from '../utils/feature-flags';
|
|
||||||
|
|
||||||
const SELECT_ALL = 'select_all';
|
|
||||||
const UNSELECT_ALL = 'unselect_all';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SupportBundle',
|
name: 'SupportBundle',
|
||||||
@@ -26,36 +17,14 @@ export default {
|
|||||||
AsyncButton,
|
AsyncButton,
|
||||||
Banner,
|
Banner,
|
||||||
AppModal,
|
AppModal,
|
||||||
LabeledSelect,
|
|
||||||
UnitInput
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
await this.$store.dispatch('harvester/findAll', { type: NAMESPACE });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl']('v1/harvester/namespaces?link=supportbundle');
|
|
||||||
const response = await this.$store.dispatch('harvester/request', { url });
|
|
||||||
|
|
||||||
this.defaultNamespaces = response.data || [];
|
|
||||||
} catch (error) {
|
|
||||||
this.defaultNamespaces = [];
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isOpen: false,
|
url: '',
|
||||||
errors: [],
|
description: '',
|
||||||
version: '',
|
errors: [],
|
||||||
clusterName: '',
|
isOpen: false,
|
||||||
url: '',
|
|
||||||
description: '',
|
|
||||||
namespaces: [],
|
|
||||||
defaultNamespaces: [],
|
|
||||||
timeout: '',
|
|
||||||
expiration: '',
|
|
||||||
nodeTimeout: '',
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -70,51 +39,23 @@ export default {
|
|||||||
|
|
||||||
percentage() {
|
percentage() {
|
||||||
return this.$store.getters['harvester-common/getBundlePercentage'];
|
return this.$store.getters['harvester-common/getBundlePercentage'];
|
||||||
},
|
}
|
||||||
|
|
||||||
availableNamespaces() {
|
|
||||||
const allNamespaces = this.$store.getters['harvester/all'](NAMESPACE).map((ns) => ns.id);
|
|
||||||
const defaultNamespacesIds = this.defaultNamespaces.map((ns) => ns.id);
|
|
||||||
|
|
||||||
return allNamespaces.filter((ns) => !defaultNamespacesIds.includes(ns) || this.namespaces.includes(ns));
|
|
||||||
},
|
|
||||||
|
|
||||||
namespaceOptions() {
|
|
||||||
if (this.availableNamespaces.length === 0) return [];
|
|
||||||
|
|
||||||
const allSelected = this.namespaces.length === this.availableNamespaces.length &&
|
|
||||||
this.availableNamespaces.every((ns) => this.namespaces.includes(ns));
|
|
||||||
|
|
||||||
const controlOption = allSelected ? { label: this.t('harvester.modal.bundle.namespaces.unselectAll'), value: UNSELECT_ALL } : { label: this.t('harvester.modal.bundle.namespaces.selectAll'), value: SELECT_ALL };
|
|
||||||
|
|
||||||
return [controlOption, ...this.availableNamespaces];
|
|
||||||
},
|
|
||||||
|
|
||||||
docLink() {
|
|
||||||
const version = this.$store.getters['harvester-common/getServerVersion']();
|
|
||||||
|
|
||||||
return docLink(DOC.SUPPORT_BUNDLE_NAMESPACES, version);
|
|
||||||
},
|
|
||||||
|
|
||||||
customSupportBundleFeatureEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('customSupportBundle');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
isShowBundleModal: {
|
isShowBundleModal: {
|
||||||
immediate: true,
|
|
||||||
handler(show) {
|
handler(show) {
|
||||||
this.isOpen = show;
|
if (show) {
|
||||||
}
|
this.$nextTick(() => {
|
||||||
},
|
this.isOpen = true;
|
||||||
|
});
|
||||||
isOpen(newVal) {
|
} else {
|
||||||
if (newVal) {
|
this.isOpen = false;
|
||||||
this.loadDefaultSettings();
|
this.url = '';
|
||||||
} else {
|
this.description = '';
|
||||||
this.resetForm();
|
}
|
||||||
}
|
},
|
||||||
|
immediate: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -124,93 +65,33 @@ export default {
|
|||||||
close() {
|
close() {
|
||||||
this.isOpen = false;
|
this.isOpen = false;
|
||||||
this.$store.commit('harvester-common/toggleBundleModal', false);
|
this.$store.commit('harvester-common/toggleBundleModal', false);
|
||||||
},
|
this.backUpName = '';
|
||||||
|
|
||||||
loadDefaultSettings() {
|
|
||||||
const cluster = this.$store.getters['currentCluster'];
|
|
||||||
const versionSetting = this.$store.getters['harvester/byId'](HCI.SETTING, HCI_SETTING.SERVER_VERSION);
|
|
||||||
const namespacesSetting = this.$store.getters['harvester/byId'](HCI.SETTING, HCI_SETTING.SUPPORT_BUNDLE_NAMESPACES);
|
|
||||||
const timeoutSetting = this.$store.getters['harvester/byId'](HCI.SETTING, HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT);
|
|
||||||
const expirationSetting = this.$store.getters['harvester/byId'](HCI.SETTING, HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION);
|
|
||||||
const nodeTimeoutSetting = this.$store.getters['harvester/byId'](HCI.SETTING, HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT);
|
|
||||||
|
|
||||||
this.version = versionSetting?.currentVersion || '';
|
|
||||||
this.clusterName = cluster?.id || '';
|
|
||||||
this.namespaces = (namespacesSetting?.value ?? namespacesSetting?.default ?? '').split(',').map((ns) => ns.trim()).filter((ns) => ns);
|
|
||||||
this.timeout = timeoutSetting?.value ?? timeoutSetting?.default ?? '';
|
|
||||||
this.expiration = expirationSetting?.value ?? expirationSetting?.default ?? '';
|
|
||||||
this.nodeTimeout = nodeTimeoutSetting?.value ?? nodeTimeoutSetting?.default ?? '';
|
|
||||||
this.url = '';
|
|
||||||
this.description = '';
|
|
||||||
this.errors = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
resetForm() {
|
|
||||||
this.url = '';
|
|
||||||
this.description = '';
|
|
||||||
this.namespaces = [];
|
|
||||||
this.timeout = '';
|
|
||||||
this.expiration = '';
|
|
||||||
this.nodeTimeout = '';
|
|
||||||
this.errors = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNamespaces(selected) {
|
|
||||||
if (selected.includes(SELECT_ALL)) {
|
|
||||||
this.namespaces = [...this.availableNamespaces];
|
|
||||||
} else if (selected.includes(UNSELECT_ALL)) {
|
|
||||||
this.namespaces = [];
|
|
||||||
} else {
|
|
||||||
this.namespaces = selected.filter((val) => val !== SELECT_ALL && val !== UNSELECT_ALL);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNumberValue(field, value) {
|
|
||||||
if (value === '' || value === null || isNaN(value)) {
|
|
||||||
this[field] = '';
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const num = Number(value);
|
|
||||||
const isValid = Number.isInteger(num) && num >= 0;
|
|
||||||
|
|
||||||
this[field] = isValid ? String(num) : '';
|
|
||||||
},
|
|
||||||
|
|
||||||
onKeyDown(e) {
|
|
||||||
if (['e', 'E', '+', '-', '.'].includes(e.key)) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async save(buttonCb) {
|
async save(buttonCb) {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
|
|
||||||
const name = `bundle-${ this.clusterName }-${ this.version }-${ randomStr(5).toLowerCase() }`;
|
const name = `bundle-${ randomStr(5).toLowerCase() }`;
|
||||||
const namespace = 'harvester-system';
|
const namespace = 'harvester-system';
|
||||||
|
|
||||||
const spec = {
|
|
||||||
description: this.description.trim(),
|
|
||||||
...(this.url.trim() && { issueURL: this.url.trim() }),
|
|
||||||
...(this.namespaces.length > 0 && { extraCollectionNamespaces: this.namespaces }),
|
|
||||||
...(this.timeout !== '' && { timeout: Number(this.timeout) }),
|
|
||||||
...(this.expiration !== '' && { expiration: Number(this.expiration) }),
|
|
||||||
...(this.nodeTimeout !== '' && { nodeTimeout: Number(this.nodeTimeout) }),
|
|
||||||
};
|
|
||||||
|
|
||||||
const bundleCrd = {
|
const bundleCrd = {
|
||||||
apiVersion: 'harvesterhci.io/v1beta1',
|
apiVersion: 'harvesterhci.io/v1beta1',
|
||||||
type: HCI.SUPPORT_BUNDLE,
|
type: HCI.SUPPORT_BUNDLE,
|
||||||
kind: 'SupportBundle',
|
kind: 'SupportBundle',
|
||||||
metadata: { name, namespace },
|
metadata: {
|
||||||
spec,
|
name,
|
||||||
|
namespace
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
issueURL: this.url,
|
||||||
|
description: this.description
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const bundleValue = await this.$store.dispatch(`${ inStore }/create`, bundleCrd);
|
||||||
const bundleValue = await this.$store.dispatch(`${ inStore }/create`, bundleCrd);
|
|
||||||
|
|
||||||
|
try {
|
||||||
await bundleValue.save();
|
await bundleValue.save();
|
||||||
|
|
||||||
this.$store.commit('harvester-common/setLatestBundleId', `${ namespace }/${ name }`, { root: true });
|
this.$store.commit('harvester-common/setLatestBundleId', `${ namespace }/${ name }`, { root: true });
|
||||||
@@ -237,110 +118,69 @@ export default {
|
|||||||
@close="close"
|
@close="close"
|
||||||
>
|
>
|
||||||
<div class="p-20">
|
<div class="p-20">
|
||||||
<h2>{{ t('harvester.modal.bundle.title') }}</h2>
|
<h2>
|
||||||
<div class="content">
|
{{ t('harvester.modal.bundle.title') }}
|
||||||
<div
|
</h2>
|
||||||
v-if="bundlePending"
|
|
||||||
class="circle mb-20"
|
<div
|
||||||
>
|
v-if="!bundlePending"
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="url"
|
||||||
|
:label="t('harvester.modal.bundle.url')"
|
||||||
|
class="mb-20"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="description"
|
||||||
|
:label="t('harvester.modal.bundle.description')"
|
||||||
|
type="multiline"
|
||||||
|
:min-height="120"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
<div class="circle">
|
||||||
<GraphCircle
|
<GraphCircle
|
||||||
primary-stroke-color="green"
|
primary-stroke-color="green"
|
||||||
secondary-stroke-color="lightgrey"
|
secondary-stroke-color="white"
|
||||||
:stroke-width="6"
|
:stroke-width="6"
|
||||||
:percentage="percentage"
|
:percentage="percentage"
|
||||||
:show-text="true"
|
:show-text="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
</div>
|
||||||
<p
|
|
||||||
v-if="customSupportBundleFeatureEnabled"
|
|
||||||
v-clean-html="t('harvester.modal.bundle.tip', { doc: docLink }, true)"
|
|
||||||
class="mb-20"
|
|
||||||
/>
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="url"
|
|
||||||
:label="t('harvester.modal.bundle.url')"
|
|
||||||
class="mb-10"
|
|
||||||
/>
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="description"
|
|
||||||
required
|
|
||||||
:label="t('harvester.modal.bundle.description')"
|
|
||||||
type="multiline"
|
|
||||||
:min-height="80"
|
|
||||||
class="mb-10"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template v-if="customSupportBundleFeatureEnabled">
|
<div
|
||||||
<LabeledSelect
|
v-for="(err, idx) in errors"
|
||||||
v-model:value="namespaces"
|
:key="idx"
|
||||||
:label="t('harvester.modal.bundle.namespaces.label')"
|
>
|
||||||
:clearable="true"
|
<Banner
|
||||||
:multiple="true"
|
color="error"
|
||||||
:options="namespaceOptions"
|
:label="stringify(err)"
|
||||||
class="mb-10 label-select"
|
/>
|
||||||
:tooltip="t('harvester.modal.bundle.namespaces.tooltip', _, true)"
|
</div>
|
||||||
@update:value="updateNamespaces"
|
|
||||||
/>
|
<div class="footer mt-20">
|
||||||
<UnitInput
|
<button
|
||||||
v-model:value="timeout"
|
class="btn btn-sm role-secondary mr-10"
|
||||||
:label="t('harvester.modal.bundle.timeout.label')"
|
@click="close"
|
||||||
class="mb-10"
|
|
||||||
type="number"
|
|
||||||
:min="0"
|
|
||||||
:tooltip="t('harvester.modal.bundle.timeout.tooltip', _, true)"
|
|
||||||
:suffix="timeout > 1 ? 'Minutes' : 'Minute'"
|
|
||||||
@keydown="onKeyDown"
|
|
||||||
@update:value="val => updateNumberValue('timeout', val)"
|
|
||||||
/>
|
|
||||||
<UnitInput
|
|
||||||
v-model:value="expiration"
|
|
||||||
:label="t('harvester.modal.bundle.expiration.label')"
|
|
||||||
class="mb-10"
|
|
||||||
type="number"
|
|
||||||
:min="0"
|
|
||||||
:tooltip="t('harvester.modal.bundle.expiration.tooltip', _, true)"
|
|
||||||
:suffix="expiration > 1 ? 'Minutes' : 'Minute'"
|
|
||||||
@keydown="onKeyDown"
|
|
||||||
@update:value="val => updateNumberValue('expiration', val)"
|
|
||||||
/>
|
|
||||||
<UnitInput
|
|
||||||
v-model:value="nodeTimeout"
|
|
||||||
:label="t('harvester.modal.bundle.nodeTimeout.label')"
|
|
||||||
class="mb-10"
|
|
||||||
type="number"
|
|
||||||
:min="0"
|
|
||||||
:tooltip="t('harvester.modal.bundle.nodeTimeout.tooltip', _, true)"
|
|
||||||
:suffix="nodeTimeout > 1 ? 'Minutes' : 'Minute'"
|
|
||||||
@keydown="onKeyDown"
|
|
||||||
@update:value="val => updateNumberValue('nodeTimeout', val)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<div
|
|
||||||
v-for="(err, idx) in errors"
|
|
||||||
:key="idx"
|
|
||||||
>
|
>
|
||||||
<Banner
|
{{ t('generic.close') }}
|
||||||
color="error"
|
</button>
|
||||||
:label="stringify(err)"
|
|
||||||
/>
|
<AsyncButton
|
||||||
</div>
|
type="submit"
|
||||||
<div class="footer mt-20">
|
mode="generate"
|
||||||
<button
|
class="btn btn-sm bg-primary"
|
||||||
class="btn btn-sm role-secondary mr-10"
|
:disabled="bundlePending"
|
||||||
@click="close"
|
@click="save"
|
||||||
>
|
/>
|
||||||
{{ t('generic.close') }}
|
|
||||||
</button>
|
|
||||||
<AsyncButton
|
|
||||||
type="submit"
|
|
||||||
mode="generate"
|
|
||||||
class="btn btn-sm bg-primary"
|
|
||||||
:disabled="bundlePending"
|
|
||||||
@click="save"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</app-modal>
|
</app-modal>
|
||||||
@@ -354,10 +194,6 @@ export default {
|
|||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.labeled-select.taggable ::v-deep(.vs__selected-options .vs__selected.vs__selected > button) {
|
|
||||||
margin: 0 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bundle {
|
.bundle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
@@ -368,8 +204,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
height: 218px;
|
||||||
|
|
||||||
.circle {
|
.circle {
|
||||||
padding: 10px 0;
|
padding-top: 20px;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,10 +73,8 @@ export default {
|
|||||||
|
|
||||||
data.spec.clusterNetwork = this.clusterNetwork;
|
data.spec.clusterNetwork = this.clusterNetwork;
|
||||||
|
|
||||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl'](`v1/harvester/${ HCI.VLAN_CONFIG }s/${ data.id }`);
|
|
||||||
|
|
||||||
await this.$store.dispatch('harvester/request', {
|
await this.$store.dispatch('harvester/request', {
|
||||||
url,
|
url: `/v1/harvester/${ HCI.VLAN_CONFIG }s/${ data.id }`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="currentComponent"
|
:is="currentComponent"
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ export default {
|
|||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export default {
|
|||||||
:errors="errors"
|
:errors="errors"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<div class="header mb-20">
|
<div class="header mb-20">
|
||||||
<FileSelector
|
<FileSelector
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export default {
|
|||||||
:validation-passed="true"
|
:validation-passed="true"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
:can-yaml="true"
|
:can-yaml="true"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors = e"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export default {
|
|||||||
:resource="value"
|
:resource="value"
|
||||||
:subtypes="[]"
|
:subtypes="[]"
|
||||||
:cancel-event="true"
|
:cancel-event="true"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors = e"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -190,7 +190,6 @@ export default {
|
|||||||
:errors="errors"
|
:errors="errors"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="saveNetwork"
|
@finish="saveNetwork"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
ref="nd"
|
ref="nd"
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export default {
|
|||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
:validation-passed="canSave"
|
:validation-passed="canSave"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors = e"
|
||||||
>
|
>
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<Banner
|
<Banner
|
||||||
|
|||||||
@@ -84,10 +84,6 @@ export default {
|
|||||||
this.value['_type'] = TYPES.OPAQUE;
|
this.value['_type'] = TYPES.OPAQUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( this.realMode === _CLONE) {
|
|
||||||
this.value['_type'] = this.liveValue._type;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isCloud,
|
isCloud,
|
||||||
nodeDrivers: null,
|
nodeDrivers: null,
|
||||||
@@ -238,7 +234,7 @@ export default {
|
|||||||
:resource="value"
|
:resource="value"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
@finish="saveSecret"
|
@finish="saveSecret"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors = e"
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import { RadioGroup } from '@components/Form/Radio';
|
|||||||
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 { TextAreaAutoGrow } from '@components/Form/TextArea';
|
import { TextAreaAutoGrow } from '@components/Form/TextArea';
|
||||||
import UnitInput from '@shell/components/form/UnitInput';
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
import { DOC } from '../config/doc-links';
|
|
||||||
import { docLink } from '../utils/feature-flags';
|
|
||||||
|
|
||||||
import { HCI_ALLOWED_SETTINGS, HCI_SINGLE_CLUSTER_ALLOWED_SETTING, HCI_SETTING } from '../config/settings';
|
import { HCI_ALLOWED_SETTINGS, HCI_SINGLE_CLUSTER_ALLOWED_SETTING, HCI_SETTING } from '../config/settings';
|
||||||
|
|
||||||
@@ -17,8 +15,7 @@ export default {
|
|||||||
LabeledInput,
|
LabeledInput,
|
||||||
LabeledSelect,
|
LabeledSelect,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
TextAreaAutoGrow,
|
TextAreaAutoGrow
|
||||||
UnitInput
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView],
|
||||||
@@ -60,13 +57,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
setting,
|
setting,
|
||||||
description: isHarvester ? t(
|
description: isHarvester ? t(`advancedSettings.descriptions.harv-${ this.value.id }`) : t(`advancedSettings.descriptions.${ this.value.id }`),
|
||||||
`advancedSettings.descriptions.harv-${ this.value.id }`,
|
|
||||||
this.getDocLinkParams()
|
|
||||||
) : t(
|
|
||||||
`advancedSettings.descriptions.${ this.value.id }`,
|
|
||||||
this.getDocLinkParams()
|
|
||||||
),
|
|
||||||
editHelp: t(`advancedSettings.editHelp.${ this.value.id }`),
|
editHelp: t(`advancedSettings.editHelp.${ this.value.id }`),
|
||||||
enumOptions,
|
enumOptions,
|
||||||
canReset,
|
canReset,
|
||||||
@@ -137,13 +128,6 @@ export default {
|
|||||||
await this.clusterRegistrationUrlTip();
|
await this.clusterRegistrationUrlTip();
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove any leading zeros (e.g. '0123' becomes 123)
|
|
||||||
if (this.setting.kind === 'number' && this.value.value) {
|
|
||||||
const num = Number(this.value.value);
|
|
||||||
|
|
||||||
this.value.value = isNaN(num) ? 0 : `${ num }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.save(done);
|
this.save(done);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -179,18 +163,6 @@ export default {
|
|||||||
if (typeof this.$refs.settingComp?.useDefault === 'function') {
|
if (typeof this.$refs.settingComp?.useDefault === 'function') {
|
||||||
this.$refs.settingComp.useDefault();
|
this.$refs.settingComp.useDefault();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
getDocLinkParams() {
|
|
||||||
const setting = HCI_ALLOWED_SETTINGS[this.value.id] || HCI_SINGLE_CLUSTER_ALLOWED_SETTING[this.value.id];
|
|
||||||
|
|
||||||
if (setting?.docPath) {
|
|
||||||
const version = this.$store.getters['harvester-common/getServerVersion']();
|
|
||||||
const url = docLink(DOC[setting.docPath], version);
|
|
||||||
|
|
||||||
return { url };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -205,7 +177,7 @@ export default {
|
|||||||
:subtypes="[]"
|
:subtypes="[]"
|
||||||
:can-yaml="false"
|
:can-yaml="false"
|
||||||
:cancel-event="true"
|
:cancel-event="true"
|
||||||
@error="e=>errors=e"
|
@error="e => (errors = e)"
|
||||||
@finish="saveSettings"
|
@finish="saveSettings"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
>
|
>
|
||||||
@@ -266,13 +238,6 @@ export default {
|
|||||||
:min-height="254"
|
:min-height="254"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="setting.kind === 'number'">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="value.value"
|
|
||||||
:label="t('advancedSettings.edit.value')"
|
|
||||||
type="number"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model:value="value.value"
|
v-model:value="value.value"
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ export default {
|
|||||||
:validation-passed="true"
|
:validation-passed="true"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors = e"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ export default {
|
|||||||
headers: {},
|
headers: {},
|
||||||
fileUrl: '',
|
fileUrl: '',
|
||||||
file: '',
|
file: '',
|
||||||
HCI_ANNOTATIONS,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -596,8 +595,6 @@ export default {
|
|||||||
:pad-left="false"
|
:pad-left="false"
|
||||||
:read-allowed="false"
|
:read-allowed="false"
|
||||||
:value-can-be-empty="true"
|
:value-can-be-empty="true"
|
||||||
:toggle-filter="true"
|
|
||||||
:protected-keys="[HCI_ANNOTATIONS.IMAGE_DISPLAY_NAME]"
|
|
||||||
@focusKey="focusKey"
|
@focusKey="focusKey"
|
||||||
@update:value="value.setLabels($event)"
|
@update:value="value.setLabels($event)"
|
||||||
>
|
>
|
||||||
@@ -614,7 +611,7 @@ export default {
|
|||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
v-model="row[valueName]"
|
v-model="row[valueName]"
|
||||||
:disabled="isView || row[keyName] === HCI_ANNOTATIONS.IMAGE_DISPLAY_NAME"
|
:disabled="isView"
|
||||||
:type="'text'"
|
:type="'text'"
|
||||||
:placeholder="t('keyValue.valuePlaceholder')"
|
:placeholder="t('keyValue.valuePlaceholder')"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
|
|||||||
@@ -189,16 +189,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.value.spec['templateId'] = `${ namespace }/${ name }`;
|
this.value.spec['templateId'] = `${ namespace }/${ name }`;
|
||||||
|
|
||||||
// inherit labels and annotations so the VM gets them when created from the template
|
|
||||||
this.value.spec.vm.metadata.labels = {
|
|
||||||
...this.value.spec.vm.metadata.labels,
|
|
||||||
...this.value.metadata.labels
|
|
||||||
};
|
|
||||||
this.value.spec.vm.metadata.annotations = {
|
|
||||||
...this.value.spec.vm.metadata.annotations,
|
|
||||||
...this.value.metadata.annotations
|
|
||||||
};
|
|
||||||
const res = await this.value.save();
|
const res = await this.value.save();
|
||||||
|
|
||||||
await this.saveSecret(res);
|
await this.saveSecret(res);
|
||||||
@@ -229,7 +219,6 @@ export default {
|
|||||||
:errors="errors"
|
:errors="errors"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="saveVMT"
|
@finish="saveVMT"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="templateValue"
|
:value="templateValue"
|
||||||
@@ -369,26 +358,6 @@ export default {
|
|||||||
@update:value="value.setInstanceLabels($event)"
|
@update:value="value.setInstanceLabels($event)"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
|
||||||
name="annotations"
|
|
||||||
:label="t('harvester.tab.annotations')"
|
|
||||||
:weight="-11"
|
|
||||||
>
|
|
||||||
<Banner color="info">
|
|
||||||
<t k="harvester.virtualMachine.annotations.banner" />
|
|
||||||
</Banner>
|
|
||||||
<KeyValue
|
|
||||||
key="annotations"
|
|
||||||
:value="value.annotations"
|
|
||||||
:protected-keys="value.systemAnnotations || []"
|
|
||||||
:toggle-filter="true"
|
|
||||||
:add-label="t('labels.addAnnotation')"
|
|
||||||
:mode="mode"
|
|
||||||
:read-allowed="false"
|
|
||||||
:value-can-be-empty="true"
|
|
||||||
@update:value="value.setAnnotations($event)"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
<Tab
|
<Tab
|
||||||
name="advanced"
|
name="advanced"
|
||||||
:label="t('harvester.tab.advanced')"
|
:label="t('harvester.tab.advanced')"
|
||||||
|
|||||||
@@ -304,10 +304,6 @@ export default {
|
|||||||
return ['ReadWriteMany'];
|
return ['ReadWriteMany'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.value?.spec?.accessModes && this.value?.spec?.accessModes?.length > 0) {
|
|
||||||
return this.value.spec.accessModes;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -378,12 +374,10 @@ export default {
|
|||||||
:generate-yaml="generateYaml"
|
:generate-yaml="generateYaml"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
:namespaced="true"
|
:namespaced="true"
|
||||||
:name-required="false"
|
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
@update:value="$emit('update:value', $event)"
|
@update:value="$emit('update:value', $event)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ export default {
|
|||||||
:errors="errors"
|
:errors="errors"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
-10
@@ -20,10 +20,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'create'
|
default: 'create'
|
||||||
},
|
},
|
||||||
osType: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@@ -68,12 +64,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
osType(neu) {
|
|
||||||
if (neu === 'windows') {
|
|
||||||
this.id = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
value(neu) {
|
value(neu) {
|
||||||
this.yamlScript = neu;
|
this.yamlScript = neu;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
osType: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
userScript: {
|
userScript: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
@@ -207,7 +203,6 @@ export default {
|
|||||||
<DataTemplate
|
<DataTemplate
|
||||||
ref="userTemplate"
|
ref="userTemplate"
|
||||||
:value="userScript"
|
:value="userScript"
|
||||||
:os-type="osType"
|
|
||||||
type="user"
|
type="user"
|
||||||
:view-code="viewCode"
|
:view-code="viewCode"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
@@ -224,7 +219,6 @@ export default {
|
|||||||
ref="networkTemplate"
|
ref="networkTemplate"
|
||||||
:value="networkScript"
|
:value="networkScript"
|
||||||
type="network"
|
type="network"
|
||||||
:os-type="osType"
|
|
||||||
:view-code="viewCode"
|
:view-code="viewCode"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:config-id="configNetworkId"
|
:config-id="configNetworkId"
|
||||||
|
|||||||
+6
-19
@@ -8,7 +8,6 @@ import { Banner } from '@components/Banner';
|
|||||||
import { PVC } from '@shell/config/types';
|
import { PVC } from '@shell/config/types';
|
||||||
import { formatSi, parseSi } from '@shell/utils/units';
|
import { formatSi, parseSi } from '@shell/utils/units';
|
||||||
import { HCI } from '../../../../types';
|
import { HCI } from '../../../../types';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
|
||||||
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
|
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';
|
||||||
@@ -71,7 +70,6 @@ export default {
|
|||||||
return {
|
return {
|
||||||
GIBIBYTE,
|
GIBIBYTE,
|
||||||
VOLUME_TYPE,
|
VOLUME_TYPE,
|
||||||
HCI_ANNOTATIONS,
|
|
||||||
InterfaceOption,
|
InterfaceOption,
|
||||||
loading: false,
|
loading: false,
|
||||||
images: [],
|
images: [],
|
||||||
@@ -92,23 +90,12 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
imagesOption() {
|
imagesOption() {
|
||||||
return this.images
|
return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => {
|
||||||
.filter((image) => {
|
return {
|
||||||
if (!image.isReady) return false;
|
label: this.imageOptionLabel(I),
|
||||||
|
value: I.id
|
||||||
// exclude internal images created during upgrade
|
};
|
||||||
const isInternalCreatedImage =
|
});
|
||||||
image.namespace === 'harvester-system' &&
|
|
||||||
image.labels?.[HCI_ANNOTATIONS.UPGRADE];
|
|
||||||
|
|
||||||
return !isInternalCreatedImage;
|
|
||||||
})
|
|
||||||
.sort((a, b) => (a.creationTimestamp > b.creationTimestamp ? -1 : 1))
|
|
||||||
.map((image) => ({
|
|
||||||
label: this.imageOptionLabel(image),
|
|
||||||
value: image.id,
|
|
||||||
disabled: image.isImportedImage
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
imageName() {
|
imageName() {
|
||||||
|
|||||||
@@ -114,11 +114,13 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
machineTypeOptions() {
|
machineTypeOptions() {
|
||||||
return this.machineTypes.map((type) => {
|
return [{
|
||||||
if (!type) return { label: 'None', value: '' };
|
label: 'None',
|
||||||
|
value: ''
|
||||||
return { label: type, value: type };
|
}, {
|
||||||
});
|
label: 'q35',
|
||||||
|
value: 'q35'
|
||||||
|
}];
|
||||||
},
|
},
|
||||||
|
|
||||||
templateOptions() {
|
templateOptions() {
|
||||||
@@ -255,11 +257,6 @@ export default {
|
|||||||
|
|
||||||
cloneVersionVM.metadata.annotations[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE] = JSON.stringify(deleteDataSource);
|
cloneVersionVM.metadata.annotations[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE] = JSON.stringify(deleteDataSource);
|
||||||
|
|
||||||
// Update labels, instance labels and annotations
|
|
||||||
this.value.metadata.labels = cloneVersionVM.metadata.labels;
|
|
||||||
this.value.spec.template.metadata.labels = cloneVersionVM.spec.template.metadata.labels;
|
|
||||||
this.value.metadata.annotations = cloneVersionVM.metadata.annotations;
|
|
||||||
|
|
||||||
this.getInitConfig({
|
this.getInitConfig({
|
||||||
value: cloneVersionVM, existUserData: true, fromTemplate: true
|
value: cloneVersionVM, existUserData: true, fromTemplate: true
|
||||||
});
|
});
|
||||||
@@ -522,7 +519,6 @@ export default {
|
|||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
@finish="saveVM"
|
@finish="saveVM"
|
||||||
@cancel="cancelAction"
|
@cancel="cancelAction"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
v-if="isCreate"
|
v-if="isCreate"
|
||||||
@@ -765,31 +761,10 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab
|
|
||||||
name="annotations"
|
|
||||||
:label="t('harvester.tab.annotations')"
|
|
||||||
:weight="-11"
|
|
||||||
>
|
|
||||||
<Banner color="info">
|
|
||||||
<t k="harvester.virtualMachine.annotations.banner" />
|
|
||||||
</Banner>
|
|
||||||
<KeyValue
|
|
||||||
key="annotations"
|
|
||||||
:value="value.annotations"
|
|
||||||
:protected-keys="value.systemAnnotations || []"
|
|
||||||
:toggle-filter="toggler"
|
|
||||||
:add-label="t('labels.addAnnotation')"
|
|
||||||
:mode="mode"
|
|
||||||
:read-allowed="false"
|
|
||||||
:value-can-be-empty="true"
|
|
||||||
@update:value="value.setAnnotations($event)"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
<Tab
|
||||||
name="advanced"
|
name="advanced"
|
||||||
:label="t('harvester.tab.advanced')"
|
:label="t('harvester.tab.advanced')"
|
||||||
:weight="-12"
|
:weight="-11"
|
||||||
>
|
>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
@@ -887,7 +862,6 @@ export default {
|
|||||||
ref="yamlEditor"
|
ref="yamlEditor"
|
||||||
:user-script="userScript"
|
:user-script="userScript"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:os-type="osType"
|
|
||||||
:view-code="isWindows"
|
:view-code="isWindows"
|
||||||
:namespace="value.metadata.namespace"
|
:namespace="value.metadata.namespace"
|
||||||
:network-script="networkScript"
|
:network-script="networkScript"
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ export default {
|
|||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
:yaml-modifiers="yamlModifiers"
|
:yaml-modifiers="yamlModifiers"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
@@ -235,7 +235,6 @@ export default {
|
|||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
:yaml-modifiers="yamlModifiers"
|
:yaml-modifiers="yamlModifiers"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ export default {
|
|||||||
:subtypes="[]"
|
:subtypes="[]"
|
||||||
:can-yaml="false"
|
:can-yaml="false"
|
||||||
:validation-passed="fvFormIsValid"
|
:validation-passed="fvFormIsValid"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors = e"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
@@ -373,7 +373,6 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
>
|
||||||
<NameNsDescription
|
<NameNsDescription
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script>
|
<script>
|
||||||
import { NORMAN } from '@shell/config/types';
|
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -14,24 +13,10 @@ export default {
|
|||||||
const harvesterSettings = await this.$store.dispatch('harvester/findAll', { type: HCI.SETTING });
|
const harvesterSettings = await this.$store.dispatch('harvester/findAll', { type: HCI.SETTING });
|
||||||
|
|
||||||
this.harvesterSettings = harvesterSettings;
|
this.harvesterSettings = harvesterSettings;
|
||||||
|
|
||||||
const clusterRoleTemplateBindingSchema = this.$store.getters['rancher/schemaFor'](NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING);
|
|
||||||
|
|
||||||
if (clusterRoleTemplateBindingSchema) {
|
|
||||||
const normanBindings = await this.$store.dispatch('rancher/findAll', { type: NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING }, { root: true });
|
|
||||||
|
|
||||||
this.clusterRoleTemplateBinding = normanBindings;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
const user = this.$store.getters['auth/v3User'];
|
return { harvesterSettings: [] };
|
||||||
|
|
||||||
return {
|
|
||||||
harvesterSettings: [],
|
|
||||||
clusterRoleTemplateBinding: [],
|
|
||||||
user,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -46,28 +31,19 @@ export default {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
return isMatch;
|
return isMatch;
|
||||||
},
|
|
||||||
|
|
||||||
isClusterOwner() {
|
|
||||||
const template = this.clusterRoleTemplateBinding.find((template) => {
|
|
||||||
return template.userId === this.user?.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
return template?.roleTemplateId === 'cluster-owner';
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldShowError() {
|
|
||||||
return this.isClusterOwner && !this.isMatch && this.value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="isMatch">
|
||||||
|
{{ value }}
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
{{ value }}
|
{{ value }}
|
||||||
<p
|
<p
|
||||||
v-if="shouldShowError"
|
v-if="value"
|
||||||
class="text-error"
|
class="text-error"
|
||||||
>
|
>
|
||||||
{{ t('harvester.backup.matchTarget') }}
|
{{ t('harvester.backup.matchTarget') }}
|
||||||
|
|||||||
@@ -35,26 +35,11 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
// Return VM instance IP and VM annotation IP
|
||||||
ips() {
|
ips() {
|
||||||
if (this.vmiIp.length) {
|
return [...this.vmiIp, ...this.networkAnnotationIP]
|
||||||
return [...this.vmiIp, ...this.networkAnnotationIP]
|
.filter(Boolean)
|
||||||
.filter(Boolean)
|
.sort((a, b) => a.ip < b.ip ? -1 : 1);
|
||||||
.sort((a, b) => a.ip < b.ip ? -1 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.customAnnotationIP;
|
|
||||||
},
|
|
||||||
|
|
||||||
customAnnotationIP() {
|
|
||||||
const annotationIp = get(this.row, `metadata.annotations."${ HCI_ANNOTATIONS.CUSTOM_IP }"`);
|
|
||||||
|
|
||||||
if (annotationIp && isIpv4(annotationIp)) {
|
|
||||||
return [{
|
|
||||||
name: 'custom-ip', ip: annotationIp, isCustom: true
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
},
|
},
|
||||||
|
|
||||||
networkAnnotationIP() {
|
networkAnnotationIP() {
|
||||||
@@ -112,13 +97,12 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="showIP">
|
<div v-if="showIP">
|
||||||
<span
|
<span
|
||||||
v-for="{ ip, name, isCustom } in ips"
|
v-for="{ip, name} in ips"
|
||||||
:key="`${ip}-${name}`"
|
:key="ip"
|
||||||
>
|
>
|
||||||
<CopyToClipboardText
|
<CopyToClipboardText
|
||||||
v-clean-tooltip="isCustom ? t('harvester.formatters.harvesterIpAddress.customIpTooltip') : name"
|
v-clean-tooltip="name"
|
||||||
:text="ip"
|
:text="ip"
|
||||||
:plain="isCustom"
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,13 +49,9 @@ dialog:
|
|||||||
}
|
}
|
||||||
protip: "Tip: Hold the {alternateLabel} key while clicking action to bypass this confirmation"
|
protip: "Tip: Hold the {alternateLabel} key while clicking action to bypass this confirmation"
|
||||||
stop:
|
stop:
|
||||||
message: "Are you sure you want to stop the {type} {names}?"
|
message: "Are you sure you want to continue stop the {type} {names}?"
|
||||||
pause:
|
pause:
|
||||||
message: "Are you sure you want to pause the {type} {names}?"
|
message: "Are you sure you want to continue pause the {type} {names}?"
|
||||||
restart:
|
|
||||||
message: "Are you sure you want to restart the {type} {names}?"
|
|
||||||
softreboot:
|
|
||||||
message: "Are you sure you want to soft reboot the {type} {names}?"
|
|
||||||
promptRemove:
|
promptRemove:
|
||||||
title: "Delete {type}"
|
title: "Delete {type}"
|
||||||
warningMessage: "Deleting the selected {type} permanently removes all resources on {thisOrThese} {type}. This action is irreversible. Do you want to continue?"
|
warningMessage: "Deleting the selected {type} permanently removes all resources on {thisOrThese} {type}. This action is irreversible. Do you want to continue?"
|
||||||
@@ -70,6 +66,7 @@ harvester:
|
|||||||
tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB'
|
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
|
||||||
@@ -136,24 +133,10 @@ harvester:
|
|||||||
delete: Delete
|
delete: Delete
|
||||||
bundle:
|
bundle:
|
||||||
title: Generate a Support Bundle
|
title: Generate a Support Bundle
|
||||||
titleDescription: Collect system-related logs in Harvester to assist with troubleshooting and support.
|
|
||||||
tip: Each field below specifies an aspect of the support bundle. For detailed explanations, please refer to the <a href="{doc}" target="_blank">documentation</a>.
|
|
||||||
url: Issue URL
|
url: Issue URL
|
||||||
description: Description
|
description: Description
|
||||||
namespaces:
|
requiredDesc: Description is required!
|
||||||
label: Namespaces
|
titleDescription: Collect system-related logs in Harvester to help with troubleshooting and support.
|
||||||
tooltip: Select additional namespaces to include in the support bundle.<br/>See docs support-bundle-namespaces for detail.
|
|
||||||
selectAll: 'Select All'
|
|
||||||
unselectAll: 'Unselect All'
|
|
||||||
timeout:
|
|
||||||
label: Timeout
|
|
||||||
tooltip: Minutes allows for completion of the support bundle generation process.<br/>See docs support-bundle-timeout for detail.
|
|
||||||
expiration:
|
|
||||||
label: Expiration
|
|
||||||
tooltip: Minutes before deleting packaged but not downloaded support bundle.<br/>See docs support-bundle-expiration for detail.
|
|
||||||
nodeTimeout:
|
|
||||||
label: Node Collection Timeout
|
|
||||||
tooltip: Minutes allowed for collecting logs/configurations on nodes.<br/>See docs support-bundle-node-collection-timeout for detail.
|
|
||||||
hotplug:
|
hotplug:
|
||||||
success: 'Volume { diskName } is mounted to the virtual machine { vm }.'
|
success: 'Volume { diskName } is mounted to the virtual machine { vm }.'
|
||||||
title: Add Volume
|
title: Add Volume
|
||||||
@@ -246,8 +229,6 @@ harvester:
|
|||||||
{count, plural,
|
{count, plural,
|
||||||
=1 {core}
|
=1 {core}
|
||||||
other {cores}}
|
other {cores}}
|
||||||
harvesterIpAddress:
|
|
||||||
customIpTooltip: "Custom IP (set via annotation)"
|
|
||||||
tableHeaders:
|
tableHeaders:
|
||||||
imageEncryption: Encryption
|
imageEncryption: Encryption
|
||||||
size: Size
|
size: Size
|
||||||
@@ -295,7 +276,6 @@ harvester:
|
|||||||
quotas: Quotas
|
quotas: Quotas
|
||||||
snapshots: Snapshots
|
snapshots: Snapshots
|
||||||
instanceLabel: Instance Labels
|
instanceLabel: Instance Labels
|
||||||
annotations: Annotations
|
|
||||||
fields:
|
fields:
|
||||||
version: Version
|
version: Version
|
||||||
name: Name
|
name: Name
|
||||||
@@ -694,7 +674,7 @@ harvester:
|
|||||||
network:
|
network:
|
||||||
label: Network Data Template
|
label: Network Data Template
|
||||||
title: "Network Data:"
|
title: "Network Data:"
|
||||||
tip: "The network-data configuration allows you to customize the instance's networking interfaces by assigning subnet configuration, virtual device creation (bonds, bridges, VLANs) routes and DNS configuration. <a href='https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v1.html' target='_blank'>Learn more</a>"
|
tip: "The network-data configuration allows you to customize the instance’s networking interfaces by assigning subnet configuration, virtual device creation (bonds, bridges, VLANs) routes and DNS configuration. <a href='https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v1.html' target='_blank'>Learn more</a>"
|
||||||
scheduling:
|
scheduling:
|
||||||
affinity:
|
affinity:
|
||||||
anyNode: 'Run virtual machine on any available node'
|
anyNode: 'Run virtual machine on any available node'
|
||||||
@@ -774,7 +754,6 @@ harvester:
|
|||||||
dedicatedResources: Dedicated Resources
|
dedicatedResources: Dedicated Resources
|
||||||
down: Virtual machine not running
|
down: Virtual machine not running
|
||||||
affinityRules: Affinity Rules
|
affinityRules: Affinity Rules
|
||||||
phase: Phase
|
|
||||||
sourceNode: Source Node
|
sourceNode: Source Node
|
||||||
targetNode: Target Node
|
targetNode: Target Node
|
||||||
started: Started
|
started: Started
|
||||||
@@ -812,8 +791,6 @@ harvester:
|
|||||||
banner: These labels are automatically synchronized to the virtual machine instance.
|
banner: These labels are automatically synchronized to the virtual machine instance.
|
||||||
labels:
|
labels:
|
||||||
banner: These key values are added as labels to the virtual machine.
|
banner: These key values are added as labels to the virtual machine.
|
||||||
annotations:
|
|
||||||
banner: These key values are added as annotations to the virtual machine.
|
|
||||||
|
|
||||||
volume:
|
volume:
|
||||||
label: Volumes
|
label: Volumes
|
||||||
@@ -878,8 +855,6 @@ harvester:
|
|||||||
uploading: "{name} is uploading, please do not refresh or close the page."
|
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.
|
||||||
tooltip:
|
|
||||||
imported: Created automatically by the vm-import-controller
|
|
||||||
|
|
||||||
vmTemplate:
|
vmTemplate:
|
||||||
label: Templates
|
label: Templates
|
||||||
@@ -1057,7 +1032,6 @@ harvester:
|
|||||||
accessKeyId: Specify your access key ID
|
accessKeyId: Specify your access key ID
|
||||||
secretAccessKey: Specify your secret access key
|
secretAccessKey: Specify your secret access key
|
||||||
cert: Upload a self-signed SSL certificate
|
cert: Upload a self-signed SSL certificate
|
||||||
noSearchMatch: No settings match your search.
|
|
||||||
vlanChangeTip: The newly modified default network interface only applies to newly added nodes, not existing ones.
|
vlanChangeTip: The newly modified default network interface only applies to newly added nodes, not existing ones.
|
||||||
defaultPhysicalNIC: Default Network Interface
|
defaultPhysicalNIC: Default Network Interface
|
||||||
modifiedMessage: Settings that have been customized from default settings are tagged with 'Modified'.
|
modifiedMessage: Settings that have been customized from default settings are tagged with 'Modified'.
|
||||||
@@ -1130,10 +1104,6 @@ harvester:
|
|||||||
uploadSuccess: "{name} uploaded successfully. Press Upgrade button to start the cluster upgrade process."
|
uploadSuccess: "{name} uploaded successfully. Press Upgrade button to start the cluster upgrade process."
|
||||||
deleteImage: Please select an image to delete.
|
deleteImage: Please select an image to delete.
|
||||||
deleteSuccess: "{name} deleted successfully."
|
deleteSuccess: "{name} deleted successfully."
|
||||||
imagePreloadStrategy: Image Preload Strategy
|
|
||||||
restoreVM: Restore VM
|
|
||||||
strategyType: Strategy Type
|
|
||||||
concurrency: Concurrency
|
|
||||||
harvesterMonitoring:
|
harvesterMonitoring:
|
||||||
label: Harvester Monitoring
|
label: Harvester Monitoring
|
||||||
section:
|
section:
|
||||||
@@ -1617,7 +1587,7 @@ advancedSettings:
|
|||||||
'harv-vm-force-reset-policy': Configuration for the force-reset action when a virtual machine is stuck on a node that is down.
|
'harv-vm-force-reset-policy': Configuration for the force-reset action when a virtual machine is stuck on a node that is down.
|
||||||
'harv-ssl-parameters': Custom SSL Parameters for TLS validation.
|
'harv-ssl-parameters': Custom SSL Parameters for TLS validation.
|
||||||
'harv-storage-network': 'Longhorn storage-network setting.'
|
'harv-storage-network': 'Longhorn storage-network setting.'
|
||||||
'harv-support-bundle-namespaces': Select additional namespaces to include in the support bundle.
|
'harv-support-bundle-namespaces': Specify resources in other namespaces to be collected by the support package.
|
||||||
'harv-auto-disk-provision-paths': Specify the disks(using glob pattern) that Harvester will automatically add as virtual machine storage.
|
'harv-auto-disk-provision-paths': Specify the disks(using glob pattern) that Harvester will automatically add as virtual machine storage.
|
||||||
'harv-support-bundle-image': Support bundle image configuration. Find different versions in <a href="https://hub.docker.com/r/rancher/support-bundle-kit/tags" target="_blank">rancher/support-bundle-kit</a>.
|
'harv-support-bundle-image': Support bundle image configuration. Find different versions in <a href="https://hub.docker.com/r/rancher/support-bundle-kit/tags" target="_blank">rancher/support-bundle-kit</a>.
|
||||||
'harv-release-download-url': This setting allows you to configure the <code>upgrade release download</code> URL address. Harvester will get the ISO URL and checksum value from the (<code>$URL</code>/<code>$VERSION</code>/version.yaml) file hosted by the configured URL.
|
'harv-release-download-url': This setting allows you to configure the <code>upgrade release download</code> URL address. Harvester will get the ISO URL and checksum value from the (<code>$URL</code>/<code>$VERSION</code>/version.yaml) file hosted by the configured URL.
|
||||||
@@ -1627,7 +1597,6 @@ advancedSettings:
|
|||||||
'harv-kubeconfig-default-token-ttl-minutes': 'TTL (in minutes) applied on Harvester administration kubeconfig files. Default is 0, which means to never expire.'
|
'harv-kubeconfig-default-token-ttl-minutes': 'TTL (in minutes) applied on Harvester administration kubeconfig files. Default is 0, which means to never expire.'
|
||||||
'harv-longhorn-v2-data-engine-enabled': 'Enable the Longhorn V2 data engine. Default is false. <ul><li>Changing this setting will restart RKE2 on all nodes. This will not affect running VM workloads.</li><li>If you see "not enough hugepages-2Mi capacity" errors when enabling this setting, wait a minute for the error to clear. If the error remains, reboot the affected node.</li></ul>'
|
'harv-longhorn-v2-data-engine-enabled': 'Enable the Longhorn V2 data engine. Default is false. <ul><li>Changing this setting will restart RKE2 on all nodes. This will not affect running VM workloads.</li><li>If you see "not enough hugepages-2Mi capacity" errors when enabling this setting, wait a minute for the error to clear. If the error remains, reboot the affected node.</li></ul>'
|
||||||
'harv-additional-guest-memory-overhead-ratio': 'The ratio for kubevirt to adjust the VM overhead memory. The value could be zero, empty value or floating number between 1.0 and 10.0, default to 1.5.'
|
'harv-additional-guest-memory-overhead-ratio': 'The ratio for kubevirt to adjust the VM overhead memory. The value could be zero, empty value or floating number between 1.0 and 10.0, default to 1.5.'
|
||||||
'harv-upgrade-config': 'Configure image preloading and VM restore options for upgrades. See related fields in <a href="{url}" target="_blank" rel="noopener">settings/upgrade-config</a>'
|
|
||||||
|
|
||||||
typeLabel:
|
typeLabel:
|
||||||
kubevirt.io.virtualmachine: |-
|
kubevirt.io.virtualmachine: |-
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return { initSettings: [], searchQuery: '' };
|
return { initSettings: [] };
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -116,15 +116,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
...s,
|
...s,
|
||||||
description: isHarvester ? `advancedSettings.descriptions.harv-${ s.id }` : `advancedSettings.descriptions.${ s.id }`,
|
description: isHarvester ? `advancedSettings.descriptions.harv-${ s.id }` : `advancedSettings.descriptions.${ s.id }`,
|
||||||
customized: (!s.readOnly && s.data.value && (
|
customized: (!s.readOnly && s.data.value && s.data.value !== s.data.default) || s.data.hasCustomized
|
||||||
s.kind === 'json' ? (() => {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(JSON.parse(s.data.value)) !== JSON.stringify(JSON.parse(s.data.default));
|
|
||||||
} catch {
|
|
||||||
return s.data.value !== s.data.default;
|
|
||||||
}
|
|
||||||
})() : s.data.value !== s.data.default
|
|
||||||
)) || s.data.hasCustomized
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -143,19 +135,9 @@ export default {
|
|||||||
{{ t('harvester.setting.modifiedMessage') }}
|
{{ t('harvester.setting.modifiedMessage') }}
|
||||||
</div>
|
</div>
|
||||||
</Banner>
|
</Banner>
|
||||||
<div class="fixed-header-actions harvester-settings-search">
|
|
||||||
<div class="search row">
|
|
||||||
<input
|
|
||||||
v-model="searchQuery"
|
|
||||||
type="search"
|
|
||||||
class="input-sm search-box"
|
|
||||||
:aria-label="t('sortableTable.searchLabel')"
|
|
||||||
:placeholder="t('sortableTable.search')"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Tabbed
|
<Tabbed
|
||||||
class="mt-15"
|
class="mt-30"
|
||||||
>
|
>
|
||||||
<Tab
|
<Tab
|
||||||
name="advanced"
|
name="advanced"
|
||||||
@@ -164,7 +146,6 @@ export default {
|
|||||||
>
|
>
|
||||||
<Settings
|
<Settings
|
||||||
:settings="settings"
|
:settings="settings"
|
||||||
:search-query="searchQuery"
|
|
||||||
category="advanced"
|
category="advanced"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -175,7 +156,6 @@ export default {
|
|||||||
>
|
>
|
||||||
<Settings
|
<Settings
|
||||||
:settings="settings"
|
:settings="settings"
|
||||||
:search-query="searchQuery"
|
|
||||||
category="ui"
|
category="ui"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
@@ -187,20 +167,4 @@ export default {
|
|||||||
.settings-banner {
|
.settings-banner {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.harvester-settings-search {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
height: 40px;
|
|
||||||
margin-left: 10px;
|
|
||||||
min-width: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -90,11 +90,6 @@ export default {
|
|||||||
v-if="row.isEncrypted"
|
v-if="row.isEncrypted"
|
||||||
class="icon icon-lock"
|
class="icon icon-lock"
|
||||||
/>
|
/>
|
||||||
<i
|
|
||||||
v-if="row.isImportedImage"
|
|
||||||
v-clean-tooltip="t('harvester.image.tooltip.imported')"
|
|
||||||
class="icon icon-info"
|
|
||||||
/>
|
|
||||||
</router-link>
|
</router-link>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
{{ row.nameDisplay }}
|
{{ row.nameDisplay }}
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ export default {
|
|||||||
diskRows: [],
|
diskRows: [],
|
||||||
networkRows: [],
|
networkRows: [],
|
||||||
machineType: '',
|
machineType: '',
|
||||||
machineTypes: [],
|
|
||||||
secretName: '',
|
secretName: '',
|
||||||
secretRef: null,
|
secretRef: null,
|
||||||
showAdvanced: false,
|
showAdvanced: false,
|
||||||
@@ -296,20 +295,6 @@ export default {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
await this.$store.dispatch(`${ this.inStore }/findAll`, { type: SECRET });
|
await this.$store.dispatch(`${ this.inStore }/findAll`, { type: SECRET });
|
||||||
|
|
||||||
if (this.value.vmMachineTypesFeatureEnabled) {
|
|
||||||
try {
|
|
||||||
const url = this.$store.getters['harvester-common/getHarvesterClusterUrl']('v1/harvester/clusters/local?link=machineTypes');
|
|
||||||
const machineTypes = await this.$store.dispatch('harvester/request', { url });
|
|
||||||
|
|
||||||
this.machineTypes = machineTypes;
|
|
||||||
} catch (err) {
|
|
||||||
this.machineTypes = [''];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.machineTypes = [''];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getInitConfig({ value: this.value, init: this.isCreate });
|
this.getInitConfig({ value: this.value, init: this.isCreate });
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -346,8 +331,7 @@ export default {
|
|||||||
const maintenanceStrategy = vm.metadata.labels?.[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY] || 'Migrate';
|
const maintenanceStrategy = vm.metadata.labels?.[HCI_ANNOTATIONS.VM_MAINTENANCE_MODE_STRATEGY] || 'Migrate';
|
||||||
|
|
||||||
const runStrategy = spec.runStrategy || 'RerunOnFailure';
|
const runStrategy = spec.runStrategy || 'RerunOnFailure';
|
||||||
const machineType = spec.template.spec.domain?.machine?.type || this.machineTypes[0];
|
const machineType = value.machineType;
|
||||||
|
|
||||||
const cpu = spec.template.spec.domain?.cpu?.cores;
|
const cpu = spec.template.spec.domain?.cpu?.cores;
|
||||||
const memory = spec.template.spec.domain.resources.limits.memory;
|
const memory = spec.template.spec.domain.resources.limits.memory;
|
||||||
const reservedMemory = vm.metadata?.annotations?.[HCI_ANNOTATIONS.VM_RESERVED_MEMORY];
|
const reservedMemory = vm.metadata?.annotations?.[HCI_ANNOTATIONS.VM_RESERVED_MEMORY];
|
||||||
@@ -1549,7 +1533,6 @@ export default {
|
|||||||
if (val) {
|
if (val) {
|
||||||
this['sshKey'] = [];
|
this['sshKey'] = [];
|
||||||
this['userScript'] = undefined;
|
this['userScript'] = undefined;
|
||||||
this['networkScript'] = undefined;
|
|
||||||
this['installAgent'] = false;
|
this['installAgent'] = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1607,9 +1590,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
osType(neu, old) {
|
osType(neu) {
|
||||||
this.installAgent = old === 'windows' ? true : this.installAgent;
|
const out = this.getUserData({ installAgent: this.installAgent, osType: neu });
|
||||||
const out = old === 'windows' ? this.getInitUserData({ osType: neu }) : this.getUserData({ installAgent: this.installAgent, osType: neu });
|
|
||||||
|
|
||||||
this['userScript'] = out;
|
this['userScript'] = out;
|
||||||
this.refreshYamlEditor();
|
this.refreshYamlEditor();
|
||||||
|
|||||||
@@ -109,18 +109,15 @@ export default class HciSetting extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get parseValue() {
|
get parseValue() {
|
||||||
|
let parseDefaultValue = {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.value) {
|
parseDefaultValue = JSON.parse(this.value);
|
||||||
return JSON.parse(this.value);
|
|
||||||
} else if (this.default) {
|
|
||||||
return JSON.parse(this.default);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line no-console
|
parseDefaultValue = JSON.parse(this.default);
|
||||||
console.error('Failed to parse setting value or default:', err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return parseDefaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isS3() {
|
get isS3() {
|
||||||
|
|||||||
@@ -43,11 +43,6 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
|
|
||||||
out = out.filter( (A) => !toFilter.includes(A.action));
|
out = out.filter( (A) => !toFilter.includes(A.action));
|
||||||
|
|
||||||
// show `Clone` only when imageSource is `download`
|
|
||||||
if (this.imageSource !== 'download') {
|
|
||||||
out = out.filter(({ action }) => action !== 'goToClone');
|
|
||||||
}
|
|
||||||
|
|
||||||
const schema = this.$getters['schemaFor'](HCI.VM);
|
const schema = this.$getters['schemaFor'](HCI.VM);
|
||||||
let canCreateVM = true;
|
let canCreateVM = true;
|
||||||
|
|
||||||
@@ -57,42 +52,40 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
|
|
||||||
const customActions = this.isReady ? [
|
const customActions = this.isReady ? [
|
||||||
{
|
{
|
||||||
action: 'createFromImage',
|
action: 'createFromImage',
|
||||||
enabled: canCreateVM,
|
enabled: canCreateVM,
|
||||||
icon: 'icon icon-circle-plus',
|
icon: 'icon icon-circle-plus',
|
||||||
label: this.t('harvester.action.createVM'),
|
label: this.t('harvester.action.createVM'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'encryptImage',
|
action: 'encryptImage',
|
||||||
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted,
|
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted,
|
||||||
icon: 'icon icon-lock',
|
icon: 'icon icon-lock',
|
||||||
label: this.t('harvester.action.encryptImage'),
|
label: this.t('harvester.action.encryptImage'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'decryptImage',
|
action: 'decryptImage',
|
||||||
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted,
|
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted,
|
||||||
icon: 'icon icon-unlock',
|
icon: 'icon icon-unlock',
|
||||||
label: this.t('harvester.action.decryptImage'),
|
label: this.t('harvester.action.decryptImage'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'imageDownload',
|
action: 'imageDownload',
|
||||||
enabled: this.links?.download,
|
enabled: this.links?.download,
|
||||||
icon: 'icon icon-download',
|
icon: 'icon icon-download',
|
||||||
label: this.t('asyncButton.download.action'),
|
label: this.t('asyncButton.download.action'),
|
||||||
}
|
}
|
||||||
] : [];
|
] : [];
|
||||||
|
|
||||||
// imported image only allow download, download yaml and delete actions
|
let filteredOut;
|
||||||
if (this.isImportedImage) {
|
|
||||||
const custom = customActions.find((a) => a.action === 'imageDownload');
|
|
||||||
const filtered = out.filter(({ action }) => ['download', 'promptRemove'].includes(action));
|
|
||||||
|
|
||||||
return custom ? [custom, { divider: true }, ...filtered] : filtered;
|
if (customActions.length > 0) {
|
||||||
|
filteredOut = out;
|
||||||
|
} else {
|
||||||
|
// if the first item is a divider, remove it from the array
|
||||||
|
filteredOut = out[0]?.divider ? out.slice(1) : out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the first item is a divider, remove it from the array
|
|
||||||
const filteredOut = customActions.length > 0 ? out : (out[0]?.divider ? out.slice(1) : out);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...customActions,
|
...customActions,
|
||||||
...filteredOut
|
...filteredOut
|
||||||
@@ -213,10 +206,6 @@ export default class HciVmImage extends HarvesterResource {
|
|||||||
!!this.spec.securityParameters?.sourceImageNamespace;
|
!!this.spec.securityParameters?.sourceImageNamespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isImportedImage() {
|
|
||||||
return (this.metadata?.labels?.[HCI_ANNOTATIONS.IMPORTED_IMAGE]) === 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
get displayNameWithNamespace() {
|
get displayNameWithNamespace() {
|
||||||
return `${ this.metadata.namespace }/${ this.spec.displayName }`;
|
return `${ this.metadata.namespace }/${ this.spec.displayName }`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ export default class HciVmTemplateVersion extends HarvesterResource {
|
|||||||
applyDefaults() {
|
applyDefaults() {
|
||||||
const spec = {
|
const spec = {
|
||||||
vm: {
|
vm: {
|
||||||
metadata: { annotations: { [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: '[]' }, labels: {} },
|
metadata: { annotations: { [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: '[]' } },
|
||||||
spec: {
|
spec: {
|
||||||
runStrategy: 'RerunOnFailure',
|
runStrategy: 'RerunOnFailure',
|
||||||
template: {
|
template: {
|
||||||
metadata: { annotations: {}, labels: {} },
|
metadata: { annotations: {} },
|
||||||
spec: {
|
spec: {
|
||||||
domain: {
|
domain: {
|
||||||
machine: { type: '' },
|
machine: { type: '' },
|
||||||
@@ -284,10 +284,4 @@ export default class HciVmTemplateVersion extends HarvesterResource {
|
|||||||
get efiPersistentStateFeatureEnabled() {
|
get efiPersistentStateFeatureEnabled() {
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
|
||||||
}
|
}
|
||||||
|
|
||||||
get systemAnnotations() {
|
|
||||||
const annotations = this.annotations || {};
|
|
||||||
|
|
||||||
return Object.keys(annotations).filter((key) => key.includes(HCI_ANNOTATIONS.TEMPLATE_VERSION_CUSTOM_NAME));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,12 +131,11 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
label: this.t('harvester.action.unpause')
|
label: this.t('harvester.action.unpause')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'restartVM',
|
action: 'restartVM',
|
||||||
enabled: !!this.actions?.restart,
|
enabled: !!this.actions?.restart,
|
||||||
icon: 'icon icon-refresh',
|
icon: 'icon icon-refresh',
|
||||||
label: this.t('harvester.action.restart'),
|
label: this.t('harvester.action.restart'),
|
||||||
bulkable: true,
|
bulkable: true
|
||||||
bulkAction: 'restartVM'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'softrebootVM',
|
action: 'softrebootVM',
|
||||||
@@ -234,7 +233,7 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
const spec = {
|
const spec = {
|
||||||
runStrategy: 'RerunOnFailure',
|
runStrategy: 'RerunOnFailure',
|
||||||
template: {
|
template: {
|
||||||
metadata: { annotations: {}, labels: {} },
|
metadata: { annotations: {} },
|
||||||
spec: {
|
spec: {
|
||||||
domain: {
|
domain: {
|
||||||
machine: { type: '' },
|
machine: { type: '' },
|
||||||
@@ -284,7 +283,6 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
|
|
||||||
if (realMode !== _CLONE) {
|
if (realMode !== _CLONE) {
|
||||||
this.metadata['annotations'] = { [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: '[]' };
|
this.metadata['annotations'] = { [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: '[]' };
|
||||||
this.metadata['labels'] = {};
|
|
||||||
this['spec'] = spec;
|
this['spec'] = spec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,22 +311,12 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
this.metadata.annotations[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE] = JSON.stringify(deleteDataSource);
|
this.metadata.annotations[HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE] = JSON.stringify(deleteDataSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
restartVM(resources = this) {
|
restartVM() {
|
||||||
this.$dispatch('promptModal', {
|
this.doActionGrowl('restart', {});
|
||||||
resources,
|
|
||||||
action: 'restart',
|
|
||||||
warningMessageKey: 'dialog.confirmExecution.restart.message',
|
|
||||||
component: 'ConfirmExecutionDialog'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
softrebootVM(resources = this) {
|
softrebootVM() {
|
||||||
this.$dispatch('promptModal', {
|
this.doActionGrowl('softreboot', {});
|
||||||
resources,
|
|
||||||
action: 'softreboot',
|
|
||||||
warningMessageKey: 'dialog.confirmExecution.softreboot.message',
|
|
||||||
component: 'ConfirmExecutionDialog'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openLogs() {
|
openLogs() {
|
||||||
@@ -1217,10 +1205,6 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
|
||||||
}
|
}
|
||||||
|
|
||||||
get vmMachineTypesFeatureEnabled() {
|
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('vmMachineTypes');
|
|
||||||
}
|
|
||||||
|
|
||||||
setInstanceLabels(val) {
|
setInstanceLabels(val) {
|
||||||
if ( !this.spec?.template?.metadata?.labels ) {
|
if ( !this.spec?.template?.metadata?.labels ) {
|
||||||
set(this, 'spec.template.metadata.labels', {});
|
set(this, 'spec.template.metadata.labels', {});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester",
|
"name": "harvester",
|
||||||
"description": "Rancher UI Extension for Harvester",
|
"description": "Rancher UI Extension for Harvester",
|
||||||
"version": "1.6.0-rc1",
|
"version": "1.5.2-rc2",
|
||||||
"private": false,
|
"private": false,
|
||||||
"rancher": {
|
"rancher": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ export default {
|
|||||||
:cancel-event="true"
|
:cancel-event="true"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors = e"
|
||||||
>
|
>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
v-model:value="imageSource"
|
v-model:value="imageSource"
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { resourceNames } from '@shell/utils/string';
|
|
||||||
import { mapGetters, mapState } from 'vuex';
|
|
||||||
import { BACKUP_TYPE } from '@pkg/harvester/config/types';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PromptRemoveVMBackupDialog',
|
|
||||||
|
|
||||||
emits: ['errors'],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Array,
|
|
||||||
default: () => {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
names: {
|
|
||||||
type: Array,
|
|
||||||
default: () => {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
|
|
||||||
close: {
|
|
||||||
type: Function,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
|
|
||||||
doneLocation: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { errors: [] };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapState('action-menu', ['toRemove']),
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
resourceType() {
|
|
||||||
const isSnapshot = this.value?.[0]?.spec?.type === BACKUP_TYPE.SNAPSHOT;
|
|
||||||
const count = this.names?.length || 1;
|
|
||||||
|
|
||||||
if (isSnapshot) {
|
|
||||||
return this.t(`typeLabel."${ HCI.VM_SNAPSHOT }"`, { count });
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.t(`typeLabel."${ HCI.BACKUP }"`, { count });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
resourceNames,
|
|
||||||
async remove(buttonDone) {
|
|
||||||
try {
|
|
||||||
await Promise.all(this.value.map((resource) => resource.remove()));
|
|
||||||
this.close(buttonDone);
|
|
||||||
} catch (err) {
|
|
||||||
this.errors = exceptionToErrorsArray(err);
|
|
||||||
buttonDone(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="mt-10">
|
|
||||||
<div class="mb-5">
|
|
||||||
{{ t('promptRemove.attemptingToRemove', { type: resourceType }) }} <span
|
|
||||||
v-clean-html="resourceNames(names, null, t)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Banner
|
|
||||||
v-for="(error, i) in errors"
|
|
||||||
:key="i"
|
|
||||||
class=""
|
|
||||||
color="error"
|
|
||||||
:label="error"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -135,7 +135,7 @@ export default {
|
|||||||
<div>
|
<div>
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
{{ t('promptRemove.attemptingToRemove', {type}) }}
|
{{ t('promptRemove.attemptingToRemove', {type}) }}
|
||||||
<span v-clean-html="resourceNames(names, null, t)"></span>
|
<span v-clean-html="resourceNames(names, t)"></span>
|
||||||
|
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
{{ t('harvester.virtualMachine.promptRemove.title') }}
|
{{ t('harvester.virtualMachine.promptRemove.title') }}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ export const docLink = (suffix, version) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function getVersion(v) {
|
export function getVersion(v) {
|
||||||
// e.g v1.4.0
|
|
||||||
if (process.env.VUE_APP_SERVER_VERSION) {
|
if (process.env.VUE_APP_SERVER_VERSION) {
|
||||||
return process.env.VUE_APP_SERVER_VERSION;
|
return process.env.VUE_APP_SERVER_VERSION;
|
||||||
}
|
}
|
||||||
@@ -18,20 +17,21 @@ export function getVersion(v) {
|
|||||||
return `v${ semver.major(v) }.${ semver.minor(v) }.${ semver.patch(v) }`;
|
return `v${ semver.major(v) }.${ semver.minor(v) }.${ semver.patch(v) }`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// fallback to the latest version
|
// fallback to the latest version
|
||||||
return latestVersion(Object.keys(RELEASE_FEATURES));
|
return getLatestVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function latestVersion(versions) {
|
function getLatestVersion() {
|
||||||
return versions.sort((a, b) => semver.compare(a, b)).pop();
|
const allVersions = Object.keys(RELEASE_FEATURES).filter(semver.valid).sort(semver.rcompare);
|
||||||
|
|
||||||
|
return allVersions[0] || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// v1.3.3 => latest v1.3.x, v1.4.2 => latest v1.4.x
|
function getLatestCompatibleVersion(version) {
|
||||||
function latestMinorVersion(v) {
|
const allVersions = Object.keys(RELEASE_FEATURES).filter(semver.valid);
|
||||||
const minor = `v${ semver.major(v) }.${ semver.minor(v) }`;
|
const compatible = allVersions.filter((v) => semver.lte(v, version)).sort(semver.rcompare);
|
||||||
const minorVersions = Object.keys(RELEASE_FEATURES).filter((version) => version.startsWith(minor));
|
|
||||||
|
|
||||||
return latestVersion(minorVersions);
|
return compatible[0] || getLatestVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const featureEnabled = (featureKey, serverVersion) => {
|
export const featureEnabled = (featureKey, serverVersion) => {
|
||||||
@@ -46,12 +46,16 @@ export const featureEnabled = (featureKey, serverVersion) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let releasedFeatures = RELEASE_FEATURES[version];
|
const compatibleVersion = getLatestCompatibleVersion(version);
|
||||||
|
const releasedFeatures = RELEASE_FEATURES?.[compatibleVersion];
|
||||||
|
|
||||||
if (!releasedFeatures) {
|
if (!Array.isArray(releasedFeatures)) {
|
||||||
const fallback = latestMinorVersion(version);
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(
|
||||||
|
`Feature flags for version ${ version } are not defined. Please upgrade Harvester UI extension and check the support matrix.`
|
||||||
|
);
|
||||||
|
|
||||||
releasedFeatures = RELEASE_FEATURES[fallback];
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return releasedFeatures.includes(featureKey);
|
return releasedFeatures.includes(featureKey);
|
||||||
|
|||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
TAG_VERSION=$1
|
||||||
|
|
||||||
|
if [[ -z "$TAG_VERSION" ]]; then
|
||||||
|
echo "No tag version provided"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$(jq -r .version package.json)
|
||||||
|
EXTENSION_VERSION=$(jq -r .version pkg/harvester/package.json)
|
||||||
|
PKG_VERSION="v${EXTENSION_VERSION}"
|
||||||
|
|
||||||
|
if [[ "$VERSION" != "$EXTENSION_VERSION" ]]; then
|
||||||
|
echo "Package version mismatch: $VERSION vs $EXTENSION_VERSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${TAG_VERSION}" != "$PKG_VERSION" ]]; then
|
||||||
|
echo "Package version $PKG_VERSION in pkg/harvester/package.json does not match tag version ${TAG_VERSION}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Package version and tag version match: ${TAG_VERSION}"
|
||||||
Reference in New Issue
Block a user