mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-05 05:36:05 +08:00
Compare commits
72
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c92a06268 | ||
|
|
6256cdc97a | ||
|
|
4323132565 | ||
|
|
d05ec68e16 | ||
|
|
cb2cef5e73 | ||
|
|
45116b568a | ||
|
|
f42d798d10 | ||
|
|
50b4cf5248 | ||
|
|
cba37194b6 | ||
|
|
bbe0c4643f | ||
|
|
00584f1d19 | ||
|
|
4fa6470343 | ||
|
|
d550046189 | ||
|
|
5f32a9ade5 | ||
|
|
3f39d561be | ||
|
|
7c84269ed6 | ||
|
|
f63be73b11 | ||
|
|
fabc1cec21 | ||
|
|
ed2b6f2551 | ||
|
|
72282ae50e | ||
|
|
8fba4e7050 | ||
|
|
fbd4a1108f | ||
|
|
823a88c85b | ||
|
|
3a2c6e3a3d | ||
|
|
be6e9b5f06 | ||
|
|
a88bed9eb4 | ||
|
|
f97f14c811 | ||
|
|
f8a29a1c54 | ||
|
|
9a935434c2 | ||
|
|
afe632b723 | ||
|
|
d050f21f9a | ||
|
|
b87f367583 | ||
|
|
92aa8ec603 | ||
|
|
e013534373 | ||
|
|
d23c3d3b72 | ||
|
|
719c33fa12 | ||
|
|
a6bd7ec00e | ||
|
|
ac26fe2f0e | ||
|
|
790cade04d | ||
|
|
2903e86c02 | ||
|
|
2d747e435a | ||
|
|
eadda2a18b | ||
|
|
a178302f73 | ||
|
|
d376bfb3a5 | ||
|
|
7a84e08f99 | ||
|
|
3d39626b93 | ||
|
|
1c3b40b631 | ||
|
|
87baf75301 | ||
|
|
a10d651827 | ||
|
|
11ff7c8a7c | ||
|
|
f6dffe9816 | ||
|
|
b813bc578e | ||
|
|
7c3b2730ab | ||
|
|
429d9e18ad | ||
|
|
dc166d8fc9 | ||
|
|
3ff4534294 | ||
|
|
d4223ad2ae | ||
|
|
44297ada16 | ||
|
|
aea53fdd65 | ||
|
|
9640c87947 | ||
|
|
2cefd7cb78 | ||
|
|
fb6ffa3c0f | ||
|
|
e0436bff28 | ||
|
|
219f39ae52 | ||
|
|
41cf44d231 | ||
|
|
7d09cb7142 | ||
|
|
560643d299 | ||
|
|
c3bb0ea4be | ||
|
|
3c37f04ef6 | ||
|
|
d98db6ba49 | ||
|
|
531ed03796 | ||
|
|
59f260eab8 |
@@ -8,12 +8,16 @@
|
|||||||
- [ ] Yes, the backend owner is @
|
- [ ] Yes, the backend owner is @
|
||||||
- [ ] No, frontend-only.
|
- [ ] No, frontend-only.
|
||||||
|
|
||||||
### Related Issue #
|
### Related Issue
|
||||||
<!-- Link the issue as harvester/harvester#<issue_number> -->
|
<!-- Link the issue as harvester/harvester#<issue_number> -->
|
||||||
harvester/harvester#<issue_number>
|
harvester/harvester#<issue_number>
|
||||||
|
|
||||||
|
|
||||||
### Test screenshot or video (Required)
|
### Test Steps
|
||||||
<!-- Attach screenshot or video of the changes and eventual comparison if you find it necessary -->
|
<!-- Provide the test steps for reviewer to verify the issue -->
|
||||||
|
1. Go to ... page
|
||||||
|
|
||||||
|
### Test screenshot or video
|
||||||
|
<!-- Attach screenshot or video of the changes and eventual comparison -->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,13 @@ jobs:
|
|||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
|
|
||||||
|
# Tag builds (RC, formal and sprint releases) ship the tag as the package
|
||||||
|
# version. RC/formal tags already match package.json; sprint tags
|
||||||
|
# (vX.Y.Z-dev-YYYYMMDD) only share its prefix, so pin it here for the build.
|
||||||
|
- name: Pin package version to tag
|
||||||
|
if: ${{ inputs.CI_BUILD_TAG != '' }}
|
||||||
|
run: ./scripts/bump_version.sh "${{ inputs.CI_BUILD_TAG }}"
|
||||||
|
|
||||||
- id: ci-build-pkg
|
- id: ci-build-pkg
|
||||||
name: Build pkg
|
name: Build pkg
|
||||||
run: ./scripts/ci-build-pkg.sh harvester
|
run: ./scripts/ci-build-pkg.sh harvester
|
||||||
|
|||||||
@@ -58,6 +58,12 @@ jobs:
|
|||||||
- name: Setup Nodejs with yarn install
|
- name: Setup Nodejs with yarn install
|
||||||
uses: ./.github/actions/setup
|
uses: ./.github/actions/setup
|
||||||
|
|
||||||
|
# The catalog image is tagged with the package.json version. Sprint tags
|
||||||
|
# (vX.Y.Z-dev-YYYYMMDD) only share its prefix, so pin it to the tag here.
|
||||||
|
- name: Pin package version to tag
|
||||||
|
if: ${{ github.ref_type == 'tag' }}
|
||||||
|
run: ./scripts/bump_version.sh "${{ github.ref_name }}"
|
||||||
|
|
||||||
- name: Build and push UI image
|
- name: Build and push UI image
|
||||||
run: |
|
run: |
|
||||||
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
|
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
name: Build Standalone on PR Merge
|
name: Build Standalone on PR Merge
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch: # manually trigger on Github Actions only support PR branch pushed to harvester/harvester-ui-extension
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
@@ -29,3 +30,35 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
with:
|
with:
|
||||||
CI_BRANCH: ${{github.ref_name}}
|
CI_BRANCH: ${{github.ref_name}}
|
||||||
|
comment-on-pr:
|
||||||
|
name: Comment build result on PR
|
||||||
|
# Only for manual runs so the existing push / pull_request flows are untouched.
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- name: Add build artifact comment to the PR opened from this branch
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
BRANCH: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# Match scripts/version: sanitize the branch and map main -> latest.
|
||||||
|
if [ "$BRANCH" = "main" ]; then
|
||||||
|
DIR="latest"
|
||||||
|
else
|
||||||
|
DIR=$(printf '%s' "$BRANCH" | sed -E 's/[^a-zA-Z0-9.-]+/-/g')
|
||||||
|
fi
|
||||||
|
URL="https://releases.rancher.com/harvester-ui/dashboard/${DIR}/index.html"
|
||||||
|
|
||||||
|
PR=$(gh pr list --repo "$GITHUB_REPOSITORY" --state open --base main --head "$BRANCH" --json number --jq '.[0].number // empty')
|
||||||
|
if [ -z "$PR" ]; then
|
||||||
|
echo "No open PR found for branch $BRANCH, skipping comment."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
BODY=$(printf '🚀 **UI Artifact Build Status:** `SUCCESS` ✅\n> ui-index: %s\n> ui-source: external' "$URL")
|
||||||
|
gh pr comment "$PR" --repo "$GITHUB_REPOSITORY" --body "$BODY"
|
||||||
|
|||||||
+9
-7
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester-ui-extension",
|
"name": "harvester-ui-extension",
|
||||||
"version": "1.9.0-rc6",
|
"version": "1.10.0-dev",
|
||||||
"private": false,
|
"private": false,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.0.0"
|
"node": ">=24.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-transform-class-static-block": "7.29.7",
|
"@babel/plugin-transform-class-static-block": "7.29.7",
|
||||||
"@rancher/shell": "3.0.12-rc.7",
|
"@rancher/shell": "3.0.13",
|
||||||
"@vue-flow/background": "^1.3.0",
|
"@vue-flow/background": "^1.3.0",
|
||||||
"@vue-flow/controls": "^1.1.1",
|
"@vue-flow/controls": "^1.1.1",
|
||||||
"@vue-flow/core": "^1.33.5",
|
"@vue-flow/core": "^1.33.5",
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "25.9.5",
|
"@types/node": "25.9.6",
|
||||||
"cronstrue": "2.59.0",
|
"cronstrue": "2.59.0",
|
||||||
"d3-color": "3.1.0",
|
"d3-color": "3.1.0",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"merge": "2.1.1",
|
"merge": "2.1.1",
|
||||||
"node-forge": "1.4.0",
|
"node-forge": "1.4.0",
|
||||||
"nth-check": "2.1.1",
|
"nth-check": "2.1.1",
|
||||||
"qs": "6.15.3",
|
"qs": "6.16.0",
|
||||||
"roarr": "7.21.7",
|
"roarr": "7.21.7",
|
||||||
"semver": "7.8.5",
|
"semver": "7.8.5",
|
||||||
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
||||||
@@ -48,15 +48,17 @@
|
|||||||
"dev": "NODE_ENV=dev ./node_modules/.bin/vue-cli-service serve",
|
"dev": "NODE_ENV=dev ./node_modules/.bin/vue-cli-service serve",
|
||||||
"build": "./node_modules/.bin/vue-cli-service build",
|
"build": "./node_modules/.bin/vue-cli-service build",
|
||||||
"clean": "./node_modules/@rancher/shell/scripts/clean",
|
"clean": "./node_modules/@rancher/shell/scripts/clean",
|
||||||
"lint": "./node_modules/.bin/eslint --max-warnings 0 --ext .js,.ts,.vue .",
|
"lint": "./node_modules/@rancher/shell/scripts/lint --max-warnings 0 --ext .js,.ts,.vue .",
|
||||||
"lint:fix": "./node_modules/.bin/eslint --fix --max-warnings 0 --ext .js,.ts,.vue .",
|
"lint:fix": "./node_modules/@rancher/shell/scripts/lint --fix --max-warnings 0 --ext .js,.ts,.vue .",
|
||||||
"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",
|
"commitlint": "commitlint --edit",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"agents:generate": "./scripts/generate-agent-and-persona-mds.sh"
|
"agents:generate": "./scripts/generate-agent-and-persona-mds.sh",
|
||||||
|
"prelint": "npm --prefix node_modules/@rancher/shell/eslint-legacy install eslint-plugin-jest@^24 --no-save --no-audit --no-fund --legacy-peer-deps --loglevel=error",
|
||||||
|
"prelint:fix": "npm --prefix node_modules/@rancher/shell/eslint-legacy install eslint-plugin-jest@^24 --no-save --no-audit --no-fund --legacy-peer-deps --loglevel=error"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/load": "^19.8.1",
|
"@commitlint/load": "^19.8.1",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.8 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.8 KiB |
@@ -10,6 +10,7 @@ import { docLink } from '../utils/feature-flags';
|
|||||||
const CATEGORY = {
|
const CATEGORY = {
|
||||||
ui: [
|
ui: [
|
||||||
'branding',
|
'branding',
|
||||||
|
HCI_SETTING.SERVER_FLAVOR,
|
||||||
'ui-source',
|
'ui-source',
|
||||||
'ui-index',
|
'ui-index',
|
||||||
]
|
]
|
||||||
@@ -53,7 +54,7 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: { ...mapGetters({ t: 'i18n/t' }) },
|
computed: { ...mapGetters({ t: 'i18n/t', isHarvesterPrime: 'harvester-common/isHarvesterPrime' }) },
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
settings: {
|
settings: {
|
||||||
@@ -231,14 +232,21 @@ export default {
|
|||||||
v-if="setting.customized"
|
v-if="setting.customized"
|
||||||
class="modified"
|
class="modified"
|
||||||
>
|
>
|
||||||
Modified
|
{{ t('advancedSettings.tags.modified.label') }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="setting.experimental"
|
v-if="setting.experimental"
|
||||||
v-clean-tooltip="t('advancedSettings.experimental')"
|
v-clean-tooltip="t('advancedSettings.tags.experimental.description')"
|
||||||
class="experimental"
|
class="experimental"
|
||||||
>
|
>
|
||||||
Experimental
|
{{ t('advancedSettings.tags.experimental.label') }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="setting.preview"
|
||||||
|
v-clean-tooltip="t('advancedSettings.tags.preview.description')"
|
||||||
|
class="preview"
|
||||||
|
>
|
||||||
|
{{ t('advancedSettings.tags.preview.label') }}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<h2 v-clean-html="t(setting.description, getDocLinkParams(setting) || {}, true)">
|
<h2 v-clean-html="t(setting.description, getDocLinkParams(setting) || {}, true)">
|
||||||
@@ -262,7 +270,8 @@ export default {
|
|||||||
v-if="!setting.hide"
|
v-if="!setting.hide"
|
||||||
class="settings-value"
|
class="settings-value"
|
||||||
>
|
>
|
||||||
<pre v-if="setting.kind === 'json'">{{ setting.json }}</pre>
|
<pre v-if="setting.id === 'branding' && isHarvesterPrime">{{ $store.getters['harvester-common/privateLabel'] }}</pre>
|
||||||
|
<pre v-else-if="setting.kind === 'json'">{{ setting.json }}</pre>
|
||||||
<pre v-else-if="setting.kind === 'multiline'">{{ setting.data.value || setting.data.default }}</pre>
|
<pre v-else-if="setting.kind === 'multiline'">{{ setting.data.value || setting.data.default }}</pre>
|
||||||
<pre v-else-if="setting.kind === 'enum'">{{ t(setting.enum) }}</pre>
|
<pre v-else-if="setting.kind === 'enum'">{{ t(setting.enum) }}</pre>
|
||||||
<pre v-else-if="setting.kind === 'custom' && setting.custom">{{ setting.custom }}</pre>
|
<pre v-else-if="setting.kind === 'custom' && setting.custom">{{ setting.custom }}</pre>
|
||||||
@@ -352,21 +361,19 @@ export default {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modified {
|
.modified,
|
||||||
|
.experimental,
|
||||||
|
.preview {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
border: 1px solid var(--primary);
|
border: 1px solid;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.experimental {
|
.modified { border-color: var(--primary); }
|
||||||
margin-left: 10px;
|
.experimental { border-color: var(--error); }
|
||||||
border: 1px solid var(--error);
|
.preview { border-color: var(--warning); }
|
||||||
border-radius: 5px;
|
|
||||||
padding: 2px 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-search-match {
|
.no-search-match {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ const FEATURE_FLAGS = {
|
|||||||
'staticIPForVM',
|
'staticIPForVM',
|
||||||
'fsFreezeDeadline',
|
'fsFreezeDeadline',
|
||||||
],
|
],
|
||||||
|
'v1.9.1': [
|
||||||
|
'lvmDmThinOnly',
|
||||||
|
],
|
||||||
|
'v1.10.0': [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateFeatureFlags = () => {
|
const generateFeatureFlags = () => {
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ import {
|
|||||||
} from './table-headers';
|
} from './table-headers';
|
||||||
import { ADD_ONS } from './harvester-map';
|
import { ADD_ONS } from './harvester-map';
|
||||||
import { registerAddonSideNav } from '../utils/dynamic-nav';
|
import { registerAddonSideNav } from '../utils/dynamic-nav';
|
||||||
|
import { setVendor } from '@shell/config/private-label';
|
||||||
|
import { createCssVars } from '@shell/utils/color';
|
||||||
|
|
||||||
const TEMPLATE = HCI.VM_VERSION;
|
const TEMPLATE = HCI.VM_VERSION;
|
||||||
const MONITORING_GROUP = 'Monitoring & Logging::Monitoring';
|
const MONITORING_GROUP = 'Monitoring & Logging::Monitoring';
|
||||||
@@ -109,15 +111,51 @@ export function init($plugin, store) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
store.dispatch('setIsSingleProduct', {
|
store.watch(
|
||||||
productName: PRODUCT_NAME,
|
(_state, getters) => JSON.stringify([
|
||||||
logo: require(`@shell/assets/images/providers/harvester.svg`),
|
!!getters['harvester-common/isHarvesterPrime'],
|
||||||
productNameKey: 'harvester.productLabel',
|
getters['prefs/theme'] === 'dark',
|
||||||
getVersionInfo: (store) => store.getters[`${ PRODUCT_NAME }/byId`]?.(HCI.SETTING, 'server-version')?.value || 'unknown',
|
getters['harvester-common/privateLabel']
|
||||||
afterLoginRoute: home,
|
]),
|
||||||
logoRoute: home,
|
(value) => {
|
||||||
supportCustomLogo: true
|
const [isHarvesterPrime, isDark, privateLabel] = JSON.parse(value);
|
||||||
});
|
const primeLogo = isDark ? require('../assets/images/dark/suse-virtualization.svg') : require('../assets/images/suse-virtualization.svg');
|
||||||
|
|
||||||
|
setVendor(privateLabel || '');
|
||||||
|
|
||||||
|
let primaryColorStyle = document.getElementById('harvester-prime-primary-color');
|
||||||
|
|
||||||
|
// set harvester prime primary color style to #4dd192 (Cyan Green)
|
||||||
|
if (isHarvesterPrime) {
|
||||||
|
if (!primaryColorStyle) {
|
||||||
|
primaryColorStyle = document.createElement('style');
|
||||||
|
primaryColorStyle.id = 'harvester-prime-primary-color';
|
||||||
|
document.head.appendChild(primaryColorStyle);
|
||||||
|
primaryColorStyle.sheet.insertRule('body.theme-light, body.theme-dark {}', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = createCssVars('#4dd192', isDark ? 'dark' : 'light');
|
||||||
|
const rule = primaryColorStyle.sheet.cssRules[0];
|
||||||
|
|
||||||
|
Object.entries(colors).forEach(([property, color]) => rule.style.setProperty(property.trim(), color));
|
||||||
|
} else {
|
||||||
|
primaryColorStyle?.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const primeLabelKey = privateLabel ? 'harvester.branding.primeLabel' : 'harvester.branding.primeEmptyLabel';
|
||||||
|
|
||||||
|
store.dispatch('setIsSingleProduct', {
|
||||||
|
productName: PRODUCT_NAME,
|
||||||
|
logo: isHarvesterPrime ? primeLogo : require(`@shell/assets/images/providers/harvester.svg`),
|
||||||
|
productNameKey: isHarvesterPrime ? primeLabelKey : 'harvester.productLabel',
|
||||||
|
getVersionInfo: (store) => store.getters[`${ PRODUCT_NAME }/byId`]?.(HCI.SETTING, 'server-version')?.value || 'unknown',
|
||||||
|
afterLoginRoute: home,
|
||||||
|
logoRoute: home,
|
||||||
|
supportCustomLogo: !isHarvesterPrime
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
product({
|
product({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export const HCI_SETTING = {
|
|||||||
CONTAINERD_REGISTRY: 'containerd-registry',
|
CONTAINERD_REGISTRY: 'containerd-registry',
|
||||||
LOG_LEVEL: 'log-level',
|
LOG_LEVEL: 'log-level',
|
||||||
SERVER_VERSION: 'server-version',
|
SERVER_VERSION: 'server-version',
|
||||||
|
SERVER_FLAVOR: 'server-flavor',
|
||||||
UI_INDEX: 'ui-index',
|
UI_INDEX: 'ui-index',
|
||||||
UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled',
|
UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled',
|
||||||
UPGRADE_CHECKER_URL: 'upgrade-checker-url',
|
UPGRADE_CHECKER_URL: 'upgrade-checker-url',
|
||||||
@@ -68,6 +69,7 @@ export const HCI_ALLOWED_SETTINGS = {
|
|||||||
kind: 'json', from: 'import', featureFlag: 'csiOnlineExpandValidation'
|
kind: 'json', from: 'import', featureFlag: 'csiOnlineExpandValidation'
|
||||||
},
|
},
|
||||||
[HCI_SETTING.SERVER_VERSION]: { readOnly: true },
|
[HCI_SETTING.SERVER_VERSION]: { readOnly: true },
|
||||||
|
[HCI_SETTING.SERVER_FLAVOR]: { readOnly: true },
|
||||||
[HCI_SETTING.UPGRADE_CHECKER_ENABLED]: { kind: 'boolean' },
|
[HCI_SETTING.UPGRADE_CHECKER_ENABLED]: { kind: 'boolean' },
|
||||||
[HCI_SETTING.UPGRADE_CHECKER_URL]: { kind: 'url' },
|
[HCI_SETTING.UPGRADE_CHECKER_URL]: { kind: 'url' },
|
||||||
[HCI_SETTING.HTTP_PROXY]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.HTTP_PROXY]: { kind: 'json', from: 'import' },
|
||||||
@@ -111,15 +113,17 @@ export const HCI_ALLOWED_SETTINGS = {
|
|||||||
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { kind: 'number', featureFlag: 'kubeconfigDefaultTokenTTLMinutesSetting' },
|
[HCI_SETTING.KUBECONFIG_DEFAULT_TOKEN_TTL_MINUTES]: { kind: 'number', featureFlag: 'kubeconfigDefaultTokenTTLMinutesSetting' },
|
||||||
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: {
|
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_ENABLED]: {
|
||||||
kind: 'boolean',
|
kind: 'boolean',
|
||||||
experimental: true,
|
preview: true,
|
||||||
featureFlag: 'longhornV2LVMSupport'
|
featureFlag: 'longhornV2LVMSupport'
|
||||||
},
|
},
|
||||||
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_HUGEPAGE_ENABLED]: {
|
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_HUGEPAGE_ENABLED]: {
|
||||||
kind: 'boolean',
|
kind: 'boolean',
|
||||||
|
preview: true,
|
||||||
featureFlag: 'longhornV2HugepageSettings'
|
featureFlag: 'longhornV2HugepageSettings'
|
||||||
},
|
},
|
||||||
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_MEMORY_SIZE]: {
|
[HCI_SETTING.LONGHORN_V2_DATA_ENGINE_MEMORY_SIZE]: {
|
||||||
kind: 'number',
|
kind: 'number',
|
||||||
|
preview: true,
|
||||||
featureFlag: 'longhornV2HugepageSettings'
|
featureFlag: 'longhornV2HugepageSettings'
|
||||||
},
|
},
|
||||||
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
|
[HCI_SETTING.ADDITIONAL_GUEST_MEMORY_OVERHEAD_RATIO]: { kind: 'string', from: 'import' },
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ export const VOLUME_MODE = {
|
|||||||
FILE_SYSTEM: 'Filesystem'
|
FILE_SYSTEM: 'Filesystem'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ACCESS_MODE = {
|
||||||
|
READ_WRITE_MANY: 'ReadWriteMany',
|
||||||
|
READ_WRITE_ONCE: 'ReadWriteOnce',
|
||||||
|
READ_ONLY_MANY: 'ReadOnlyMany',
|
||||||
|
READ_WRITE_ONCE_POD: 'ReadWriteOncePod',
|
||||||
|
};
|
||||||
|
|
||||||
export const NETWORK_PROTOCOL = {
|
export const NETWORK_PROTOCOL = {
|
||||||
IPv4: 'IPv4',
|
IPv4: 'IPv4',
|
||||||
IPv6: 'IPv6',
|
IPv6: 'IPv6',
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
<script>
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import jsyaml from 'js-yaml';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { Card } from '@components/Card';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import { escapeHtml } from '@shell/utils/string';
|
||||||
|
|
||||||
|
const DEFAULT_VALUE = {
|
||||||
|
rancher: {
|
||||||
|
hostname: '',
|
||||||
|
version: '',
|
||||||
|
bootstrapPassword: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HarvesterEnableK3k',
|
||||||
|
|
||||||
|
emits: ['close'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
AsyncButton,
|
||||||
|
Card,
|
||||||
|
LabeledInput,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
resources: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
const addon = this.resources[0];
|
||||||
|
let valuesContentJson;
|
||||||
|
|
||||||
|
try {
|
||||||
|
valuesContentJson = merge({}, DEFAULT_VALUE, jsyaml.load(addon.spec.valuesContent));
|
||||||
|
} catch (e) {
|
||||||
|
valuesContentJson = merge({}, DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valuesContentJson };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({ t: 'i18n/t' }),
|
||||||
|
|
||||||
|
buttonDisabled() {
|
||||||
|
const { rancher } = this.valuesContentJson;
|
||||||
|
|
||||||
|
return !(rancher?.hostname || '').trim() || !(rancher?.version || '').trim() || !(rancher?.bootstrapPassword || '').trim();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
async enable(buttonCb) {
|
||||||
|
const addon = this.resources[0];
|
||||||
|
|
||||||
|
try {
|
||||||
|
addon.spec.valuesContent = jsyaml.dump(this.valuesContentJson);
|
||||||
|
addon.spec.enabled = true;
|
||||||
|
await addon.save();
|
||||||
|
buttonCb(true);
|
||||||
|
this.close();
|
||||||
|
} catch (err) {
|
||||||
|
addon.spec.enabled = false;
|
||||||
|
this.$store.dispatch('growl/fromError', {
|
||||||
|
title: this.t('generic.notification.title.error', { name: escapeHtml(addon.metadata.name) }),
|
||||||
|
err,
|
||||||
|
}, { root: true });
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card :show-highlight-border="false">
|
||||||
|
<template #title>
|
||||||
|
<h4
|
||||||
|
v-clean-html="t('harvester.addons.rancherK3k.enable.title')"
|
||||||
|
class="text-default-text"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<div class="body">
|
||||||
|
<div class="row mb-15">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="valuesContentJson.rancher.hostname"
|
||||||
|
:required="true"
|
||||||
|
:label="t('harvester.addons.rancherK3k.hostname')"
|
||||||
|
placeholder="rancher.$vip.nip.io"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="valuesContentJson.rancher.version"
|
||||||
|
:required="true"
|
||||||
|
:label="t('harvester.addons.rancherK3k.version')"
|
||||||
|
placeholder="v1.14.0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-15">
|
||||||
|
<div class="col span-12">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="valuesContentJson.rancher.bootstrapPassword"
|
||||||
|
:required="true"
|
||||||
|
type="password"
|
||||||
|
:label="t('harvester.addons.rancherK3k.password')"
|
||||||
|
:tooltip="t('harvester.addons.rancherK3k.passwordTooltip')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<div class="buttons actions">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary mr-10"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<AsyncButton
|
||||||
|
mode="enable"
|
||||||
|
:disabled="buttonDisabled"
|
||||||
|
@click="enable"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
<script>
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import jsyaml from 'js-yaml';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
import { RadioGroup } from '@components/Form/Radio';
|
||||||
|
|
||||||
|
const DEFAULT_VALUE = {
|
||||||
|
certManagerVersion: 'v1.20.2',
|
||||||
|
rancher: {
|
||||||
|
hostname: '',
|
||||||
|
version: '',
|
||||||
|
bootstrapPassword: '',
|
||||||
|
},
|
||||||
|
k3kCluster: {
|
||||||
|
servers: 1,
|
||||||
|
version: 'v1.35.4-k3s1',
|
||||||
|
storageClassName: 'harvester-longhorn',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'EditAddonRancherK3k',
|
||||||
|
components: { LabeledInput, RadioGroup },
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
registerBeforeHook: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
let valuesContentJson = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
valuesContentJson = merge({}, DEFAULT_VALUE, jsyaml.load(this.value.spec.valuesContent));
|
||||||
|
} catch (err) {
|
||||||
|
valuesContentJson = merge({}, DEFAULT_VALUE);
|
||||||
|
|
||||||
|
this.$store.dispatch('growl/fromError', {
|
||||||
|
title: this.$store.getters['i18n/t']('generic.notification.title.error'),
|
||||||
|
err: err.data || err,
|
||||||
|
}, { root: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valuesContentJson };
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
if (this.registerBeforeHook) {
|
||||||
|
this.registerBeforeHook(this.willSave, 'willSave');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
willSave() {
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
if (!this.value.spec.enabled) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.valuesContentJson.rancher.hostname) {
|
||||||
|
errors.push(this.t('validation.required', { key: this.t('harvester.addons.rancherK3k.hostname') }, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.valuesContentJson.rancher.version) {
|
||||||
|
errors.push(this.t('validation.required', { key: this.t('harvester.addons.rancherK3k.version') }, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.valuesContentJson.rancher.bootstrapPassword) {
|
||||||
|
errors.push(this.t('validation.required', { key: this.t('harvester.addons.rancherK3k.password') }, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Promise.reject(errors);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
valuesContentJson: {
|
||||||
|
handler(neu) {
|
||||||
|
this.value.spec['valuesContent'] = jsyaml.dump(neu);
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col span-12">
|
||||||
|
<RadioGroup
|
||||||
|
v-model:value="value.spec.enabled"
|
||||||
|
class="mb-20"
|
||||||
|
name="model"
|
||||||
|
:mode="mode"
|
||||||
|
:options="[true,false]"
|
||||||
|
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="value.spec.enabled">
|
||||||
|
<div class="row mb-20">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="valuesContentJson.rancher.hostname"
|
||||||
|
label-key="harvester.addons.rancherK3k.hostname"
|
||||||
|
:required="true"
|
||||||
|
:mode="mode"
|
||||||
|
placeholder="rancher.$vip.nip.io"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="valuesContentJson.rancher.version"
|
||||||
|
label-key="harvester.addons.rancherK3k.version"
|
||||||
|
:required="true"
|
||||||
|
:mode="mode"
|
||||||
|
placeholder="v1.14.0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-20">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="valuesContentJson.rancher.bootstrapPassword"
|
||||||
|
label-key="harvester.addons.rancherK3k.password"
|
||||||
|
:mode="mode"
|
||||||
|
:required="true"
|
||||||
|
type="password"
|
||||||
|
:tooltip="t('harvester.addons.rancherK3k.passwordTooltip')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep() .radio-group {
|
||||||
|
display: flex;
|
||||||
|
.radio-container {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,6 +7,7 @@ import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|||||||
import YamlEditor from '@shell/components/YamlEditor';
|
import YamlEditor from '@shell/components/YamlEditor';
|
||||||
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
|
import FormValidation from '@shell/mixins/form-validation';
|
||||||
import { HCI } from '@pkg/harvester/config/labels-annotations';
|
import { HCI } from '@pkg/harvester/config/labels-annotations';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -23,14 +24,19 @@ export default {
|
|||||||
NameNsDescription,
|
NameNsDescription,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
mixins: [CreateEditView, FormValidation],
|
||||||
|
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
config: this.value.data?.cloudInit || '',
|
config: this.value.data?.cloudInit || '',
|
||||||
type: this.value?.metadata?.labels?.[HCI.CLOUD_INIT] || 'user',
|
type: this.value?.metadata?.labels?.[HCI.CLOUD_INIT] || 'user',
|
||||||
|
fvFormRuleSets: [{
|
||||||
|
path: 'metadata.name',
|
||||||
|
rules: ['required'],
|
||||||
|
translationKey: 'nameNsDescription.name.label',
|
||||||
|
}],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -78,6 +84,7 @@ export default {
|
|||||||
:resource="value"
|
:resource="value"
|
||||||
:errors="errors"
|
:errors="errors"
|
||||||
:apply-hooks="applyHooks"
|
:apply-hooks="applyHooks"
|
||||||
|
:validation-passed="fvFormIsValid"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors=e"
|
||||||
@@ -86,6 +93,7 @@ export default {
|
|||||||
:value="value"
|
:value="value"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:namespaced="true"
|
:namespaced="true"
|
||||||
|
:rules="{ name: fvGetAndReportPathRules('metadata.name') }"
|
||||||
@update:value="$emit('update:value', $event)"
|
@update:value="$emit('update:value', $event)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
import ArrayList from '@shell/components/form/ArrayList';
|
import ArrayList from '@shell/components/form/ArrayList';
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
@@ -10,6 +10,10 @@ import { HCI_SETTING } from '../../config/settings';
|
|||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import { _EDIT, _CREATE } from '@shell/config/query-params';
|
import { _EDIT, _CREATE } from '@shell/config/query-params';
|
||||||
|
|
||||||
|
const {
|
||||||
|
READ_WRITE_ONCE, READ_ONLY_MANY, READ_WRITE_MANY, READ_WRITE_ONCE_POD
|
||||||
|
} = ACCESS_MODE;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CDISettings',
|
name: 'CDISettings',
|
||||||
|
|
||||||
@@ -90,10 +94,10 @@ export default {
|
|||||||
|
|
||||||
accessModeOptions() {
|
accessModeOptions() {
|
||||||
return [
|
return [
|
||||||
{ label: 'ReadWriteOnce', value: 'ReadWriteOnce' },
|
{ label: READ_WRITE_ONCE, value: READ_WRITE_ONCE },
|
||||||
{ label: 'ReadOnlyMany', value: 'ReadOnlyMany' },
|
{ label: READ_ONLY_MANY, value: READ_ONLY_MANY },
|
||||||
{ label: 'ReadWriteMany', value: 'ReadWriteMany' },
|
{ label: READ_WRITE_MANY, value: READ_WRITE_MANY },
|
||||||
{ label: 'ReadWriteOncePod', value: 'ReadWriteOncePod' },
|
{ label: READ_WRITE_ONCE_POD, value: READ_WRITE_ONCE_POD },
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -7,8 +7,12 @@ import { allHash } from '@shell/utils/promise';
|
|||||||
import { clone } from '@shell/utils/object';
|
import { clone } from '@shell/utils/object';
|
||||||
import { HCI } from '../../../types';
|
import { HCI } from '../../../types';
|
||||||
import { NODE } from '@shell/config/types';
|
import { NODE } from '@shell/config/types';
|
||||||
|
import { _CREATE, _CLONE } from '@shell/config/query-params';
|
||||||
import { LVM_TOPOLOGY_LABEL } from '../index.vue';
|
import { LVM_TOPOLOGY_LABEL } from '../index.vue';
|
||||||
|
|
||||||
|
const STRIPED_TYPE = 'striped';
|
||||||
|
const DM_THIN_TYPE = 'dm-thin';
|
||||||
|
|
||||||
const DEFAULT_PARAMETERS = [
|
const DEFAULT_PARAMETERS = [
|
||||||
'type',
|
'type',
|
||||||
'vgName'
|
'vgName'
|
||||||
@@ -54,8 +58,17 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
const node = (this.value.allowedTopologies?.[0]?.matchLabelExpressions || []).find((t) => t.key === LVM_TOPOLOGY_LABEL)?.values[0];
|
const node = (this.value.allowedTopologies?.[0]?.matchLabelExpressions || []).find((t) => t.key === LVM_TOPOLOGY_LABEL)?.values[0];
|
||||||
|
|
||||||
|
// striped is dropped once the LVM add-on reaches GA (v1.9.1); dm-thin is the only supported type.
|
||||||
|
const dmThinOnly = this.$store.getters['harvester-common/getFeatureEnabled']('lvmDmThinOnly');
|
||||||
|
const volumeGroupTypes = dmThinOnly ? [DM_THIN_TYPE] : [STRIPED_TYPE, DM_THIN_TYPE];
|
||||||
|
|
||||||
|
// Default new storage classes to dm-thin, and reset a cloned striped type that is no longer selectable.
|
||||||
|
if (dmThinOnly && [_CREATE, _CLONE].includes(this.realMode) && !volumeGroupTypes.includes(this.value.parameters.type)) {
|
||||||
|
this.value.parameters.type = DM_THIN_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
volumeGroupTypes: ['striped', 'dm-thin'],
|
volumeGroupTypes,
|
||||||
node,
|
node,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ import { HCI, VOLUME_SNAPSHOT } from '../types';
|
|||||||
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
||||||
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
|
||||||
import { GIBIBYTE } from '../utils/unit';
|
import { GIBIBYTE } from '../utils/unit';
|
||||||
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
||||||
import { isInternalStorageClass } from '../utils/storage-class';
|
import { isInternalStorageClass } from '../utils/storage-class';
|
||||||
|
|
||||||
|
const { READ_WRITE_MANY, READ_WRITE_ONCE, READ_ONLY_MANY } = ACCESS_MODE;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterVolume',
|
name: 'HarvesterVolume',
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ export default {
|
|||||||
if (this.mode === _CREATE) {
|
if (this.mode === _CREATE) {
|
||||||
// default volumeMode to Block
|
// default volumeMode to Block
|
||||||
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
|
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
|
||||||
this.value.spec.accessModes = ['ReadWriteMany'];
|
this.value.spec.accessModes = [READ_WRITE_MANY];
|
||||||
}
|
}
|
||||||
|
|
||||||
const storage = this.value?.spec?.resources?.requests?.storage || null;
|
const storage = this.value?.spec?.resources?.requests?.storage || null;
|
||||||
@@ -160,7 +162,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
accessModeOptions() {
|
accessModeOptions() {
|
||||||
return ['ReadWriteOnce', 'ReadWriteMany', 'ReadOnlyMany'];
|
return [READ_WRITE_ONCE, READ_WRITE_MANY, READ_ONLY_MANY];
|
||||||
},
|
},
|
||||||
|
|
||||||
imageOption() {
|
imageOption() {
|
||||||
@@ -355,7 +357,7 @@ export default {
|
|||||||
|
|
||||||
getAccessMode() {
|
getAccessMode() {
|
||||||
if (!this.longhornV2LVMSupport) {
|
if (!this.longhornV2LVMSupport) {
|
||||||
return ['ReadWriteMany'];
|
return [READ_WRITE_MANY];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.value?.spec?.accessModes && this.value?.spec?.accessModes?.length > 0) {
|
if (this.value?.spec?.accessModes && this.value?.spec?.accessModes?.length > 0) {
|
||||||
@@ -371,7 +373,7 @@ export default {
|
|||||||
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.value.thirdPartyStorageFeatureEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.value.thirdPartyStorageFeatureEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
|
return readWriteOnce ? [READ_WRITE_ONCE] : [READ_WRITE_MANY];
|
||||||
},
|
},
|
||||||
buildDataVolumeObj() {
|
buildDataVolumeObj() {
|
||||||
const storage = {
|
const storage = {
|
||||||
|
|||||||
@@ -246,4 +246,27 @@ export default {
|
|||||||
.group-actions {
|
.group-actions {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match the enable/disable passthrough bulk-action buttons height to the .btn (40px) on the right.
|
||||||
|
:deep(.bulk .rc-button.btn-medium.bulk-action:not(.btn-sm)) {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the collapsed "Actions" dropdown button 40px too.
|
||||||
|
:deep(.bulk .rc-button.btn-medium.bulk-actions-dropdown:not(.btn-sm)) {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lay the bulk row out with flex so the "N selected" label can wrap below the buttons.
|
||||||
|
:deep(.bulk) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.bulk .action-availability) {
|
||||||
|
flex-basis: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|||||||
import Banner from '@components/Banner/Banner.vue';
|
import Banner from '@components/Banner/Banner.vue';
|
||||||
import remove from 'lodash/remove';
|
import remove from 'lodash/remove';
|
||||||
import { set } from '@shell/utils/object';
|
import { set } from '@shell/utils/object';
|
||||||
|
import { uniq } from '@shell/utils/array';
|
||||||
import { HCI } from '../../../types';
|
import { HCI } from '../../../types';
|
||||||
import DeviceList from './DeviceList';
|
import DeviceList from './DeviceList';
|
||||||
import CompatibilityMatrix from '../CompatibilityMatrix';
|
import CompatibilityMatrix from '../CompatibilityMatrix';
|
||||||
@@ -60,7 +61,15 @@ export default {
|
|||||||
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
|
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
vmDevices.forEach(({ name }) => {
|
// When off, read from spec; otherwise the spec name is a placeholder ('provisioned'),
|
||||||
|
// so read the real allocated device names from the deviceAllocationDetails annotation.
|
||||||
|
const hostDeviceNames = this.vm.isOff ? [
|
||||||
|
...vmDevices.map(({ name }) => name),
|
||||||
|
] : [
|
||||||
|
...Object.values(this.vm?.provisionedHostDevices || {}).reduce((acc, devices) => [...acc, ...devices], []),
|
||||||
|
];
|
||||||
|
|
||||||
|
uniq(hostDeviceNames).forEach((name) => {
|
||||||
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
||||||
selectedDevices.push(name);
|
selectedDevices.push(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
|
|||||||
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
|
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
|
||||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
|
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
|
||||||
import { HCI } from '../../../types';
|
import { HCI } from '../../../types';
|
||||||
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
||||||
import { OFF } from '../../../models/kubevirt.io.virtualmachine';
|
import { OFF } from '../../../models/kubevirt.io.virtualmachine';
|
||||||
import { EMPTY_IMAGE } from '../../../utils/vm';
|
import { EMPTY_IMAGE } from '../../../utils/vm';
|
||||||
|
|
||||||
|
const { READ_WRITE_MANY } = ACCESS_MODE;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['update:value'],
|
emits: ['update:value'],
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ export default {
|
|||||||
|
|
||||||
customAccessMode: {
|
customAccessMode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'ReadWriteMany'
|
default: READ_WRITE_MANY
|
||||||
},
|
},
|
||||||
|
|
||||||
resourceType: {
|
resourceType: {
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storagec
|
|||||||
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
|
||||||
import { GIBIBYTE } from '../../../../utils/unit';
|
import { GIBIBYTE } from '../../../../utils/unit';
|
||||||
import { isInternalStorageClass } from '../../../../utils/storage-class';
|
import { isInternalStorageClass } from '../../../../utils/storage-class';
|
||||||
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
|
const { READ_WRITE_MANY, READ_WRITE_ONCE } = ACCESS_MODE;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditVolume',
|
name: 'HarvesterEditVolume',
|
||||||
@@ -205,7 +207,7 @@ export default {
|
|||||||
|
|
||||||
getAccessMode(storageClassName) {
|
getAccessMode(storageClassName) {
|
||||||
if (!this.longhornV2LVMSupport) {
|
if (!this.longhornV2LVMSupport) {
|
||||||
return 'ReadWriteMany';
|
return READ_WRITE_MANY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
||||||
@@ -216,7 +218,7 @@ export default {
|
|||||||
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.thirdPartyStorageClassEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.thirdPartyStorageClassEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readWriteOnce ? 'ReadWriteOnce' : 'ReadWriteMany';
|
return readWriteOnce ? READ_WRITE_ONCE : READ_WRITE_MANY;
|
||||||
},
|
},
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ dialog:
|
|||||||
|
|
||||||
harvester:
|
harvester:
|
||||||
branding:
|
branding:
|
||||||
|
primeLabel: '{vendor}'
|
||||||
|
primeEmptyLabel: ''
|
||||||
logos:
|
logos:
|
||||||
tip: 'Upload a logo to replace the Harvester logo in the top-level navigation header. Image height should be 21 pixels with a max width of 200 pixels. Max file size is 20KB. Accepted formats: JPEG, PNG, SVG.'
|
tip: 'Upload a logo to replace the Harvester logo in the top-level navigation header. Image height should be 21 pixels with a max width of 200 pixels. Max file size is 20KB. Accepted formats: JPEG, PNG, SVG.'
|
||||||
favicon:
|
favicon:
|
||||||
@@ -1832,6 +1834,8 @@ harvester:
|
|||||||
'rancher-logging': rancher-logging is an add-on to collect versatile logs, events, and audits from the Harvester cluster and route them to many kinds of servers based on flows.
|
'rancher-logging': rancher-logging is an add-on to collect versatile logs, events, and audits from the Harvester cluster and route them to many kinds of servers based on flows.
|
||||||
'rancher-monitoring': rancher-monitoring is an add-on to collect Harvester cluster and virtual machine metrics, view them on the embedded dashboard, and send alert(s) to remote servers.
|
'rancher-monitoring': rancher-monitoring is an add-on to collect Harvester cluster and virtual machine metrics, view them on the embedded dashboard, and send alert(s) to remote servers.
|
||||||
'rancher-vcluster': rancher-vcluster deploys a virtual cluster (vcluster) with Rancher installed.
|
'rancher-vcluster': rancher-vcluster deploys a virtual cluster (vcluster) with Rancher installed.
|
||||||
|
'k3k': k3k is an add-on to deploy the k3k controller.
|
||||||
|
'rancher-k3k': rancher-k3k deploys a k3k virtual cluster with Rancher installed.
|
||||||
'harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
|
'harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
|
||||||
'harvester-system/harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
|
'harvester-system/harvester-seeder': harvester-seeder is an add-on that uses IPMI and Redfish to discover hardware information and perform out-of-band operations.
|
||||||
'harvester-csi-driver-lvm': harvester-csi-driver-lvm is an add-on allowing users to create PVC through the LVM with local devices.
|
'harvester-csi-driver-lvm': harvester-csi-driver-lvm is an add-on allowing users to create PVC through the LVM with local devices.
|
||||||
@@ -2136,6 +2140,14 @@ harvester:
|
|||||||
hostname: Hostname
|
hostname: Hostname
|
||||||
rancherVersion: Rancher Version
|
rancherVersion: Rancher Version
|
||||||
password: Bootstrap Password
|
password: Bootstrap Password
|
||||||
|
rancherK3k:
|
||||||
|
accessRancher: Access the Rancher Dashboard
|
||||||
|
hostname: Hostname
|
||||||
|
version: Rancher Version
|
||||||
|
password: Bootstrap Password
|
||||||
|
passwordTooltip: The initial password in Rancher for the first time login.
|
||||||
|
enable:
|
||||||
|
title: Enable K3k
|
||||||
switchFailed: "{action} {name} failed!"
|
switchFailed: "{action} {name} failed!"
|
||||||
nvidiaDriverToolkit:
|
nvidiaDriverToolkit:
|
||||||
titles:
|
titles:
|
||||||
@@ -2417,7 +2429,15 @@ typeDescription:
|
|||||||
harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme."
|
harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme."
|
||||||
|
|
||||||
advancedSettings:
|
advancedSettings:
|
||||||
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
|
tags:
|
||||||
|
modified:
|
||||||
|
label: Modified
|
||||||
|
experimental:
|
||||||
|
label: Experimental
|
||||||
|
description: 'Experimental: The feature is incomplete but its essential functionality is operational. The feature is not recommended for use in production environments.'
|
||||||
|
preview:
|
||||||
|
label: Technical Preview
|
||||||
|
description: 'Technical Preview: The feature is almost complete and its functionality is not expected to change significantly. Explore the feature extensively before using in production environments.'
|
||||||
descriptions:
|
descriptions:
|
||||||
'harv-vlan': Default Network Interface name of the VLAN network.
|
'harv-vlan': Default Network Interface name of the VLAN network.
|
||||||
'harv-backup-target': Custom backup target to store virtual machine backups.
|
'harv-backup-target': Custom backup target to store virtual machine backups.
|
||||||
@@ -2427,6 +2447,7 @@ advancedSettings:
|
|||||||
'harv-containerd-registry': Containerd Registry Configuration to connect private registries.
|
'harv-containerd-registry': Containerd Registry Configuration to connect private registries.
|
||||||
'harv-log-level': Configure Harvester server log level. Defaults to Info.
|
'harv-log-level': Configure Harvester server log level. Defaults to Info.
|
||||||
'harv-server-version': Harvester server version.
|
'harv-server-version': Harvester server version.
|
||||||
|
'harv-server-flavor': "Indicates the Harvester edition: Prime or Community."
|
||||||
'harv-upgrade-checker-enabled': Specifies whether to enable Harvester upgrade check or not. Default is True.
|
'harv-upgrade-checker-enabled': Specifies whether to enable Harvester upgrade check or not. Default is True.
|
||||||
'harv-upgrade-checker-url': Default Harvester upgrade check url. Only used when the <code>upgrade-checker-enabled</code> is equal to True.
|
'harv-upgrade-checker-url': Default Harvester upgrade check url. Only used when the <code>upgrade-checker-enabled</code> is equal to True.
|
||||||
'harv-ui-source': Configure how to load the UI source. The `External` option is used to load the UI from a ui-index URL, while the `Bundled` option loads the UI from the local Harvester server.
|
'harv-ui-source': Configure how to load the UI source. The `External` option is used to load the UI from a ui-index URL, while the `Bundled` option loads the UI from the local Harvester server.
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
v-if="scope.row.metadata.name === 'rancher-vcluster' && scope.row.spec.enabled"
|
v-if="['rancher-vcluster', 'rancher-k3k'].includes(scope.row.metadata.name) && scope.row.spec.enabled"
|
||||||
v-tooltip="t('harvester.addons.rancherVcluster.accessRancher')"
|
v-tooltip="t('harvester.addons.rancherVcluster.accessRancher')"
|
||||||
class="ml-5"
|
class="ml-5"
|
||||||
rel="nofollow noopener noreferrer"
|
rel="nofollow noopener noreferrer"
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ import { HCI } from '../../types';
|
|||||||
import { parseVolumeClaimTemplates, EMPTY_IMAGE } from '../../utils/vm';
|
import { parseVolumeClaimTemplates, EMPTY_IMAGE } from '../../utils/vm';
|
||||||
import impl, { QGA_JSON, USB_TABLET } from './impl';
|
import impl, { QGA_JSON, USB_TABLET } from './impl';
|
||||||
import { GIBIBYTE } from '../../utils/unit';
|
import { GIBIBYTE } from '../../utils/unit';
|
||||||
import { VOLUME_MODE, FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE, ACCESS_MODE, FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
|
const { READ_WRITE_MANY } = ACCESS_MODE;
|
||||||
|
|
||||||
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
||||||
|
|
||||||
@@ -276,7 +278,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
customAccessMode() {
|
customAccessMode() {
|
||||||
return this.storageClassSetting.accessModes || 'ReadWriteMany';
|
return this.storageClassSetting.accessModes || READ_WRITE_MANY;
|
||||||
},
|
},
|
||||||
|
|
||||||
isWindows() {
|
isWindows() {
|
||||||
@@ -504,7 +506,7 @@ export default {
|
|||||||
id: randomStr(5),
|
id: randomStr(5),
|
||||||
source: SOURCE_TYPE.IMAGE,
|
source: SOURCE_TYPE.IMAGE,
|
||||||
name: 'disk-0',
|
name: 'disk-0',
|
||||||
accessMode: 'ReadWriteMany', // root disk only support LHv1 volume, should be RWX
|
accessMode: READ_WRITE_MANY, // root disk only support LHv1 volume, should be RWX
|
||||||
bus,
|
bus,
|
||||||
volumeName: '',
|
volumeName: '',
|
||||||
size,
|
size,
|
||||||
@@ -570,7 +572,7 @@ export default {
|
|||||||
const pvcResource = allPVCs.find( (O) => O.id === `${ namespace }/${ volume?.persistentVolumeClaim?.claimName }`);
|
const pvcResource = allPVCs.find( (O) => O.id === `${ namespace }/${ volume?.persistentVolumeClaim?.claimName }`);
|
||||||
|
|
||||||
source = SOURCE_TYPE.ATTACH_VOLUME;
|
source = SOURCE_TYPE.ATTACH_VOLUME;
|
||||||
accessMode = pvcResource?.spec?.accessModes?.[0] || 'ReadWriteMany';
|
accessMode = pvcResource?.spec?.accessModes?.[0] || READ_WRITE_MANY;
|
||||||
size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
|
size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
|
||||||
storageClassName = pvcResource?.spec?.storageClassName;
|
storageClassName = pvcResource?.spec?.storageClassName;
|
||||||
volumeMode = pvcResource?.spec?.volumeMode || VOLUME_MODE.BLOCK;
|
volumeMode = pvcResource?.spec?.volumeMode || VOLUME_MODE.BLOCK;
|
||||||
@@ -1315,8 +1317,9 @@ export default {
|
|||||||
case SOURCE_TYPE.IMAGE: {
|
case SOURCE_TYPE.IMAGE: {
|
||||||
const image = this.images.find( (I) => R.image === I.id);
|
const image = this.images.find( (I) => R.image === I.id);
|
||||||
|
|
||||||
|
out.spec.storageClassName = R.realName ? R.storageClassName : (R.storageClassName || image?.storageClassName);
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
out.spec.storageClassName = image.storageClassName;
|
|
||||||
out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: image.id };
|
out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: image.id };
|
||||||
} else {
|
} else {
|
||||||
out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: '' };
|
out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: '' };
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import HarvesterResource from './harvester';
|
|||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
|
|
||||||
const HARVESTER_NVIDIA_DRIVER_TOOLKIT = 'harvester-system/nvidia-driver-toolkit';
|
const HARVESTER_NVIDIA_DRIVER_TOOLKIT = 'harvester-system/nvidia-driver-toolkit';
|
||||||
|
const RANCHER_VCLUSTER = 'harvester-system/rancher-vcluster';
|
||||||
|
const RANCHER_K3K_ADDON = 'rancher-k3k/rancher-k3k';
|
||||||
|
|
||||||
export default class HciAddonConfig extends HarvesterResource {
|
export default class HciAddonConfig extends HarvesterResource {
|
||||||
get availableActions() {
|
get availableActions() {
|
||||||
const out = super._availableActions;
|
const out = super._availableActions;
|
||||||
|
|
||||||
if (this.id === 'harvester-system/rancher-vcluster') {
|
if (this.id === RANCHER_VCLUSTER || this.id === RANCHER_K3K_ADDON) {
|
||||||
const rancherDashboard = {
|
const rancherDashboard = {
|
||||||
action: 'goToRancher',
|
action: 'goToRancher',
|
||||||
enabled: this.spec.enabled,
|
enabled: this.spec.enabled,
|
||||||
@@ -57,6 +59,15 @@ export default class HciAddonConfig extends HarvesterResource {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.spec.enabled && this.id === RANCHER_K3K_ADDON) {
|
||||||
|
this.$dispatch('promptModal', {
|
||||||
|
resources: [this],
|
||||||
|
component: 'HarvesterEnableK3k',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.spec.enabled = !this.spec.enabled;
|
this.spec.enabled = !this.spec.enabled;
|
||||||
await this.save();
|
await this.save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -69,18 +80,19 @@ export default class HciAddonConfig extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
goToRancher() {
|
goToRancher() {
|
||||||
const valuesContent = jsyaml.load(this.spec.valuesContent);
|
|
||||||
|
|
||||||
window.open(
|
window.open(
|
||||||
`https://${ valuesContent.hostname }`,
|
this.rancherHostname,
|
||||||
'_blank',
|
'_blank',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get rancherHostname() {
|
get rancherHostname() {
|
||||||
const valuesContent = jsyaml.load(this.spec.valuesContent);
|
const valuesContent = jsyaml.load(this.spec.valuesContent) || {};
|
||||||
|
|
||||||
return `https://${ valuesContent.hostname }`;
|
// rancher-k3k nests the hostname under `rancher`, rancher-vcluster keeps it at the top level
|
||||||
|
const hostname = this.id === RANCHER_K3K_ADDON ? valuesContent.rancher?.hostname : valuesContent.hostname;
|
||||||
|
|
||||||
|
return `https://${ hostname }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get stateColor() {
|
get stateColor() {
|
||||||
|
|||||||
@@ -1315,6 +1315,18 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get provisionedHostDevices() {
|
||||||
|
// Rancher-provisioned VMs store hostDevices.name as a placeholder ('provisioned'); the
|
||||||
|
// real allocated PCI device names live in this annotation, keyed by resourceName.
|
||||||
|
try {
|
||||||
|
const deviceAllocationDetails = JSON.parse(this.metadata?.annotations[HCI_ANNOTATIONS.VM_DEVICE_ALLOCATION_DETAILS] || '{}');
|
||||||
|
|
||||||
|
return deviceAllocationDetails?.hostdevices || {};
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get schedulingVMBackupFeatureEnabled() {
|
get schedulingVMBackupFeatureEnabled() {
|
||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('schedulingVMBackup');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('schedulingVMBackup');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import ManagementSetting from '@shell/models/management.cattle.io.setting';
|
||||||
|
import { HCI_SETTING } from '../../config/settings';
|
||||||
|
|
||||||
|
export default class HarvesterManagementSetting extends ManagementSetting {
|
||||||
|
get _availableActions() {
|
||||||
|
const actions = super._availableActions;
|
||||||
|
|
||||||
|
if (this.$rootGetters['isStandaloneHarvester'] && this.id === HCI_SETTING.UI_PL) {
|
||||||
|
return actions.filter((action) => !['goToClone', 'promptRemove'].includes(action.action));
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester",
|
"name": "harvester",
|
||||||
"description": "Rancher UI Extension for Harvester",
|
"description": "Rancher UI Extension for Harvester",
|
||||||
"version": "1.9.0-rc6",
|
"version": "1.10.0-dev",
|
||||||
"private": false,
|
"private": false,
|
||||||
"rancher": {
|
"rancher": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
vendor: getVendor(),
|
vendor: getVendor(),
|
||||||
uiPLSetting: {},
|
uiPLSetting: {},
|
||||||
|
privateLabelDraft: null,
|
||||||
uiLogoDarkSetting: {},
|
uiLogoDarkSetting: {},
|
||||||
uiLogoDark: '',
|
uiLogoDark: '',
|
||||||
uiLogoLightSetting: {},
|
uiLogoLightSetting: {},
|
||||||
@@ -81,6 +82,17 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isHarvesterPrime() {
|
||||||
|
return !!this.$store.getters['harvester-common/isHarvesterPrime'];
|
||||||
|
},
|
||||||
|
privateLabel: {
|
||||||
|
get() {
|
||||||
|
return this.privateLabelDraft ?? (this.isHarvesterPrime ? this.$store.getters['harvester-common/privateLabel'] : this.uiPLSetting.value);
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.privateLabelDraft = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
mode() {
|
mode() {
|
||||||
const schema = this.$store.getters[`management/schemaFor`](MANAGEMENT.SETTING);
|
const schema = this.$store.getters[`management/schemaFor`](MANAGEMENT.SETTING);
|
||||||
|
|
||||||
@@ -112,7 +124,7 @@ export default {
|
|||||||
this.errors.push(e);
|
this.errors.push(e);
|
||||||
},
|
},
|
||||||
async save(btnCB) {
|
async save(btnCB) {
|
||||||
this.uiPLSetting.value = this.uiPLSetting.value.replaceAll(/[\<>&=#()"]/gm, '');
|
this.uiPLSetting.value = (this.privateLabel || '').replaceAll(/[\<>&=#()"]/gm, '');
|
||||||
if (this.customizeLogo) {
|
if (this.customizeLogo) {
|
||||||
this.uiLogoLightSetting.value = this.uiLogoLight;
|
this.uiLogoLightSetting.value = this.uiLogoLight;
|
||||||
this.uiLogoDarkSetting.value = this.uiLogoDark;
|
this.uiLogoDarkSetting.value = this.uiLogoDark;
|
||||||
@@ -169,7 +181,7 @@ export default {
|
|||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model:value="uiPLSetting.value"
|
v-model:value="privateLabel"
|
||||||
:label="t('branding.uiPL.label')"
|
:label="t('branding.uiPL.label')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
:maxlength="100"
|
:maxlength="100"
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import Parse from 'url-parse';
|
import Parse from 'url-parse';
|
||||||
|
import { MANAGEMENT } from '@shell/config/types';
|
||||||
|
import { SETTING } from '@shell/config/settings';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
import { PRODUCT_NAME } from '../config/harvester';
|
import { PRODUCT_NAME } from '../config/harvester';
|
||||||
import { featureEnabled, getVersion } from '../utils/feature-flags';
|
import { featureEnabled, getVersion } from '../utils/feature-flags';
|
||||||
@@ -64,6 +66,23 @@ const mutations = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
|
isHarvesterPrime: (_state, _getters, _rootState, rootGetters) => {
|
||||||
|
const setting = rootGetters['harvester/byId']?.(HCI.SETTING, 'server-flavor');
|
||||||
|
|
||||||
|
return (setting?.value || setting?.default) === 'prime';
|
||||||
|
},
|
||||||
|
|
||||||
|
privateLabel: (_state, getters, _rootState, rootGetters) => {
|
||||||
|
const setting = rootGetters['management/byId']?.(MANAGEMENT.SETTING, SETTING.PL);
|
||||||
|
const value = setting?.value || '';
|
||||||
|
|
||||||
|
if (getters.isHarvesterPrime && value === (setting?.default || 'Harvester')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
getBundleId(state) {
|
getBundleId(state) {
|
||||||
return state.latestBundleId;
|
return state.latestBundleId;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { FAVORITE_TYPES } from '@shell/store/prefs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically toggles SideNav entries based on the enabled status of a specific Addon.
|
* Dynamically toggles SideNav entries based on the enabled status of a specific Addon.
|
||||||
*
|
*
|
||||||
@@ -26,10 +28,13 @@ export function registerAddonSideNav(store, productName, {
|
|||||||
|
|
||||||
// Toggle the trigger a few times so an early kick (fired before the SideNav
|
// Toggle the trigger a few times so an early kick (fired before the SideNav
|
||||||
// has mounted on first login) is retried once the component is listening.
|
// has mounted on first login) is retried once the component is listening.
|
||||||
|
// Use the prefs/load mutation (in-memory only)
|
||||||
[0, 600, 1500].forEach((delay) => {
|
[0, 600, 1500].forEach((delay) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
store.dispatch('type-map/addFavorite', TRIGGER);
|
const base = (store.getters['prefs/get'](FAVORITE_TYPES) || []).filter((t) => t !== TRIGGER);
|
||||||
setTimeout(() => store.dispatch('type-map/removeFavorite', TRIGGER), 300);
|
|
||||||
|
store.commit('prefs/load', { key: FAVORITE_TYPES, value: [...base, TRIGGER] });
|
||||||
|
setTimeout(() => store.commit('prefs/load', { key: FAVORITE_TYPES, value: [...base] }), 300);
|
||||||
}, delay);
|
}, delay);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user