mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-04 21:26:08 +08:00
Compare commits
52
Commits
v1.9.0-rc2
...
v1.9.1-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
010dd710fc | ||
|
|
788c583210 | ||
|
|
ee8f214b7f | ||
|
|
27c1e64a05 | ||
|
|
68d67a7cbc | ||
|
|
6a7691456f | ||
|
|
66f6c36759 | ||
|
|
eb77d03b97 | ||
|
|
22deb0e1f6 | ||
|
|
f096147aa3 | ||
|
|
d73a9413be | ||
|
|
63ff8ce8fe | ||
|
|
b3b467f7b5 | ||
|
|
20557ee788 | ||
|
|
8ab432fb52 | ||
|
|
61624dc0ff | ||
|
|
d1367fc9a9 | ||
|
|
18c973c8c4 | ||
|
|
c9b26ab4bb | ||
|
|
2b9d093b38 | ||
|
|
1b3666e15c | ||
|
|
0bccb9a71f | ||
|
|
6d4cd09568 | ||
|
|
44078bf7cb | ||
|
|
1d55319aba | ||
|
|
3b7a0c30a7 | ||
|
|
d4562a91d1 | ||
|
|
080f9cdb29 | ||
|
|
b19da16b83 | ||
|
|
16a92c5e96 | ||
|
|
51a76eefe2 | ||
|
|
690652f204 | ||
|
|
a0f21d5902 | ||
|
|
f0929100f0 | ||
|
|
78ca4bb663 | ||
|
|
eb917c9d18 | ||
|
|
8815c70361 | ||
|
|
eb4c0504c8 | ||
|
|
82e7b0da19 | ||
|
|
92f34fa64f | ||
|
|
6039f21320 | ||
|
|
3cbaf97399 | ||
|
|
bcde1744e1 | ||
|
|
e991c9bcc8 | ||
|
|
6e709bc00f | ||
|
|
20d40860ce | ||
|
|
1496c17f3d | ||
|
|
e99e4ef34f | ||
|
|
73e022172b | ||
|
|
7e86c9fae0 | ||
|
|
b4b49222c3 | ||
|
|
b937a2147b |
@@ -1,14 +1,19 @@
|
|||||||
<!-- This template is for Devs to give QA details before moving the issue To-Test -->
|
|
||||||
### Summary
|
### Summary
|
||||||
|
<!-- Give the fix root cause or feature requirement, at least list what this PR changes -->
|
||||||
|
|
||||||
### PR Checklists
|
|
||||||
|
### PR Checklist
|
||||||
|
<!-- Check Yes if there is backend PR related to this UI PR, tag the backend owner's Github account -->
|
||||||
- Are backend engineers aware of UI changes ?
|
- Are backend engineers aware of UI changes ?
|
||||||
- [ ] Yes, the backend owner is:
|
- [ ] Yes, the backend owner is @
|
||||||
|
- [ ] No, frontend-only.
|
||||||
|
|
||||||
### Related Issue #
|
### Related Issue #
|
||||||
<!-- Define findings related to the feature or bug issue. -->
|
<!-- Link the issue as harvester/harvester#<issue_number> -->
|
||||||
|
harvester/harvester#<issue_number>
|
||||||
|
|
||||||
### Test screenshot or video
|
|
||||||
|
### Test screenshot or video (Required)
|
||||||
<!-- 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 -->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
> This `./AGENTS.md` file is generated by running `yarn agents:generate`.
|
> This `./AGENTS.md` file is generated by running `yarn agents:generate`. `CLAUDE.md` and `GEMINI.md` are symlinks to this file so Claude Code, GitHub Copilot and Gemini all read the same content.
|
||||||
|
|
||||||
|
|
||||||
# Project Overview
|
# Project Overview
|
||||||
@@ -28,9 +28,7 @@ You are an expert Senior Software Engineer specializing in Vue.js and TypeScript
|
|||||||
- Run `yarn lint:fix` before commits.
|
- Run `yarn lint:fix` before commits.
|
||||||
- Use conventional commit format:
|
- Use conventional commit format:
|
||||||
```
|
```
|
||||||
<type>:
|
<type>: <description>
|
||||||
|
|
||||||
<description>
|
|
||||||
```
|
```
|
||||||
- Follow existing naming conventions (PascalCase for components, camelCase for functions).
|
- Follow existing naming conventions (PascalCase for components, camelCase for functions).
|
||||||
- After changing a Vue, JS, or TS file, make sure it's automatically formatted with ESLint.
|
- After changing a Vue, JS, or TS file, make sure it's automatically formatted with ESLint.
|
||||||
@@ -130,8 +128,8 @@ To get started, follow the `Development Setup` section.
|
|||||||
- `Vue.js`:
|
- `Vue.js`:
|
||||||
- Composition API components are preferred over Options API.
|
- Composition API components are preferred over Options API.
|
||||||
- Large pages with lots of code and styles should be avoided by breaking the page up into smaller Vue components.
|
- Large pages with lots of code and styles should be avoided by breaking the page up into smaller Vue components.
|
||||||
- Place source tag above template above style.
|
- Order the Single File Component blocks as `<script>`, then `<template>`, then `<style>`.
|
||||||
- style tag should contain `lang='scss' scoped`.
|
- The `<style>` tag should include `lang="scss" scoped`.
|
||||||
- `Linting`: Follow the ESLint configuration in the root.
|
- `Linting`: Follow the ESLint configuration in the root.
|
||||||
|
|
||||||
- **File Structure:**
|
- **File Structure:**
|
||||||
@@ -163,24 +161,26 @@ To get started, follow the `Development Setup` section.
|
|||||||
|
|
||||||
## Harvester UI Extension Development Guide
|
## Harvester UI Extension Development Guide
|
||||||
|
|
||||||
1. Backward Compatibility
|
### Backward Compatibility
|
||||||
The Harvester UI Extension supports earlier cluster versions (e.g., UI Ext v1.8.0 works with clusters v1.7.0 and v1.6.0). It uses Feature Flags defined in pkg/config/feature-flags to ensure the UI matches the cluster's specific version.
|
|
||||||
|
The Harvester UI Extension supports earlier cluster versions (e.g., UI Ext v1.8.0 works with clusters v1.7.0 and v1.6.0). It uses Feature Flags defined in `pkg/harvester/config/` to ensure the UI matches the cluster's specific version.
|
||||||
|
|
||||||
|
### Implementation Steps for New Features
|
||||||
|
|
||||||
2. Implementation Steps for New Features
|
|
||||||
To add a feature in a new release, follow these steps:
|
To add a feature in a new release, follow these steps:
|
||||||
|
|
||||||
Register: Add a unique [Feature Name] to the corresponding release array in the configuration.
|
1. **Register**: Add a unique `[Feature Name]` to the corresponding release array in the configuration.
|
||||||
|
2. **Check**: Use the following getter to verify if the feature is enabled for the current version:
|
||||||
|
|
||||||
Check: Use the following getter to verify if the feature is enabled for the current version:
|
```js
|
||||||
|
computed: {
|
||||||
|
newFeatureEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('[Feature Name]');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
```
|
3. **Render**: Use the result of the check to conditionally render the UI components.
|
||||||
computed: {
|
|
||||||
newFeatureEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('[Feature Name]');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
|
||||||
Render: Use the result of the check to conditionally render the UI components.
|
|
||||||
|
|
||||||
|
|
||||||
## E2E Tests (Cypress)
|
## E2E Tests (Cypress)
|
||||||
@@ -228,7 +228,7 @@ For the Renovate config, see `.github/renovate.json`.
|
|||||||
### Commit Message Format
|
### Commit Message Format
|
||||||
All commit messages must follow the conventional commit format:
|
All commit messages must follow the conventional commit format:
|
||||||
```
|
```
|
||||||
<type>[optional scope]: <description>
|
<type>: <description>
|
||||||
|
|
||||||
[optional body]
|
[optional body]
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ For Harvester UI extension installation instructions, please refer to the page *
|
|||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
Ensure **Node.js v20 or later** is installed for development and debugging.
|
Ensure **Node.js v24 or later** is installed for development and debugging.
|
||||||
|
|
||||||
### Standalone Mode
|
### Standalone Mode
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,11 @@ module.exports = {
|
|||||||
'subject-empty': [2, 'never'],
|
'subject-empty': [2, 'never'],
|
||||||
'subject-full-stop': [2, 'never', '.'],
|
'subject-full-stop': [2, 'never', '.'],
|
||||||
'subject-max-length': [0, 'never'],
|
'subject-max-length': [0, 'never'],
|
||||||
|
'header-max-length': [0, 'always', 100],
|
||||||
'body-leading-blank': [2, 'always'],
|
'body-leading-blank': [2, 'always'],
|
||||||
'body-max-line-length': [0, 'always', 100],
|
'body-max-line-length': [0, 'always', 100],
|
||||||
'footer-leading-blank': [2, 'always'],
|
'footer-leading-blank': [2, 'always'],
|
||||||
'footer-max-line-length': [2, 'always', 100],
|
'footer-max-line-length': [0, 'always', 100],
|
||||||
},
|
},
|
||||||
// Ignore merge commits and revert commits
|
// Ignore merge commits and revert commits
|
||||||
ignores: [
|
ignores: [
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
- Run `yarn lint:fix` before commits.
|
- Run `yarn lint:fix` before commits.
|
||||||
- Use conventional commit format:
|
- Use conventional commit format:
|
||||||
```
|
```
|
||||||
<type>:
|
<type>: <description>
|
||||||
|
|
||||||
<description>
|
|
||||||
```
|
```
|
||||||
- Follow existing naming conventions (PascalCase for components, camelCase for functions).
|
- Follow existing naming conventions (PascalCase for components, camelCase for functions).
|
||||||
- After changing a Vue, JS, or TS file, make sure it's automatically formatted with ESLint.
|
- After changing a Vue, JS, or TS file, make sure it's automatically formatted with ESLint.
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ To get started, follow the `Development Setup` section.
|
|||||||
- `Vue.js`:
|
- `Vue.js`:
|
||||||
- Composition API components are preferred over Options API.
|
- Composition API components are preferred over Options API.
|
||||||
- Large pages with lots of code and styles should be avoided by breaking the page up into smaller Vue components.
|
- Large pages with lots of code and styles should be avoided by breaking the page up into smaller Vue components.
|
||||||
- Place source tag above template above style.
|
- Order the Single File Component blocks as `<script>`, then `<template>`, then `<style>`.
|
||||||
- style tag should contain `lang='scss' scoped`.
|
- The `<style>` tag should include `lang="scss" scoped`.
|
||||||
- `Linting`: Follow the ESLint configuration in the root.
|
- `Linting`: Follow the ESLint configuration in the root.
|
||||||
|
|
||||||
- **File Structure:**
|
- **File Structure:**
|
||||||
@@ -49,22 +49,24 @@ To get started, follow the `Development Setup` section.
|
|||||||
|
|
||||||
## Harvester UI Extension Development Guide
|
## Harvester UI Extension Development Guide
|
||||||
|
|
||||||
1. Backward Compatibility
|
### Backward Compatibility
|
||||||
The Harvester UI Extension supports earlier cluster versions (e.g., UI Ext v1.8.0 works with clusters v1.7.0 and v1.6.0). It uses Feature Flags defined in pkg/config/feature-flags to ensure the UI matches the cluster's specific version.
|
|
||||||
|
The Harvester UI Extension supports earlier cluster versions (e.g., UI Ext v1.8.0 works with clusters v1.7.0 and v1.6.0). It uses Feature Flags defined in `pkg/harvester/config/` to ensure the UI matches the cluster's specific version.
|
||||||
|
|
||||||
|
### Implementation Steps for New Features
|
||||||
|
|
||||||
2. Implementation Steps for New Features
|
|
||||||
To add a feature in a new release, follow these steps:
|
To add a feature in a new release, follow these steps:
|
||||||
|
|
||||||
Register: Add a unique [Feature Name] to the corresponding release array in the configuration.
|
1. **Register**: Add a unique `[Feature Name]` to the corresponding release array in the configuration.
|
||||||
|
2. **Check**: Use the following getter to verify if the feature is enabled for the current version:
|
||||||
|
|
||||||
Check: Use the following getter to verify if the feature is enabled for the current version:
|
```js
|
||||||
|
computed: {
|
||||||
|
newFeatureEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('[Feature Name]');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
```
|
3. **Render**: Use the result of the check to conditionally render the UI components.
|
||||||
computed: {
|
|
||||||
newFeatureEnabled() {
|
|
||||||
return this.$store.getters['harvester-common/getFeatureEnabled']('[Feature Name]');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
```
|
|
||||||
Render: Use the result of the check to conditionally render the UI components.
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
### Commit Message Format
|
### Commit Message Format
|
||||||
All commit messages must follow the conventional commit format:
|
All commit messages must follow the conventional commit format:
|
||||||
```
|
```
|
||||||
<type>[optional scope]: <description>
|
<type>: <description>
|
||||||
|
|
||||||
[optional body]
|
[optional body]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
> This `./AGENTS.md` file is generated by running `yarn agents:generate`.
|
> This `./AGENTS.md` file is generated by running `yarn agents:generate`. `CLAUDE.md` and `GEMINI.md` are symlinks to this file so Claude Code, GitHub Copilot and Gemini all read the same content.
|
||||||
|
|
||||||
|
|
||||||
# Project Overview
|
# Project Overview
|
||||||
|
|||||||
+12
-6
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester-ui-extension",
|
"name": "harvester-ui-extension",
|
||||||
"version": "1.9.0-rc2",
|
"version": "1.9.1-rc1",
|
||||||
"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.3",
|
"@rancher/shell": "3.0.12-rc.7",
|
||||||
"@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",
|
||||||
@@ -15,13 +15,19 @@
|
|||||||
"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",
|
||||||
|
"@xterm/xterm": "6.0.0",
|
||||||
|
"@xterm/addon-fit": "0.11.0",
|
||||||
|
"@xterm/addon-search": "0.16.0",
|
||||||
|
"@xterm/addon-web-links": "0.12.0",
|
||||||
|
"@xterm/addon-webgl": "0.19.0",
|
||||||
|
"@xterm/addon-canvas": "0.7.0",
|
||||||
"node-polyfill-webpack-plugin": "^3.0.0",
|
"node-polyfill-webpack-plugin": "^3.0.0",
|
||||||
"elkjs": "^0.11.0",
|
"elkjs": "^0.11.0",
|
||||||
"vue-draggable-next": "^2.2.1",
|
"vue-draggable-next": "^2.2.1",
|
||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "25.9.4",
|
"@types/node": "25.9.5",
|
||||||
"cronstrue": "2.59.0",
|
"cronstrue": "2.59.0",
|
||||||
"d3-color": "3.1.0",
|
"d3-color": "3.1.0",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
@@ -29,12 +35,12 @@
|
|||||||
"glob": "7.2.3",
|
"glob": "7.2.3",
|
||||||
"glob-parent": "6.0.2",
|
"glob-parent": "6.0.2",
|
||||||
"json5": "2.2.3",
|
"json5": "2.2.3",
|
||||||
"@types/lodash": "4.17.24",
|
"@types/lodash": "4.17.25",
|
||||||
"merge": "2.1.1",
|
"merge": "2.1.1",
|
||||||
"node-forge": "1.4.0",
|
"node-forge": "1.4.0",
|
||||||
"nth-check": "2.1.1",
|
"nth-check": "2.1.1",
|
||||||
"qs": "6.15.3",
|
"qs": "6.16.0",
|
||||||
"roarr": "7.21.6",
|
"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"
|
||||||
},
|
},
|
||||||
|
|||||||
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 |
@@ -271,4 +271,9 @@ export default {
|
|||||||
.filter-label .v-popper__arrow-container {
|
.filter-label .v-popper__arrow-container {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fixed-header-actions .middle .filter + .btn-group {
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -116,6 +116,11 @@ export default {
|
|||||||
.vm-schedule-dropdown .v-popper__arrow-container {
|
.vm-schedule-dropdown .v-popper__arrow-container {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fixed-header-actions .middle .vm-schedule-filter + .btn-group {
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -55,8 +55,12 @@ export default {
|
|||||||
!this.latestUpgrade.isUpgradeFailed;
|
!this.latestUpgrade.isUpgradeFailed;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canUpgrade() {
|
||||||
|
return this.versionOptions.length > 0 && !this.isUpgradeInProgress;
|
||||||
|
},
|
||||||
|
|
||||||
versionOptions() {
|
versionOptions() {
|
||||||
const versions = this.$store.getters['harvester/all'](HCI.VERSION);
|
const versions = this.$store.getters['harvester/all'](HCI.VERSION) || [];
|
||||||
|
|
||||||
return versions.map((V) => V.metadata.name);
|
return versions.map((V) => V.metadata.name);
|
||||||
},
|
},
|
||||||
@@ -154,14 +158,23 @@ export default {
|
|||||||
:cluster="currentCluster.nameDisplay"
|
:cluster="currentCluster.nameDisplay"
|
||||||
/>
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
<button
|
<span
|
||||||
v-if="versionOptions.length && !isUpgradeInProgress"
|
v-clean-tooltip="{
|
||||||
type="button"
|
content: canUpgrade ? t('harvester.upgradePage.upgradeAvailable') : t('harvester.upgradePage.upgradeUnavailable'),
|
||||||
class="btn bg-warning btn-sm"
|
triggers: ['hover', 'focus', 'touch'],
|
||||||
@click="open"
|
}"
|
||||||
|
class="upgrade-btn-wrap"
|
||||||
|
:tabindex="canUpgrade ? -1 : 0"
|
||||||
>
|
>
|
||||||
<t k="harvester.upgradePage.upgrade" />
|
<button
|
||||||
</button>
|
:disabled="!canUpgrade"
|
||||||
|
type="button"
|
||||||
|
class="btn bg-warning btn-sm"
|
||||||
|
@click="open"
|
||||||
|
>
|
||||||
|
<t k="harvester.upgradePage.upgrade" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<ModalWithCard
|
<ModalWithCard
|
||||||
@@ -296,6 +309,18 @@ export default {
|
|||||||
line-height: 23px;
|
line-height: 23px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upgrade-btn-wrap {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.currentVersion {
|
.currentVersion {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
import Socket, {
|
import Socket, {
|
||||||
EVENT_CONNECTED,
|
EVENT_CONNECTED,
|
||||||
EVENT_CONNECTING,
|
EVENT_CONNECTING,
|
||||||
@@ -22,74 +21,98 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
socket: null,
|
socket: null,
|
||||||
terminal: null,
|
terminal: null,
|
||||||
fitAddon: null,
|
textDecoder: null,
|
||||||
searchAddon: null,
|
fitAddon: null,
|
||||||
webglAddon: null,
|
searchAddon: null,
|
||||||
isOpen: false,
|
webglAddon: null,
|
||||||
isOpening: false,
|
onResize: null,
|
||||||
backlog: [],
|
isOpen: false,
|
||||||
firstTime: true,
|
isOpening: false,
|
||||||
queue: []
|
backlog: [],
|
||||||
|
firstTime: true,
|
||||||
|
queue: [],
|
||||||
|
isDraining: false,
|
||||||
|
drainScheduled: false,
|
||||||
|
drainTimer: null,
|
||||||
|
showSearch: false,
|
||||||
|
searchQuery: '',
|
||||||
|
searchOptions: {
|
||||||
|
caseSensitive: false,
|
||||||
|
regex: false,
|
||||||
|
wholeWord: false,
|
||||||
|
incremental: true,
|
||||||
|
decorations: {
|
||||||
|
matchBackground: '#B45309',
|
||||||
|
matchBorder: '#FDBA74',
|
||||||
|
matchOverviewRuler: '#FDBA74',
|
||||||
|
activeMatchBackground: '#EA580C',
|
||||||
|
activeMatchBorder: '#FED7AA',
|
||||||
|
activeMatchColorOverviewRuler: '#FED7AA',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
serialConsoleUrl() {
|
||||||
|
return this.value?.getSerialConsolePath || '';
|
||||||
|
},
|
||||||
|
|
||||||
xtermConfig() {
|
xtermConfig() {
|
||||||
return {
|
return {
|
||||||
allowProposedApi: true,
|
allowProposedApi: true,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
|
cursorStyle: 'bar',
|
||||||
|
cursorWidth: 2,
|
||||||
|
scrollback: 5000,
|
||||||
useStyle: true,
|
useStyle: true,
|
||||||
fontSize: 12,
|
fontFamily: 'JetBrains Mono, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: 1.3,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
|
||||||
queue: {
|
|
||||||
handler: debounce(async function(neu) {
|
|
||||||
if (neu.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const msg = await Promise.all(neu);
|
|
||||||
|
|
||||||
(msg || []).forEach((m) => {
|
|
||||||
this.terminal.write(m);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue = [];
|
|
||||||
}, 10),
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.close();
|
this.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
serialConsoleUrl() {
|
||||||
|
if (this.terminal) {
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
this.textDecoder = new TextDecoder('utf-8');
|
||||||
await this.setupTerminal();
|
await this.setupTerminal();
|
||||||
await this.connect();
|
await this.connect();
|
||||||
|
this.onResize = debounce(() => this.fit(), 100);
|
||||||
|
window.addEventListener('resize', this.onResize);
|
||||||
|
this.scheduleFit();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async setupTerminal() {
|
async setupTerminal() {
|
||||||
const docStyle = getComputedStyle(document.querySelector('body'));
|
const docStyle = getComputedStyle(document.querySelector('body'));
|
||||||
const xterm = await import(/* webpackChunkName: "xterm" */ 'xterm');
|
const xterm = await import(/* webpackChunkName: "xterm" */ '@xterm/xterm');
|
||||||
|
|
||||||
const addons = await allHash({
|
const addons = await allHash({
|
||||||
fit: import(/* webpackChunkName: "xterm" */ 'xterm-addon-fit'),
|
fit: import(/* webpackChunkName: "xterm" */ '@xterm/addon-fit'),
|
||||||
webgl: import(/* webpackChunkName: "xterm" */ 'xterm-addon-webgl'),
|
webgl: import(/* webpackChunkName: "xterm" */ '@xterm/addon-webgl'),
|
||||||
weblinks: import(/* webpackChunkName: "xterm" */ 'xterm-addon-web-links'),
|
weblinks: import(/* webpackChunkName: "xterm" */ '@xterm/addon-web-links'),
|
||||||
search: import(/* webpackChunkName: "xterm" */ 'xterm-addon-search'),
|
search: import(/* webpackChunkName: "xterm" */ '@xterm/addon-search'),
|
||||||
|
canvas: import(/* webpackChunkName: "xterm" */ '@xterm/addon-canvas'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const terminal = new xterm.Terminal({
|
const terminal = new xterm.Terminal({
|
||||||
theme: {
|
theme: {
|
||||||
background: docStyle.getPropertyValue('--terminal-bg').trim(),
|
background: docStyle.getPropertyValue('--terminal-bg').trim(),
|
||||||
cursor: docStyle.getPropertyValue('--terminal-cursor').trim(),
|
cursor: docStyle.getPropertyValue('--terminal-text').trim(),
|
||||||
foreground: docStyle.getPropertyValue('--terminal-text').trim()
|
foreground: docStyle.getPropertyValue('--terminal-text').trim()
|
||||||
},
|
},
|
||||||
...this.xtermConfig,
|
...this.xtermConfig,
|
||||||
@@ -97,25 +120,48 @@ export default {
|
|||||||
|
|
||||||
this.fitAddon = new addons.fit.FitAddon();
|
this.fitAddon = new addons.fit.FitAddon();
|
||||||
this.searchAddon = new addons.search.SearchAddon();
|
this.searchAddon = new addons.search.SearchAddon();
|
||||||
|
|
||||||
try {
|
|
||||||
this.webglAddon = new addons.webgl.WebGlAddon();
|
|
||||||
} catch (e) {
|
|
||||||
// Some browsers (Safari) don't support the webgl renderer, so don't use it.
|
|
||||||
this.webglAddon = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.loadAddon(this.fitAddon);
|
terminal.loadAddon(this.fitAddon);
|
||||||
terminal.loadAddon(this.searchAddon);
|
terminal.loadAddon(this.searchAddon);
|
||||||
terminal.loadAddon(new addons.weblinks.WebLinksAddon());
|
terminal.loadAddon(new addons.weblinks.WebLinksAddon());
|
||||||
terminal.open(this.$refs.xterm);
|
terminal.open(this.$refs.xterm);
|
||||||
|
|
||||||
if ( this.webglAddon ) {
|
// The webgl renderer can fail without throwing: the context may be lost
|
||||||
|
// mid-session, or it can attach but silently never paint, leaving the
|
||||||
|
// terminal's helper textarea at 0x0. Detect both cases and fall back to the
|
||||||
|
// canvas renderer so the terminal stays interactive.
|
||||||
|
try {
|
||||||
|
this.webglAddon = new addons.webgl.WebglAddon();
|
||||||
|
|
||||||
|
this.webglAddon.onContextLoss(() => {
|
||||||
|
if (!this.isUnmounted && this.terminal) {
|
||||||
|
this.fallbackToCanvas(terminal, addons);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
terminal.loadAddon(this.webglAddon);
|
terminal.loadAddon(this.webglAddon);
|
||||||
|
|
||||||
|
// Detect the silent "attaches but never paints" failure after a render frame.
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (this.isUnmounted || !this.terminal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textarea = this.$refs.xterm?.querySelector('.xterm-helper-textarea');
|
||||||
|
const hasPainted = textarea && textarea.getBoundingClientRect().height > 0;
|
||||||
|
|
||||||
|
if (this.webglAddon && !hasPainted) {
|
||||||
|
this.fallbackToCanvas(terminal, addons);
|
||||||
|
this.fit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Some browsers (e.g. Safari) don't support the webgl renderer, so don't use it.
|
||||||
|
this.fallbackToCanvas(terminal, addons);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fit();
|
this.fit();
|
||||||
this.flush();
|
this.flush();
|
||||||
|
this.scheduleDrainQueue();
|
||||||
|
|
||||||
terminal.onData((input) => {
|
terminal.onData((input) => {
|
||||||
const msg = this.str2ab(input);
|
const msg = this.str2ab(input);
|
||||||
@@ -123,9 +169,132 @@ export default {
|
|||||||
this.write(msg);
|
this.write(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
terminal.attachCustomKeyEventHandler((event) => {
|
||||||
|
const key = event.key?.toLowerCase();
|
||||||
|
|
||||||
|
if ((event.ctrlKey || event.metaKey) && key === 'f') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.openSearch();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'escape' && this.showSearch) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.closeSearch();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
this.terminal = terminal;
|
this.terminal = terminal;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openSearch() {
|
||||||
|
this.showSearch = true;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const input = this.$refs.searchInput;
|
||||||
|
|
||||||
|
if (input) {
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
closeSearch() {
|
||||||
|
this.showSearch = false;
|
||||||
|
this.searchQuery = '';
|
||||||
|
this.clearSearchHighlights();
|
||||||
|
this.terminal?.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSearchHighlights() {
|
||||||
|
this.searchAddon?.clearActiveDecoration();
|
||||||
|
this.searchAddon?.clearDecorations();
|
||||||
|
this.terminal?.clearSelection();
|
||||||
|
|
||||||
|
// Disposing the search decorations doesn't repaint the webgl/canvas
|
||||||
|
// layer, so force a refresh to remove the lingering highlight.
|
||||||
|
if (this.terminal) {
|
||||||
|
this.terminal.refresh(0, this.terminal.rows - 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
findNext() {
|
||||||
|
if (!this.searchAddon || !this.searchQuery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.searchAddon.findNext(this.searchQuery, this.searchOptions);
|
||||||
|
},
|
||||||
|
|
||||||
|
findPrevious() {
|
||||||
|
if (!this.searchAddon || !this.searchQuery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.searchAddon.findPrevious(this.searchQuery, this.searchOptions);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearchInput() {
|
||||||
|
if (!this.searchQuery) {
|
||||||
|
this.clearSearchHighlights();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.findNext();
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearchKeydown(event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (event.shiftKey) {
|
||||||
|
this.findPrevious();
|
||||||
|
} else {
|
||||||
|
this.findNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
this.closeSearch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduleFit() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.fit();
|
||||||
|
|
||||||
|
// Re-fit after layout settles to avoid undersized rows on first paint.
|
||||||
|
setTimeout(() => this.fit(), 120);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dispose the webgl renderer (if any) and switch to the canvas renderer.
|
||||||
|
// Used when webgl fails to construct, loses its context, or silently never paints.
|
||||||
|
fallbackToCanvas(terminal, addons) {
|
||||||
|
if (this.isUnmounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.webglAddon?.dispose();
|
||||||
|
this.webglAddon = null;
|
||||||
|
|
||||||
|
if (!this.canvasAddon) {
|
||||||
|
this.canvasAddon = new addons.canvas.CanvasAddon();
|
||||||
|
terminal.loadAddon(this.canvasAddon);
|
||||||
|
}
|
||||||
|
this.fit();
|
||||||
|
},
|
||||||
|
|
||||||
str2ab(str) {
|
str2ab(str) {
|
||||||
const enc = new TextEncoder();
|
const enc = new TextEncoder();
|
||||||
|
|
||||||
@@ -140,12 +309,131 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
enqueueMessage(messagePromise) {
|
||||||
|
this.queue.push(messagePromise);
|
||||||
|
this.scheduleDrainQueue();
|
||||||
|
},
|
||||||
|
|
||||||
|
scheduleDrainQueue() {
|
||||||
|
if (this.drainScheduled || this.isDraining || !this.terminal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drainScheduled = true;
|
||||||
|
|
||||||
|
const shouldDrainImmediately = this.queue.length <= 3;
|
||||||
|
|
||||||
|
const runDrain = () => {
|
||||||
|
this.drainScheduled = false;
|
||||||
|
this.drainTimer = null;
|
||||||
|
this.drainQueue();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shouldDrainImmediately) {
|
||||||
|
runDrain();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drainTimer = requestAnimationFrame(runDrain);
|
||||||
|
},
|
||||||
|
|
||||||
|
async decodeMessageData(data) {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.textDecoder) {
|
||||||
|
this.textDecoder = new TextDecoder('utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data instanceof Blob) {
|
||||||
|
const buffer = await data.arrayBuffer();
|
||||||
|
|
||||||
|
return this.textDecoder.decode(new Uint8Array(buffer), { stream: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data instanceof ArrayBuffer) {
|
||||||
|
return this.textDecoder.decode(new Uint8Array(data), { stream: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ArrayBuffer.isView(data)) {
|
||||||
|
const view = data;
|
||||||
|
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
||||||
|
|
||||||
|
return this.textDecoder.decode(bytes, { stream: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(data || '');
|
||||||
|
},
|
||||||
|
|
||||||
|
async drainQueue() {
|
||||||
|
if (this.isDraining || !this.terminal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDraining = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (this.queue.length > 0) {
|
||||||
|
const pendingMessages = this.queue.splice(0, this.queue.length);
|
||||||
|
const settledMessages = await Promise.allSettled(pendingMessages);
|
||||||
|
const messages = settledMessages
|
||||||
|
.filter((result) => result.status === 'fulfilled')
|
||||||
|
.map((result) => result.value);
|
||||||
|
const output = messages.filter(Boolean).join('');
|
||||||
|
|
||||||
|
if (output) {
|
||||||
|
await this.writeOutputInChunks(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.queue.length > 0) {
|
||||||
|
await this.nextFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.isDraining = false;
|
||||||
|
|
||||||
|
// If data arrived between the last while-check and releasing the lock,
|
||||||
|
// immediately schedule another drain so output never gets stuck.
|
||||||
|
if (this.queue.length > 0 && this.terminal) {
|
||||||
|
this.scheduleDrainQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async writeOutputInChunks(output) {
|
||||||
|
if (!output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkSize = output.length > 20000 ? 16000 : output.length > 8000 ? 8000 : output.length;
|
||||||
|
|
||||||
|
if (chunkSize === output.length) {
|
||||||
|
this.terminal.write(output);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < output.length; index += chunkSize) {
|
||||||
|
this.terminal.write(output.slice(index, index + chunkSize));
|
||||||
|
|
||||||
|
if (index + chunkSize < output.length) {
|
||||||
|
await this.nextFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
nextFrame() {
|
||||||
|
return new Promise((resolve) => requestAnimationFrame(resolve));
|
||||||
|
},
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.terminal.clear();
|
this.terminal.clear();
|
||||||
},
|
},
|
||||||
|
|
||||||
getSocketUrl() {
|
getSocketUrl() {
|
||||||
return `${ this.value?.getSerialConsolePath }`;
|
return this.serialConsoleUrl;
|
||||||
},
|
},
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
@@ -181,6 +469,7 @@ export default {
|
|||||||
this.fit();
|
this.fit();
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
|
this.scheduleFit();
|
||||||
|
|
||||||
if (this.firstTime) {
|
if (this.firstTime) {
|
||||||
this.socket.send(this.str2ab('\n'));
|
this.socket.send(this.str2ab('\n'));
|
||||||
@@ -195,7 +484,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.socket.addEventListener(EVENT_MESSAGE, (e) => {
|
this.socket.addEventListener(EVENT_MESSAGE, (e) => {
|
||||||
this.queue.push(e.detail.data.text());
|
this.enqueueMessage(this.decodeMessageData(e.detail.data));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.connect();
|
this.socket.connect();
|
||||||
@@ -218,22 +507,19 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fitAddon.fit();
|
this.fitAddon.fit();
|
||||||
|
|
||||||
const { rows, cols } = this.fitAddon.proposeDimensions();
|
|
||||||
|
|
||||||
if ( !this.isOpen ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = JSON.stringify({
|
|
||||||
Width: cols,
|
|
||||||
Height: rows
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.send(this.str2ab(message));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
if (this.onResize) {
|
||||||
|
window.removeEventListener('resize', this.onResize);
|
||||||
|
this.onResize = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.drainTimer) {
|
||||||
|
cancelAnimationFrame(this.drainTimer);
|
||||||
|
this.drainTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
if ( this.socket ) {
|
if ( this.socket ) {
|
||||||
this.socket.disconnect();
|
this.socket.disconnect();
|
||||||
}
|
}
|
||||||
@@ -248,6 +534,44 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="harvester-shell-container">
|
<div class="harvester-shell-container">
|
||||||
|
<div
|
||||||
|
v-if="showSearch"
|
||||||
|
class="shell-search"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref="searchInput"
|
||||||
|
v-model="searchQuery"
|
||||||
|
type="text"
|
||||||
|
class="shell-search-input"
|
||||||
|
placeholder="Search in terminal"
|
||||||
|
@input="onSearchInput"
|
||||||
|
@keydown="onSearchKeydown"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn role-secondary btn-sm shell-search-btn"
|
||||||
|
type="button"
|
||||||
|
title="Shift+Enter"
|
||||||
|
@click="findPrevious"
|
||||||
|
>
|
||||||
|
Prev
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn role-secondary btn-sm shell-search-btn"
|
||||||
|
type="button"
|
||||||
|
title="Enter"
|
||||||
|
@click="findNext"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn role-secondary btn-sm shell-search-btn"
|
||||||
|
type="button"
|
||||||
|
title="Esc"
|
||||||
|
@click="closeSearch"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="xterm"
|
ref="xterm"
|
||||||
class="shell-body"
|
class="shell-body"
|
||||||
@@ -257,18 +581,73 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../../../node_modules/xterm/css/xterm.css';
|
@import '../../../../node_modules/@xterm/xterm/css/xterm.css';
|
||||||
|
|
||||||
body, #__nuxt, #__layout, MAIN {
|
body, #__nuxt, #__layout, MAIN {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.harvester-shell-container {
|
.harvester-shell-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.shell-body, .terminal.xterm {
|
.resize-observer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 5;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: color-mix(in srgb, var(--body-bg) 88%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-search-input {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 420px;
|
||||||
|
flex: 0 1 320px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--body-bg);
|
||||||
|
color: var(--body-text);
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-search-btn {
|
||||||
|
min-width: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell-body {
|
||||||
|
flex: 1 1 auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal.xterm,
|
||||||
|
.terminal.xterm .xterm-viewport,
|
||||||
|
.terminal.xterm .xterm-screen,
|
||||||
|
.terminal.xterm .xterm-scrollable-element {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal.xterm .xterm-scrollable-element {
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ export default {
|
|||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
const prefix = window.location.pathname.replace(this.$route.path, '');
|
const prefix = window.location.pathname.replace(this.$route.path, '');
|
||||||
const params = this.$route?.params;
|
const params = this.$route?.params;
|
||||||
|
const popupWidth = Math.max(800, screen.width - 200);
|
||||||
|
const popupHeight = Math.max(600, screen.height - 200);
|
||||||
|
const popupLeft = Math.max(0, window.screenX + Math.floor((window.outerWidth - popupWidth) / 2));
|
||||||
|
const popupTop = Math.max(0, window.screenY + Math.floor((window.outerHeight - popupHeight) / 2));
|
||||||
|
|
||||||
const url = `https://${ host }${ prefix }/${ PRODUCT_NAME }/c/${ params.cluster }/console/${ uid }/${ type }`;
|
const url = `https://${ host }${ prefix }/${ PRODUCT_NAME }/c/${ params.cluster }/console/${ uid }/${ type }`;
|
||||||
|
|
||||||
@@ -63,7 +67,7 @@ export default {
|
|||||||
window.open(
|
window.open(
|
||||||
url,
|
url,
|
||||||
'_blank',
|
'_blank',
|
||||||
`toolbars=0,width=${ screen.width - 200 },height=${ screen.height - 200 },left=0,top=0,noreferrer`
|
`toolbars=0,width=${ popupWidth },height=${ popupHeight },left=${ popupLeft },top=${ popupTop },noreferrer`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,10 +7,15 @@ import { STORAGE_CLASS, NETWORK_ATTACHMENT } from '@shell/config/types';
|
|||||||
import { useI18n } from '@shell/composables/useI18n';
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
import { randomStr } from '@shell/utils/string';
|
import { randomStr } from '@shell/utils/string';
|
||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
|
import { VOLUME_MODE, ACCESS_MODE } from '../../config/types';
|
||||||
import { FORKLIFT_NAMESPACE } from '../../config/harvester-map';
|
import { FORKLIFT_NAMESPACE } from '../../config/harvester-map';
|
||||||
import { buildNetworkMapEntries, buildStorageMapEntries } from '../../utils/forklift';
|
import { buildNetworkMapEntries, buildStorageMapEntries } from '../../utils/forklift';
|
||||||
import { isInternalStorageClass } from '../../utils/storage-class';
|
import { isInternalStorageClass } from '../../utils/storage-class';
|
||||||
import MappingColumn from './MappingColumn.vue';
|
import MappingColumn from './MappingColumn.vue';
|
||||||
|
import StorageDefaultsModal from './StorageDefaultsModal.vue';
|
||||||
|
|
||||||
|
const DEFAULT_VOLUME_MODE = VOLUME_MODE.FILE_SYSTEM;
|
||||||
|
const DEFAULT_ACCESS_MODE = ACCESS_MODE?.READ_WRITE_MANY ?? 'ReadWriteMany';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
providerName: { type: String, default: '' },
|
providerName: { type: String, default: '' },
|
||||||
@@ -35,6 +40,10 @@ const allStorageMaps = ref([]);
|
|||||||
const errors = ref([]);
|
const errors = ref([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
|
// Storage defaults edit modal state.
|
||||||
|
const showStorageDefaultsModal = ref(false);
|
||||||
|
const editingStorageEntry = ref(null);
|
||||||
|
|
||||||
const { networkEntries, storageEntries } = toRefs(props.stepData);
|
const { networkEntries, storageEntries } = toRefs(props.stepData);
|
||||||
|
|
||||||
const NAMESPACE = FORKLIFT_NAMESPACE;
|
const NAMESPACE = FORKLIFT_NAMESPACE;
|
||||||
@@ -105,7 +114,7 @@ const applyNetworkMapTargets = (mapSpec) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyStorageMapTargets = (mapSpec) => {
|
const applyStorageMapTargets = (mapSpec, { markOverridden = false, captureInherited = false } = {}) => {
|
||||||
if (!mapSpec) {
|
if (!mapSpec) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -117,6 +126,30 @@ const applyStorageMapTargets = (mapSpec) => {
|
|||||||
|
|
||||||
if (match?.destination?.storageClass) {
|
if (match?.destination?.storageClass) {
|
||||||
entry.target = match.destination.storageClass;
|
entry.target = match.destination.storageClass;
|
||||||
|
|
||||||
|
if (captureInherited) {
|
||||||
|
entry.inheritedFromProvider = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.destination.volumeMode) {
|
||||||
|
entry.volumeMode = match.destination.volumeMode;
|
||||||
|
|
||||||
|
if (captureInherited) {
|
||||||
|
entry.inheritedVolumeMode = match.destination.volumeMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.destination.accessMode) {
|
||||||
|
entry.accessModes = [match.destination.accessMode];
|
||||||
|
|
||||||
|
if (captureInherited) {
|
||||||
|
entry.inheritedAccessModes = [match.destination.accessMode];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markOverridden) {
|
||||||
|
entry.overridden = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -134,12 +167,39 @@ const applyDefaultStorageMap = () => {
|
|||||||
(sm) => sm.metadata.name === `${ props.providerName }-storage-map-default`
|
(sm) => sm.metadata.name === `${ props.providerName }-storage-map-default`
|
||||||
);
|
);
|
||||||
|
|
||||||
applyStorageMapTargets(defaultMap?.spec?.map);
|
applyStorageMapTargets(defaultMap?.spec?.map, { captureInherited: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const allNetworksMapped = computed(() => networkEntries.value.length > 0 && networkEntries.value.every((e) => !!e.target));
|
const allNetworksMapped = computed(() => networkEntries.value.length > 0 && networkEntries.value.every((e) => !!e.target));
|
||||||
const allStorageMapped = computed(() => storageEntries.value.length > 0 && storageEntries.value.every((e) => !!e.target));
|
const allStorageMapped = computed(() => storageEntries.value.length > 0 && storageEntries.value.every((e) => !!e.target));
|
||||||
|
|
||||||
|
// The "Inherited from provider" hint only appears in the migration plan wizard
|
||||||
|
// (where mappings inherit from the provider default), not on the provider creation page.
|
||||||
|
const inheritedProviderName = computed(() => (props.useAllProviderData ? '' : props.providerName));
|
||||||
|
|
||||||
|
const openStorageDefaults = (entry) => {
|
||||||
|
editingStorageEntry.value = entry;
|
||||||
|
showStorageDefaultsModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeStorageDefaults = () => {
|
||||||
|
showStorageDefaultsModal.value = false;
|
||||||
|
editingStorageEntry.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyStorageDefaults = ({ volumeMode, accessModes }) => {
|
||||||
|
if (editingStorageEntry.value) {
|
||||||
|
editingStorageEntry.value.volumeMode = volumeMode;
|
||||||
|
editingStorageEntry.value.accessModes = accessModes;
|
||||||
|
|
||||||
|
const inheritedVolumeMode = editingStorageEntry.value.inheritedVolumeMode;
|
||||||
|
const inheritedAccessMode = editingStorageEntry.value.inheritedAccessModes?.[0];
|
||||||
|
const selectedAccessMode = accessModes?.[0];
|
||||||
|
|
||||||
|
editingStorageEntry.value.overridden = volumeMode !== inheritedVolumeMode || selectedAccessMode !== inheritedAccessMode;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const canSave = computed(() => {
|
const canSave = computed(() => {
|
||||||
if (props.useAllProviderData) {
|
if (props.useAllProviderData) {
|
||||||
return true;
|
return true;
|
||||||
@@ -202,13 +262,19 @@ const buildStorageEntries = () => {
|
|||||||
if (ds && ds.id) {
|
if (ds && ds.id) {
|
||||||
if (!datastoreMap[ds.id]) {
|
if (!datastoreMap[ds.id]) {
|
||||||
datastoreMap[ds.id] = {
|
datastoreMap[ds.id] = {
|
||||||
name: ds.name || t('harvester.addons.vmMigration.generic.unknown'),
|
name: ds.name || t('harvester.addons.vmMigration.generic.unknown'),
|
||||||
id: ds.id,
|
id: ds.id,
|
||||||
type: ds.type || '',
|
type: ds.type || '',
|
||||||
capacity: 0,
|
capacity: 0,
|
||||||
target: '',
|
target: '',
|
||||||
usedBy: [],
|
volumeMode: DEFAULT_VOLUME_MODE,
|
||||||
_key: `stor-${ ds.id }`,
|
accessModes: [DEFAULT_ACCESS_MODE],
|
||||||
|
inheritedVolumeMode: DEFAULT_VOLUME_MODE,
|
||||||
|
inheritedAccessModes: [DEFAULT_ACCESS_MODE],
|
||||||
|
inheritedFromProvider: false,
|
||||||
|
overridden: false,
|
||||||
|
usedBy: [],
|
||||||
|
_key: `stor-${ ds.id }`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,13 +304,19 @@ const buildNetworkEntriesFromProvider = (networksData) => {
|
|||||||
|
|
||||||
const buildStorageEntriesFromProvider = (datastoresData) => {
|
const buildStorageEntriesFromProvider = (datastoresData) => {
|
||||||
storageEntries.value = (Array.isArray(datastoresData) ? datastoresData : []).map((ds) => ({
|
storageEntries.value = (Array.isArray(datastoresData) ? datastoresData : []).map((ds) => ({
|
||||||
name: ds.name || ds.id,
|
name: ds.name || ds.id,
|
||||||
id: ds.id || '',
|
id: ds.id || '',
|
||||||
type: ds.type || '',
|
type: ds.type || '',
|
||||||
capacity: ds.capacity || 0,
|
capacity: ds.capacity || 0,
|
||||||
target: '',
|
target: '',
|
||||||
usedBy: [],
|
volumeMode: DEFAULT_VOLUME_MODE,
|
||||||
_key: `stor-${ ds.id || ds.name }`,
|
accessModes: [DEFAULT_ACCESS_MODE],
|
||||||
|
inheritedVolumeMode: DEFAULT_VOLUME_MODE,
|
||||||
|
inheritedAccessModes: [DEFAULT_ACCESS_MODE],
|
||||||
|
inheritedFromProvider: false,
|
||||||
|
overridden: false,
|
||||||
|
usedBy: [],
|
||||||
|
_key: `stor-${ ds.id || ds.name }`,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -461,7 +533,7 @@ const init = async() => {
|
|||||||
|
|
||||||
if (!hasExistingStorageTargets) {
|
if (!hasExistingStorageTargets) {
|
||||||
if (props.existingStorageMap?.spec?.map) {
|
if (props.existingStorageMap?.spec?.map) {
|
||||||
applyStorageMapTargets(props.existingStorageMap.spec.map);
|
applyStorageMapTargets(props.existingStorageMap.spec.map, { markOverridden: true });
|
||||||
} else {
|
} else {
|
||||||
applyDefaultStorageMap();
|
applyDefaultStorageMap();
|
||||||
}
|
}
|
||||||
@@ -511,6 +583,9 @@ init();
|
|||||||
:placeholder="t('harvester.addons.vmMigration.configureMappings.storageMapping.placeholder')"
|
:placeholder="t('harvester.addons.vmMigration.configureMappings.storageMapping.placeholder')"
|
||||||
:show-used-by="!useAllProviderData"
|
:show-used-by="!useAllProviderData"
|
||||||
:clearable="useAllProviderData"
|
:clearable="useAllProviderData"
|
||||||
|
:show-volume-settings="true"
|
||||||
|
:inherited-provider-name="inheritedProviderName"
|
||||||
|
@edit-defaults="openStorageDefaults"
|
||||||
>
|
>
|
||||||
<template #source-detail="{ entry }">
|
<template #source-detail="{ entry }">
|
||||||
<span
|
<span
|
||||||
@@ -520,6 +595,19 @@ init();
|
|||||||
</template>
|
</template>
|
||||||
</MappingColumn>
|
</MappingColumn>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<StorageDefaultsModal
|
||||||
|
v-if="showStorageDefaultsModal && editingStorageEntry"
|
||||||
|
:storage-class-name="editingStorageEntry.target"
|
||||||
|
:provider-name="providerName"
|
||||||
|
:show-inherited="!!inheritedProviderName"
|
||||||
|
:volume-mode="editingStorageEntry.volumeMode"
|
||||||
|
:access-modes="editingStorageEntry.accessModes"
|
||||||
|
:inherited-volume-mode="editingStorageEntry.inheritedVolumeMode"
|
||||||
|
:inherited-access-modes="editingStorageEntry.inheritedAccessModes"
|
||||||
|
@apply="applyStorageDefaults"
|
||||||
|
@close="closeStorageDefaults"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const {
|
|||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
skipTlsVerify,
|
skipTlsVerify,
|
||||||
|
vddkInitImage,
|
||||||
testPassed,
|
testPassed,
|
||||||
testResult,
|
testResult,
|
||||||
testError,
|
testError,
|
||||||
@@ -97,6 +98,7 @@ watch(selectedProvider, (val) => {
|
|||||||
username.value = '';
|
username.value = '';
|
||||||
password.value = '';
|
password.value = '';
|
||||||
skipTlsVerify.value = false;
|
skipTlsVerify.value = false;
|
||||||
|
vddkInitImage.value = '';
|
||||||
createdProvider.value = null;
|
createdProvider.value = null;
|
||||||
createdSecret.value = null;
|
createdSecret.value = null;
|
||||||
} else {
|
} else {
|
||||||
@@ -107,6 +109,7 @@ watch(selectedProvider, (val) => {
|
|||||||
if (provider) {
|
if (provider) {
|
||||||
providerName.value = provider.metadata.name;
|
providerName.value = provider.metadata.name;
|
||||||
url.value = provider.spec?.url || '';
|
url.value = provider.spec?.url || '';
|
||||||
|
vddkInitImage.value = provider.spec?.settings?.vddkInitImage || '';
|
||||||
|
|
||||||
const secretRef = provider.spec?.secret;
|
const secretRef = provider.spec?.secret;
|
||||||
|
|
||||||
@@ -239,6 +242,18 @@ const testConnection = async(buttonCb) => {
|
|||||||
if (props.editMode && createdProvider.value) {
|
if (props.editMode && createdProvider.value) {
|
||||||
try {
|
try {
|
||||||
createdProvider.value.spec.url = url.value;
|
createdProvider.value.spec.url = url.value;
|
||||||
|
|
||||||
|
const vddkImage = vddkInitImage.value?.trim();
|
||||||
|
|
||||||
|
if (vddkImage) {
|
||||||
|
createdProvider.value.spec.settings = {
|
||||||
|
...(createdProvider.value.spec.settings || {}),
|
||||||
|
vddkInitImage: vddkImage,
|
||||||
|
};
|
||||||
|
} else if (createdProvider.value.spec.settings) {
|
||||||
|
delete createdProvider.value.spec.settings.vddkInitImage;
|
||||||
|
}
|
||||||
|
|
||||||
await createdProvider.value.save();
|
await createdProvider.value.save();
|
||||||
|
|
||||||
const secretRef = createdProvider.value.spec?.secret;
|
const secretRef = createdProvider.value.spec?.secret;
|
||||||
@@ -283,20 +298,28 @@ const testConnection = async(buttonCb) => {
|
|||||||
const namespace = FORKLIFT_NAMESPACE;
|
const namespace = FORKLIFT_NAMESPACE;
|
||||||
const secretName = `${ providerName.value }-creds-${ randomStr(4).toLowerCase() }`;
|
const secretName = `${ providerName.value }-creds-${ randomStr(4).toLowerCase() }`;
|
||||||
|
|
||||||
|
const spec = {
|
||||||
|
type: 'vsphere',
|
||||||
|
url: url.value,
|
||||||
|
secret: {
|
||||||
|
name: secretName,
|
||||||
|
namespace,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const vddkImage = vddkInitImage.value?.trim();
|
||||||
|
|
||||||
|
if (vddkImage) {
|
||||||
|
spec.settings = { vddkInitImage: vddkImage };
|
||||||
|
}
|
||||||
|
|
||||||
const provider = await store.dispatch(`${ inStore }/create`, {
|
const provider = await store.dispatch(`${ inStore }/create`, {
|
||||||
type: HCI.FORKLIFT_PROVIDER,
|
type: HCI.FORKLIFT_PROVIDER,
|
||||||
metadata: {
|
metadata: {
|
||||||
name: providerName.value,
|
name: providerName.value,
|
||||||
namespace,
|
namespace,
|
||||||
},
|
},
|
||||||
spec: {
|
spec,
|
||||||
type: 'vsphere',
|
|
||||||
url: url.value,
|
|
||||||
secret: {
|
|
||||||
name: secretName,
|
|
||||||
namespace,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await provider.save();
|
await provider.save();
|
||||||
@@ -420,17 +443,28 @@ defineExpose({ testConnection, clickTestButton });
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="row">
|
||||||
<LabeledInput
|
<div class="col span-6">
|
||||||
v-model:value="url"
|
<LabeledInput
|
||||||
:label="t('harvester.addons.vmMigration.configureProvider.urlLabel')"
|
v-model:value="url"
|
||||||
:placeholder="t('harvester.addons.vmMigration.configureProvider.urlPlaceholder')"
|
:label="t('harvester.addons.vmMigration.configureProvider.urlLabel')"
|
||||||
:disabled="isExistingProvider && !editMode"
|
:placeholder="t('harvester.addons.vmMigration.configureProvider.urlPlaceholder')"
|
||||||
required
|
:disabled="isExistingProvider && !editMode"
|
||||||
/>
|
required
|
||||||
<p class="text-deemphasized mt-5">
|
/>
|
||||||
{{ t('harvester.addons.vmMigration.configureProvider.urlHint') }}
|
<p class="text-deemphasized mt-5">
|
||||||
</p>
|
{{ t('harvester.addons.vmMigration.configureProvider.urlHint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="vddkInitImage"
|
||||||
|
:label="t('harvester.addons.vmMigration.configureProvider.vddkImageLabel')"
|
||||||
|
:placeholder="t('harvester.addons.vmMigration.configureProvider.vddkImagePlaceholder')"
|
||||||
|
:tooltip="t('harvester.addons.vmMigration.configureProvider.vddkImageTooltip')"
|
||||||
|
:disabled="isExistingProvider && !editMode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -1,25 +1,33 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { RcItemCard } from '@components/RcItemCard';
|
import { RcItemCard } from '@components/RcItemCard';
|
||||||
import { useI18n } from '@shell/composables/useI18n';
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { VOLUME_MODE } from '../../config/types';
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { t } = useI18n(store);
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: { type: String, required: true },
|
title: { type: String, required: true },
|
||||||
description: { type: String, default: '' },
|
description: { type: String, default: '' },
|
||||||
entries: { type: Array, default: () => [] },
|
entries: { type: Array, default: () => [] },
|
||||||
options: { type: Array, default: () => [] },
|
options: { type: Array, default: () => [] },
|
||||||
placeholder: { type: String, default: '' },
|
placeholder: { type: String, default: '' },
|
||||||
showUsedBy: { type: Boolean, default: false },
|
showUsedBy: { type: Boolean, default: false },
|
||||||
clearable: { type: Boolean, default: false },
|
clearable: { type: Boolean, default: false },
|
||||||
|
// Storage-specific: show the volume/access mode defaults row with an Edit action.
|
||||||
|
showVolumeSettings: { type: Boolean, default: false },
|
||||||
|
// When set, the defaults row shows an "Inherited from provider" hint (migration plan wizard only).
|
||||||
|
inheritedProviderName: { type: String, default: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectOptions = computed(() => {
|
const emit = defineEmits(['edit-defaults']);
|
||||||
if (!props.clearable) {
|
|
||||||
|
// Only offer "Remove Map" for entries that already have a target selected;
|
||||||
|
// entries without a selection just show the regular options.
|
||||||
|
const optionsFor = (entry) => {
|
||||||
|
if (!props.clearable || !entry.target) {
|
||||||
return props.options;
|
return props.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,14 +42,16 @@ const selectOptions = computed(() => {
|
|||||||
disabled: true,
|
disabled: true,
|
||||||
kind: 'divider',
|
kind: 'divider',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: `${ props.placeholder }:`,
|
|
||||||
disabled: true,
|
|
||||||
kind: 'title',
|
|
||||||
},
|
|
||||||
...props.options,
|
...props.options,
|
||||||
];
|
];
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const formatModes = (entry) => {
|
||||||
|
const volumeMode = entry.volumeMode || VOLUME_MODE.FILE_SYSTEM;
|
||||||
|
const accessModes = (entry.accessModes || []).join(', ');
|
||||||
|
|
||||||
|
return t('harvester.addons.vmMigration.storageDefaults.summary', { volumeMode, accessModes });
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -85,12 +95,35 @@ const selectOptions = computed(() => {
|
|||||||
<div class="mapping-target">
|
<div class="mapping-target">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model:value="entry.target"
|
v-model:value="entry.target"
|
||||||
:options="selectOptions"
|
:options="optionsFor(entry)"
|
||||||
:placeholder="placeholder+'...'"
|
:placeholder="placeholder+'...'"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showVolumeSettings && entry.target"
|
||||||
|
class="storage-defaults-row"
|
||||||
|
>
|
||||||
|
<div class="storage-defaults">
|
||||||
|
<div class="storage-defaults-info">
|
||||||
|
<span class="storage-defaults-summary">{{ formatModes(entry) }}</span>
|
||||||
|
<span
|
||||||
|
v-if="inheritedProviderName && entry.inheritedFromProvider && !entry.overridden"
|
||||||
|
class="text-deemphasized storage-defaults-inherited"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.vmMigration.storageDefaults.inherited', { provider: inheritedProviderName }) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
role="button"
|
||||||
|
class="storage-defaults-edit"
|
||||||
|
@click.prevent="emit('edit-defaults', entry)"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.vmMigration.storageDefaults.edit') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="showUsedBy && entry.usedBy && entry.usedBy.length">
|
<div v-if="showUsedBy && entry.usedBy && entry.usedBy.length">
|
||||||
<span class="used-by">
|
<span class="used-by">
|
||||||
{{ t('harvester.addons.vmMigration.generic.usedBy') }} <b>{{ entry.usedBy.join(', ') }}</b>
|
{{ t('harvester.addons.vmMigration.generic.usedBy') }} <b>{{ entry.usedBy.join(', ') }}</b>
|
||||||
@@ -177,6 +210,41 @@ const selectOptions = computed(() => {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.storage-defaults-row {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-defaults {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--body-bg);
|
||||||
|
|
||||||
|
.storage-defaults-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-defaults-summary {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-defaults-inherited {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-defaults-edit {
|
||||||
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bg-light-gray {
|
.bg-light-gray {
|
||||||
background-color: var(--category-active) !important;
|
background-color: var(--category-active) !important;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import Loading from '@shell/components/Loading';
|
import Loading from '@shell/components/Loading';
|
||||||
|
import SortableTable from '@shell/components/SortableTable';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import { RcItemCard } from '@components/RcItemCard';
|
import { BadgeState } from '@components/BadgeState';
|
||||||
|
import { RcDropdown, RcDropdownItem, RcDropdownTrigger } from '@components/RcDropdown';
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import MappingsCell from '../MappingsCell';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { useI18n } from '@shell/composables/useI18n';
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { NAMESPACE } from '@shell/config/types';
|
||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
import { FORKLIFT_NAMESPACE } from '../../config/harvester-map';
|
import { FORKLIFT_NAMESPACE } from '../../config/harvester-map';
|
||||||
import {
|
import {
|
||||||
@@ -21,29 +24,70 @@ const props = defineProps({
|
|||||||
stepData: { type: Object, required: true },
|
stepData: { type: Object, required: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['ready']);
|
const emit = defineEmits(['ready', 'edit-selection', 'edit-mappings', 'remove-vm']);
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { t } = useI18n(store);
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
const vms = ref([]);
|
const vms = computed(() => props.selectedVms);
|
||||||
const networkMappings = ref([]);
|
const networkMappings = ref([]);
|
||||||
const storageMappings = ref([]);
|
const storageMappings = ref([]);
|
||||||
const planName = ref('');
|
const planName = ref('');
|
||||||
|
const targetNamespace = ref('');
|
||||||
|
const namespaceOptions = ref([]);
|
||||||
|
const existingProviderNames = ref([]);
|
||||||
|
const existingPlanNames = ref([]);
|
||||||
const errors = ref([]);
|
const errors = ref([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
// Restore persisted state
|
// Restore persisted state
|
||||||
planName.value = props.stepData.planName;
|
planName.value = props.stepData.planName;
|
||||||
|
targetNamespace.value = props.stepData.targetNamespace || '';
|
||||||
|
|
||||||
const NAMESPACE = FORKLIFT_NAMESPACE;
|
const NS = FORKLIFT_NAMESPACE;
|
||||||
const TARGET_NAMESPACE = 'default';
|
|
||||||
|
const nameConflictError = computed(() => {
|
||||||
|
const name = (planName.value || '').trim();
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingProviderNames.value.includes(name)) {
|
||||||
|
return t('harvester.addons.vmMigration.reviewMigration.planNameProviderConflict', { name });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingPlanNames.value.includes(name)) {
|
||||||
|
return t('harvester.addons.vmMigration.reviewMigration.planNamePlanConflict', { name });
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
watch(planName, (val) => {
|
watch(planName, (val) => {
|
||||||
props.stepData.planName = val;
|
props.stepData.planName = val;
|
||||||
emit('ready', !!val);
|
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
|
watch(targetNamespace, (val) => {
|
||||||
|
props.stepData.targetNamespace = val;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch([planName, targetNamespace, nameConflictError], () => {
|
||||||
|
emit('ready', !!planName.value && !!targetNamespace.value && !nameConflictError.value);
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
const formatNetworkTarget = (target = '') => {
|
||||||
|
if (target === 'pod') {
|
||||||
|
return t('harvester.addons.vmMigration.configureMappings.networkMapping.options.podNetworking');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target === 'ignored') {
|
||||||
|
return t('harvester.addons.vmMigration.generic.ignored');
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
const totalVCpu = computed(() => vms.value.reduce((sum, vm) => sum + (vm.cpuCount || vm.numCPU || 0), 0));
|
const totalVCpu = computed(() => vms.value.reduce((sum, vm) => sum + (vm.cpuCount || vm.numCPU || 0), 0));
|
||||||
|
|
||||||
const totalMemoryGB = computed(() => {
|
const totalMemoryGB = computed(() => {
|
||||||
@@ -64,7 +108,7 @@ const totalStorageGB = computed(() => {
|
|||||||
return bytesToGB(totalBytes);
|
return bytesToGB(totalBytes);
|
||||||
});
|
});
|
||||||
|
|
||||||
const vmCards = computed(() => {
|
const vmRows = computed(() => {
|
||||||
return vms.value.map((vm) => {
|
return vms.value.map((vm) => {
|
||||||
const cpus = vm.cpuCount || vm.numCPU || 0;
|
const cpus = vm.cpuCount || vm.numCPU || 0;
|
||||||
const memMB = vm.memoryMB || vm.memory || 0;
|
const memMB = vm.memoryMB || vm.memory || 0;
|
||||||
@@ -78,23 +122,93 @@ const vmCards = computed(() => {
|
|||||||
|
|
||||||
const diskDisplay = totalDiskBytes ? t('harvester.addons.vmMigration.generic.memoryGb', { value: bytesToGB(totalDiskBytes) }) : '-';
|
const diskDisplay = totalDiskBytes ? t('harvester.addons.vmMigration.generic.memoryGb', { value: bytesToGB(totalDiskBytes) }) : '-';
|
||||||
const os = vm.guestName || vm.guestOS || vm.os || '-';
|
const os = vm.guestName || vm.guestOS || vm.os || '-';
|
||||||
|
const rawPowerState = vm.powerState || vm.status?.phase || '-';
|
||||||
|
const powerState = rawPowerState.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/^./, (c) => c.toUpperCase());
|
||||||
|
const powerStateClass = powerState.toLowerCase().includes('on') || powerState.toLowerCase().includes('running') ? 'power-on' : 'power-off';
|
||||||
|
|
||||||
const vmNetworkMappings = networkMappings.value.filter((m) => m.usedBy && m.usedBy.includes(vm.name || vm.id));
|
const vmNetworkMappings = networkMappings.value.filter((m) => m.usedBy && m.usedBy.includes(vm.name || vm.id));
|
||||||
const vmStorageMappings = storageMappings.value.filter((m) => m.usedBy && m.usedBy.includes(vm.name || vm.id));
|
const vmStorageMappings = storageMappings.value.filter((m) => m.usedBy && m.usedBy.includes(vm.name || vm.id));
|
||||||
|
|
||||||
|
const networkMappingSort = vmNetworkMappings.map((m) => `${ m.source } ${ formatNetworkTarget(m.target) }`).join(', ');
|
||||||
|
const storageMappingSort = vmStorageMappings.map((m) => `${ m.source } ${ m.target }`).join(', ');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
_key: vm.id || vm.vmId || vm.metadata?.name,
|
||||||
id: vm.id,
|
id: vm.id,
|
||||||
name: vm.name || vm.id,
|
name: vm.name || vm.id,
|
||||||
|
identifier: vm.id || vm.vmId || vm.metadata?.name || '-',
|
||||||
os,
|
os,
|
||||||
cpus,
|
resources: t('harvester.addons.vmMigration.generic.vCpu', { count: cpus }),
|
||||||
memGB,
|
resourcesSub: `${ memGB } • ${ diskDisplay }`,
|
||||||
diskDisplay,
|
powerState,
|
||||||
|
powerStateClass,
|
||||||
networkMappings: vmNetworkMappings,
|
networkMappings: vmNetworkMappings,
|
||||||
storageMappings: vmStorageMappings,
|
storageMappings: vmStorageMappings,
|
||||||
|
networkMapping: networkMappingSort,
|
||||||
|
storageMapping: storageMappingSort,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const vmHeaders = [
|
||||||
|
{
|
||||||
|
name: 'vmName',
|
||||||
|
labelKey: 'harvester.addons.vmMigration.reviewMigration.columns.vmName',
|
||||||
|
value: 'name',
|
||||||
|
sort: ['name'],
|
||||||
|
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'os',
|
||||||
|
labelKey: 'harvester.addons.vmMigration.reviewMigration.columns.os',
|
||||||
|
value: 'os',
|
||||||
|
sort: ['os'],
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'resources',
|
||||||
|
labelKey: 'harvester.addons.vmMigration.reviewMigration.columns.resources',
|
||||||
|
value: 'resources',
|
||||||
|
sort: false,
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'powerState',
|
||||||
|
labelKey: 'harvester.addons.vmMigration.reviewMigration.columns.powerState',
|
||||||
|
value: 'powerState',
|
||||||
|
sort: ['powerState'],
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'networkMapping',
|
||||||
|
labelKey: 'harvester.addons.vmMigration.reviewMigration.columns.networkMapping',
|
||||||
|
value: 'networkMapping',
|
||||||
|
sort: ['networkMapping'],
|
||||||
|
subLabel: t('harvester.addons.vmMigration.reviewMigration.columns.sourceTarget'),
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'storageMapping',
|
||||||
|
labelKey: 'harvester.addons.vmMigration.reviewMigration.columns.storageMapping',
|
||||||
|
value: 'storageMapping',
|
||||||
|
sort: ['storageMapping'],
|
||||||
|
subLabel: t('harvester.addons.vmMigration.reviewMigration.columns.sourceTarget'),
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
label: ' ',
|
||||||
|
align: 'right',
|
||||||
|
sort: false,
|
||||||
|
width: 40,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const onEditSelection = () => emit('edit-selection');
|
||||||
|
const onEditMappings = () => emit('edit-mappings');
|
||||||
|
const onRemoveVm = (id) => emit('remove-vm', id);
|
||||||
|
|
||||||
const startMigrationAction = async() => {
|
const startMigrationAction = async() => {
|
||||||
const inStore = store.getters['currentProduct'].inStore;
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
@@ -103,13 +217,13 @@ const startMigrationAction = async() => {
|
|||||||
apiVersion: FORKLIFT_API_VERSION,
|
apiVersion: FORKLIFT_API_VERSION,
|
||||||
kind: 'Provider',
|
kind: 'Provider',
|
||||||
name: props.providerName,
|
name: props.providerName,
|
||||||
namespace: NAMESPACE,
|
namespace: NS,
|
||||||
},
|
},
|
||||||
destination: {
|
destination: {
|
||||||
apiVersion: FORKLIFT_API_VERSION,
|
apiVersion: FORKLIFT_API_VERSION,
|
||||||
kind: 'Provider',
|
kind: 'Provider',
|
||||||
name: 'host',
|
name: 'host',
|
||||||
namespace: NAMESPACE,
|
namespace: NS,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,15 +232,15 @@ const startMigrationAction = async() => {
|
|||||||
|
|
||||||
const networkMap = await store.dispatch(`${ inStore }/create`, {
|
const networkMap = await store.dispatch(`${ inStore }/create`, {
|
||||||
type: HCI.FORKLIFT_NETWORK_MAP,
|
type: HCI.FORKLIFT_NETWORK_MAP,
|
||||||
metadata: { name: networkMapName, namespace: NAMESPACE },
|
metadata: { name: networkMapName, namespace: NS },
|
||||||
spec: { map: buildNetworkMapEntries(props.mappingEntries?.networkEntries || [], NAMESPACE), provider: providerRef },
|
spec: { map: buildNetworkMapEntries(props.mappingEntries?.networkEntries || [], NS), provider: providerRef },
|
||||||
});
|
});
|
||||||
|
|
||||||
await networkMap.save();
|
await networkMap.save();
|
||||||
|
|
||||||
const storageMap = await store.dispatch(`${ inStore }/create`, {
|
const storageMap = await store.dispatch(`${ inStore }/create`, {
|
||||||
type: HCI.FORKLIFT_STORAGE_MAP,
|
type: HCI.FORKLIFT_STORAGE_MAP,
|
||||||
metadata: { name: storageMapName, namespace: NAMESPACE },
|
metadata: { name: storageMapName, namespace: NS },
|
||||||
spec: { map: buildStorageMapEntries(props.mappingEntries?.storageEntries || []), provider: providerRef },
|
spec: { map: buildStorageMapEntries(props.mappingEntries?.storageEntries || []), provider: providerRef },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,7 +248,7 @@ const startMigrationAction = async() => {
|
|||||||
|
|
||||||
const plan = await store.dispatch(`${ inStore }/create`, {
|
const plan = await store.dispatch(`${ inStore }/create`, {
|
||||||
type: HCI.FORKLIFT_PLAN,
|
type: HCI.FORKLIFT_PLAN,
|
||||||
metadata: { name: planName.value, namespace: NAMESPACE },
|
metadata: { name: planName.value, namespace: NS },
|
||||||
spec: {
|
spec: {
|
||||||
provider: providerRef,
|
provider: providerRef,
|
||||||
map: {
|
map: {
|
||||||
@@ -142,16 +256,16 @@ const startMigrationAction = async() => {
|
|||||||
apiVersion: FORKLIFT_API_VERSION,
|
apiVersion: FORKLIFT_API_VERSION,
|
||||||
kind: 'NetworkMap',
|
kind: 'NetworkMap',
|
||||||
name: networkMapName,
|
name: networkMapName,
|
||||||
namespace: NAMESPACE,
|
namespace: NS,
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
apiVersion: FORKLIFT_API_VERSION,
|
apiVersion: FORKLIFT_API_VERSION,
|
||||||
kind: 'StorageMap',
|
kind: 'StorageMap',
|
||||||
name: storageMapName,
|
name: storageMapName,
|
||||||
namespace: NAMESPACE,
|
namespace: NS,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
targetNamespace: TARGET_NAMESPACE,
|
targetNamespace: targetNamespace.value,
|
||||||
vms: vms.value.map((vm) => ({ id: vm.id, name: vm.name || vm.id })),
|
vms: vms.value.map((vm) => ({ id: vm.id, name: vm.name || vm.id })),
|
||||||
warm: false,
|
warm: false,
|
||||||
},
|
},
|
||||||
@@ -159,26 +273,37 @@ const startMigrationAction = async() => {
|
|||||||
|
|
||||||
await plan.save();
|
await plan.save();
|
||||||
|
|
||||||
const planOwnerRef = {
|
|
||||||
apiVersion: FORKLIFT_API_VERSION,
|
|
||||||
kind: 'Plan',
|
|
||||||
name: plan.metadata.name,
|
|
||||||
uid: plan.metadata.uid,
|
|
||||||
blockOwnerDeletion: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
networkMap.metadata.ownerReferences = [planOwnerRef];
|
|
||||||
await networkMap.save();
|
|
||||||
storageMap.metadata.ownerReferences = [planOwnerRef];
|
|
||||||
await storageMap.save();
|
|
||||||
|
|
||||||
// Kick off the first migration through the model so the Migration payload
|
// Kick off the first migration through the model so the Migration payload
|
||||||
// lives in a single place (also reused by the dashboard start/restart action).
|
// lives in a single place (also reused by the dashboard start/restart action).
|
||||||
await plan.startMigration();
|
await plan.startMigration();
|
||||||
};
|
};
|
||||||
|
|
||||||
const init = () => {
|
const init = async() => {
|
||||||
vms.value = props.selectedVms;
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const namespaces = await store.dispatch(`${ inStore }/findAll`, { type: NAMESPACE });
|
||||||
|
|
||||||
|
namespaceOptions.value = namespaces.filter((ns) => !ns.isSystem).map((ns) => ns.metadata.name).sort().map((name) => ({ label: name, value: name }));
|
||||||
|
} catch (e) {
|
||||||
|
namespaceOptions.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
existingProviderNames.value = (store.getters[`${ inStore }/all`](HCI.FORKLIFT_PROVIDER) || [])
|
||||||
|
.map((p) => p.metadata?.name)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PLAN });
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
existingPlanNames.value = (store.getters[`${ inStore }/all`](HCI.FORKLIFT_PLAN) || [])
|
||||||
|
.map((p) => p.metadata?.name)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
if (props.mappingEntries) {
|
if (props.mappingEntries) {
|
||||||
networkMappings.value = (props.mappingEntries.networkEntries || []).map((entry) => ({
|
networkMappings.value = (props.mappingEntries.networkEntries || []).map((entry) => ({
|
||||||
@@ -211,116 +336,178 @@ defineExpose({ startMigration: startMigrationAction });
|
|||||||
<div
|
<div
|
||||||
class="review-migration-content"
|
class="review-migration-content"
|
||||||
>
|
>
|
||||||
|
<Banner
|
||||||
|
v-if="nameConflictError"
|
||||||
|
color="error"
|
||||||
|
:label="nameConflictError"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Migration Details Summary -->
|
<!-- Migration Details Summary -->
|
||||||
<div class="migration-details">
|
<div class="migration-details">
|
||||||
<h3 class="section-title m-0">
|
<div class="section-header">
|
||||||
{{ t('harvester.addons.vmMigration.reviewMigration.migrationDetails') }}
|
<h3 class="section-title m-0">
|
||||||
</h3>
|
{{ t('harvester.addons.vmMigration.reviewMigration.migrationDetails') }}
|
||||||
<div class="span-9">
|
</h3>
|
||||||
|
<span class="section-description text-deemphasized">
|
||||||
|
{{ t('harvester.addons.vmMigration.reviewMigration.migrationDetailsDescription') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="name-row">
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model:value="planName"
|
v-model:value="planName"
|
||||||
|
class="name-input"
|
||||||
:label="t('harvester.addons.vmMigration.reviewMigration.planName')"
|
:label="t('harvester.addons.vmMigration.reviewMigration.planName')"
|
||||||
:placeholder="t('harvester.addons.vmMigration.reviewMigration.planNamePlaceholder')"
|
:placeholder="t('harvester.addons.vmMigration.reviewMigration.planNamePlaceholder')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="targetNamespace"
|
||||||
|
class="namespace-select"
|
||||||
|
:label="t('harvester.addons.vmMigration.reviewMigration.targetNamespace')"
|
||||||
|
:placeholder="t('harvester.addons.vmMigration.reviewMigration.targetNamespacePlaceholder')"
|
||||||
|
:options="namespaceOptions"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="details-grid span-9">
|
|
||||||
<div class="detail-column">
|
<div class="summary-row">
|
||||||
<div class="detail-item">
|
<div class="migration-mode-column">
|
||||||
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.totalVms') }}</span>
|
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.migrationMode') }}</span>
|
||||||
<span class="detail-value detail-value-large">{{ vms.length }}</span>
|
<span class="cold-migration-badge">{{ t('harvester.addons.vmMigration.reviewMigration.coldMigration') }}</span>
|
||||||
</div>
|
<span class="text-deemphasized">{{ t('harvester.addons.vmMigration.reviewMigration.coldMigrationDescription') }}</span>
|
||||||
|
<span class="text-deemphasized">{{ t('harvester.addons.vmMigration.reviewMigration.coldMigrationHint') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-column">
|
|
||||||
<div class="detail-item">
|
<div class="stats-column">
|
||||||
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.source') }}</span>
|
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.provider') }}</span>
|
||||||
</div>
|
<span class="detail-value">{{ providerName }}</span>
|
||||||
<div class="detail-item">
|
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.totalVms') }}</span>
|
||||||
<span class="detail-value">{{ providerName }}</span>
|
<span class="detail-value">{{ vms.length }}</span>
|
||||||
</div>
|
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.vcpu') }}</span>
|
||||||
</div>
|
<span class="detail-value">{{ totalVCpu }}</span>
|
||||||
<div class="detail-column">
|
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.memory') }}</span>
|
||||||
<div class="detail-item">
|
<span class="detail-value">{{ t('harvester.addons.vmMigration.generic.memoryGb', { value: totalMemoryGB }) }}</span>
|
||||||
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.targetNamespace') }}</span>
|
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.storage') }}</span>
|
||||||
</div>
|
<span class="detail-value">{{ t('harvester.addons.vmMigration.generic.memoryGb', { value: totalStorageGB }) }}</span>
|
||||||
<div class="detail-item">
|
|
||||||
<span class="detail-value">{{ TARGET_NAMESPACE }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="detail-column">
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.vcpu') }}</span>
|
|
||||||
<span class="detail-value">{{ totalVCpu }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.memory') }}</span>
|
|
||||||
<span class="detail-value">{{ t('harvester.addons.vmMigration.generic.memoryGb', { value: totalMemoryGB }) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.storage') }}</span>
|
|
||||||
<span class="detail-value">{{ t('harvester.addons.vmMigration.generic.memoryGb', { value: totalStorageGB }) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="detail-column grid-column-2">
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="detail-label">{{ t('harvester.addons.vmMigration.reviewMigration.migrationMode') }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-item">
|
|
||||||
<span class="detail-value">
|
|
||||||
{{ t('harvester.addons.vmMigration.reviewMigration.coldMigration') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Virtual Machines -->
|
<!-- Virtual Machines -->
|
||||||
<div class="vm-section">
|
<div class="vm-section">
|
||||||
<h3 class="section-title m-0">
|
<SortableTable
|
||||||
{{ t('harvester.addons.vmMigration.reviewMigration.virtualMachines') }} ({{ vms.length }})
|
class="vm-sortable-table"
|
||||||
</h3>
|
:rows="vmRows"
|
||||||
|
:headers="vmHeaders"
|
||||||
<div class="vm-cards-grid">
|
:search="false"
|
||||||
<RcItemCard
|
:table-actions="false"
|
||||||
v-for="vm in vmCards"
|
:row-actions="false"
|
||||||
:id="vm.id"
|
:groupable="false"
|
||||||
:key="vm.id"
|
:paging="true"
|
||||||
:variant="'small'"
|
key-field="_key"
|
||||||
:header="{ title: { text: vm.name } }"
|
>
|
||||||
>
|
<template #header-left>
|
||||||
<template #item-card-content>
|
<h4 class="section-title m-0">
|
||||||
<div class="vm-card-content">
|
{{ t('harvester.addons.vmMigration.reviewMigration.virtualMachines') }} ({{ vms.length }})
|
||||||
<div class="vm-card-specs">
|
</h4>
|
||||||
<span class="vm-os text-deemphasized">{{ vm.os }}</span>
|
</template>
|
||||||
<span class="vm-resources">
|
<template #header-right>
|
||||||
<i
|
<a
|
||||||
class="icon icon-disk"
|
href="#"
|
||||||
aria-hidden="true"
|
class="edit-selection-link"
|
||||||
/>
|
@click.prevent="onEditSelection"
|
||||||
{{ t('harvester.addons.vmMigration.generic.vCpu', { count: vm.cpus }) }} • {{ vm.memGB }} • {{ vm.diskDisplay }}
|
>
|
||||||
</span>
|
{{ t('harvester.addons.vmMigration.reviewMigration.editSelection') }}
|
||||||
</div>
|
</a>
|
||||||
<MappingsCell
|
</template>
|
||||||
:network-entries="vm.networkMappings.map(m => `${m.source} → ${m.target}`)"
|
<template #cell:vmName="{ row }">
|
||||||
:storage-entries="vm.storageMappings.map(m => `${m.source} → ${m.target}`)"
|
<div class="vm-name-cell">
|
||||||
/>
|
<span class="vm-name">{{ row.name }}</span>
|
||||||
|
<span class="vm-id text-deemphasized">{{ row.identifier }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #cell:resources="{ row }">
|
||||||
|
<span>{{ row.resources }}</span><br>
|
||||||
|
<span class="text-deemphasized">{{ row.resourcesSub }}</span>
|
||||||
|
</template>
|
||||||
|
<template #cell:powerState="{ row }">
|
||||||
|
<BadgeState
|
||||||
|
:label="row.powerState"
|
||||||
|
:color="row.powerStateClass === 'power-on' ? 'bg-warning' : 'bg-darker'"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #cell:networkMapping="{ row }">
|
||||||
|
<template v-if="row.networkMappings.length">
|
||||||
|
<div
|
||||||
|
v-for="(m, i) in row.networkMappings"
|
||||||
|
:key="'net-' + i"
|
||||||
|
class="mapping-cell"
|
||||||
|
>
|
||||||
|
<span class="mapping-source">{{ m.source }}</span>
|
||||||
|
<span class="mapping-target text-deemphasized">→ {{ formatNetworkTarget(m.target) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</RcItemCard>
|
<span
|
||||||
</div>
|
v-else
|
||||||
|
class="text-deemphasized"
|
||||||
|
>-</span>
|
||||||
|
</template>
|
||||||
|
<template #cell:storageMapping="{ row }">
|
||||||
|
<template v-if="row.storageMappings.length">
|
||||||
|
<div
|
||||||
|
v-for="(m, i) in row.storageMappings"
|
||||||
|
:key="'stor-' + i"
|
||||||
|
class="mapping-cell"
|
||||||
|
>
|
||||||
|
<span class="mapping-source">{{ m.source }}</span>
|
||||||
|
<span class="mapping-target text-deemphasized">→ {{ m.target }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="text-deemphasized"
|
||||||
|
>-</span>
|
||||||
|
</template>
|
||||||
|
<template #cell:actions="{ row }">
|
||||||
|
<rc-dropdown :aria-label="t('harvester.addons.vmMigration.reviewMigration.actions.menuLabel')">
|
||||||
|
<rc-dropdown-trigger
|
||||||
|
variant="ghost"
|
||||||
|
:aria-label="t('harvester.addons.vmMigration.reviewMigration.actions.menuLabel')"
|
||||||
|
>
|
||||||
|
<i class="icon icon-actions" />
|
||||||
|
</rc-dropdown-trigger>
|
||||||
|
<template #dropdownCollection>
|
||||||
|
<rc-dropdown-item @click="onEditMappings">
|
||||||
|
{{ t('harvester.addons.vmMigration.reviewMigration.actions.editMappings') }}
|
||||||
|
</rc-dropdown-item>
|
||||||
|
<rc-dropdown-item
|
||||||
|
class="text-error"
|
||||||
|
@click="onRemoveVm(row.id)"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.vmMigration.reviewMigration.actions.removeFromPlan') }}
|
||||||
|
</rc-dropdown-item>
|
||||||
|
</template>
|
||||||
|
</rc-dropdown>
|
||||||
|
</template>
|
||||||
|
<template #no-rows>
|
||||||
|
<tr class="no-rows">
|
||||||
|
<td
|
||||||
|
:colspan="vmHeaders.length"
|
||||||
|
class="text-center"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.vmMigration.reviewMigration.noVmSelected') }}
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
class="edit-selection-link"
|
||||||
|
@click.prevent="onEditSelection"
|
||||||
|
>{{ t('harvester.addons.vmMigration.reviewMigration.noVmSelectedLink') }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</SortableTable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cold Migration Warning -->
|
|
||||||
<Banner
|
|
||||||
color="warning"
|
|
||||||
class="m-0"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<span class="banner-title">{{ t('harvester.addons.vmMigration.reviewMigration.warningTitle') }}</span><br>
|
|
||||||
{{ t('harvester.addons.vmMigration.reviewMigration.warningMessage') }}
|
|
||||||
</div>
|
|
||||||
</Banner>
|
|
||||||
|
|
||||||
<!-- Error banner -->
|
<!-- Error banner -->
|
||||||
<Banner
|
<Banner
|
||||||
v-for="(err, i) in errors"
|
v-for="(err, i) in errors"
|
||||||
@@ -347,74 +534,122 @@ defineExpose({ startMigration: startMigrationAction });
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-grid {
|
.name-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(230px, 1fr) 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
gap: 12px clamp(12px, 1vw, 96px);
|
||||||
gap: 12px clamp(64px, 8vw, 128px);
|
align-items: start;
|
||||||
line-height: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-column {
|
.summary-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px clamp(12px, 1vw, 96px);
|
||||||
|
line-height: 20px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
&.grid-column-2 {
|
.section-description {
|
||||||
grid-column: span 2;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-item {
|
.migration-mode-column {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
.detail-label {
|
.cold-migration-badge {
|
||||||
font-size: 14px;
|
padding: 2px 10px;
|
||||||
color: var(--muted);
|
border-radius: 4px;
|
||||||
}
|
opacity: 0.8;
|
||||||
|
font-weight: 600;
|
||||||
.detail-value {
|
background: rgba(224, 191, 90, 0.16);
|
||||||
|
color: #E0BF5A;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
&.detail-value-large {
|
.theme-light & {
|
||||||
font-size: 24px;
|
background: rgba(168, 128, 26, 0.16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-cards-grid {
|
.stats-column {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
grid-template-columns: auto auto;
|
||||||
gap: 16px;
|
justify-content: start;
|
||||||
|
align-content: start;
|
||||||
|
column-gap: clamp(24px, 4vw, 64px);
|
||||||
|
row-gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-card-content {
|
.detail-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-selection-link {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--link);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-sortable-table {
|
||||||
|
:deep(.fixed-header-actions) {
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(table) {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(td) {
|
||||||
|
vertical-align: middle;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(th:first-child),
|
||||||
|
:deep(td:first-child) {
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-name-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
|
||||||
line-height: 20px;
|
.vm-name {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-id {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-card-specs {
|
.mapping-cell {
|
||||||
display: flex;
|
.mapping-target {
|
||||||
flex-direction: row;
|
margin-left: 4px;
|
||||||
gap: 16px;
|
white-space: nowrap;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.vm-os {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.vm-resources {
|
.mapping-cell + .mapping-cell {
|
||||||
font-size: 14px;
|
margin-top: 8px;
|
||||||
color: var(--muted);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner-title {
|
.banner-title {
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ const { discoveredVMs, selectedVMIds, tableRows } = toRefs(props.stepData);
|
|||||||
|
|
||||||
const selectedVMs = ref([]);
|
const selectedVMs = ref([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const networkMap = ref({});
|
// Restored from stepData so friendly names survive step navigation / remount.
|
||||||
const datastoreMap = ref({});
|
const networkMap = ref(props.stepData.networkMap || {});
|
||||||
|
const datastoreMap = ref(props.stepData.datastoreMap || {});
|
||||||
const sortableTableRef = ref(null);
|
const sortableTableRef = ref(null);
|
||||||
const allVMsSelected = ref(false);
|
const allVMsSelected = ref(false);
|
||||||
const errors = ref([]);
|
const errors = ref([]);
|
||||||
@@ -94,7 +95,7 @@ const showSelectAllBanner = computed(() => {
|
|||||||
|
|
||||||
const theadElement = computed(() => sortableTableRef.value?.$el?.querySelector('thead'));
|
const theadElement = computed(() => sortableTableRef.value?.$el?.querySelector('thead'));
|
||||||
|
|
||||||
const headers = [
|
const headers = ref([
|
||||||
{
|
{
|
||||||
name: 'vmName',
|
name: 'vmName',
|
||||||
labelKey: 'harvester.addons.vmMigration.selectVms.columns.vmName',
|
labelKey: 'harvester.addons.vmMigration.selectVms.columns.vmName',
|
||||||
@@ -139,7 +140,7 @@ const headers = [
|
|||||||
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
||||||
width: 200,
|
width: 200,
|
||||||
},
|
},
|
||||||
];
|
]);
|
||||||
|
|
||||||
const buildTableRows = () => {
|
const buildTableRows = () => {
|
||||||
return discoveredVMs.value.map((vm) => {
|
return discoveredVMs.value.map((vm) => {
|
||||||
@@ -190,6 +191,7 @@ const buildTableRows = () => {
|
|||||||
return {
|
return {
|
||||||
_original: vm,
|
_original: vm,
|
||||||
_key: vm.id || vm.vmId || vm.metadata?.name,
|
_key: vm.id || vm.vmId || vm.metadata?.name,
|
||||||
|
selectedSort: selectedVMIds.value.has(vm.id) ? 0 : 1,
|
||||||
vmName: vm.name || vm.metadata?.name || '-',
|
vmName: vm.name || vm.metadata?.name || '-',
|
||||||
vmId: vm.id || vm.vmId || vm.metadata?.name || '-',
|
vmId: vm.id || vm.vmId || vm.metadata?.name || '-',
|
||||||
os: vm.guestName || vm.guestOS || vm.os || '-',
|
os: vm.guestName || vm.guestOS || vm.os || '-',
|
||||||
@@ -239,6 +241,10 @@ const clearSelection = () => {
|
|||||||
|
|
||||||
if (table) {
|
if (table) {
|
||||||
table.clearSelection();
|
table.clearSelection();
|
||||||
|
|
||||||
|
if (typeof table.setPage === 'function') {
|
||||||
|
table.setPage(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -249,6 +255,49 @@ const selectAllVMs = () => {
|
|||||||
selectedVMs.value = discoveredVMs.value.slice();
|
selectedVMs.value = discoveredVMs.value.slice();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Moves currently-selected VMs to the first page(s) and re-checks them.
|
||||||
|
const sortSelectedToFront = () => {
|
||||||
|
if (selectedVMIds.value.size === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tableRows.value.forEach((row) => {
|
||||||
|
row.selectedSort = selectedVMIds.value.has(row._original?.id) ? 0 : 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
headers.value = headers.value.map((h) => (
|
||||||
|
h.name === 'vmName' ? { ...h, sort: ['selectedSort', 'vmName'] } : h
|
||||||
|
));
|
||||||
|
|
||||||
|
skipNextSelectionEvent = true;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const table = sortableTableRef.value;
|
||||||
|
|
||||||
|
if (!table) {
|
||||||
|
skipNextSelectionEvent = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof table.changeSort === 'function') {
|
||||||
|
table.changeSort('vmName', false);
|
||||||
|
} else if (typeof table.setPage === 'function') {
|
||||||
|
table.setPage(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
const rowsToReselect = (table.pagedRows || []).filter((row) => selectedVMIds.value.has(row._original?.id));
|
||||||
|
|
||||||
|
if (rowsToReselect.length > 0) {
|
||||||
|
table.update(rowsToReselect, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
skipNextSelectionEvent = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => sortableTableRef.value?.page,
|
() => sortableTableRef.value?.page,
|
||||||
() => {
|
() => {
|
||||||
@@ -318,6 +367,9 @@ const fetchVMs = async() => {
|
|||||||
return map;
|
return map;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
props.stepData.networkMap = networkMap.value;
|
||||||
|
props.stepData.datastoreMap = datastoreMap.value;
|
||||||
|
|
||||||
lastFetchedAt.value = Date.now();
|
lastFetchedAt.value = Date.now();
|
||||||
tableRows.value = buildTableRows();
|
tableRows.value = buildTableRows();
|
||||||
};
|
};
|
||||||
@@ -374,6 +426,10 @@ const init = async() => {
|
|||||||
tableRows.value = buildTableRows();
|
tableRows.value = buildTableRows();
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|
||||||
|
// Returning to the step with an existing selection: bring selected VMs forward and re-check them.
|
||||||
|
await nextTick();
|
||||||
|
sortSelectedToFront();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +497,6 @@ init();
|
|||||||
:row-actions="false"
|
:row-actions="false"
|
||||||
:groupable="false"
|
:groupable="false"
|
||||||
:paging="true"
|
:paging="true"
|
||||||
:rows-per-page="20"
|
|
||||||
key-field="_key"
|
key-field="_key"
|
||||||
@selection="onSelect"
|
@selection="onSelect"
|
||||||
>
|
>
|
||||||
@@ -450,7 +505,20 @@ init();
|
|||||||
<h3 class="m-0">
|
<h3 class="m-0">
|
||||||
{{ t('harvester.addons.vmMigration.selectVms.availableVms') }}
|
{{ t('harvester.addons.vmMigration.selectVms.availableVms') }}
|
||||||
</h3>
|
</h3>
|
||||||
<span class="text-deemphasized">{{ selectedCount }} {{ t('harvester.addons.vmMigration.selectVms.selected') }}</span>
|
<span class="selected-actions">
|
||||||
|
<span class="text-deemphasized">
|
||||||
|
{{ selectedCount }} {{ t('harvester.addons.vmMigration.selectVms.selected') }}
|
||||||
|
</span>
|
||||||
|
<template v-if="selectedCount > 0">
|
||||||
|
<span class="text-deemphasized">|</span>
|
||||||
|
<a
|
||||||
|
role="button"
|
||||||
|
@click.prevent="clearSelection"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.vmMigration.selectVms.clearAll') }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #cell:vmName="{ row }">
|
<template #cell:vmName="{ row }">
|
||||||
@@ -466,7 +534,7 @@ init();
|
|||||||
<template #cell:powerState="{ row }">
|
<template #cell:powerState="{ row }">
|
||||||
<BadgeState
|
<BadgeState
|
||||||
:label="row.powerState"
|
:label="row.powerState"
|
||||||
:color="row.powerStateClass === 'power-on' ? 'bg-warning' : 'bg-darker'"
|
:color="row.powerStateClass === 'power-on' ? 'bg-success' : 'bg-darker'"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #cell:network="{ row }">
|
<template #cell:network="{ row }">
|
||||||
@@ -533,6 +601,23 @@ init();
|
|||||||
.text-deemphasized {
|
.text-deemphasized {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selected-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: var(--muted);
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-name-cell {
|
.vm-name-cell {
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { Card } from '@components/Card';
|
||||||
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
|
import AppModal from '@shell/components/AppModal';
|
||||||
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { VOLUME_MODE, ACCESS_MODE } from '../../config/types';
|
||||||
|
|
||||||
|
const VOLUME_MODE_OPTIONS = [VOLUME_MODE.FILE_SYSTEM, VOLUME_MODE.BLOCK];
|
||||||
|
const {
|
||||||
|
READ_WRITE_ONCE = 'ReadWriteOnce',
|
||||||
|
READ_ONLY_MANY = 'ReadOnlyMany',
|
||||||
|
READ_WRITE_MANY = 'ReadWriteMany',
|
||||||
|
READ_WRITE_ONCE_POD = 'ReadWriteOncePod'
|
||||||
|
} = ACCESS_MODE || {};
|
||||||
|
const ACCESS_MODE_OPTIONS = [READ_WRITE_ONCE, READ_WRITE_MANY, READ_ONLY_MANY, READ_WRITE_ONCE_POD];
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
storageClassName: { type: String, default: '' },
|
||||||
|
providerName: { type: String, default: '' },
|
||||||
|
showInherited: { type: Boolean, default: false },
|
||||||
|
volumeMode: { type: String, default: VOLUME_MODE.FILE_SYSTEM },
|
||||||
|
accessModes: { type: Array, default: () => ['ReadWriteMany'] },
|
||||||
|
inheritedVolumeMode: { type: String, default: VOLUME_MODE.FILE_SYSTEM },
|
||||||
|
inheritedAccessModes: { type: Array, default: () => ['ReadWriteMany'] },
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['apply', 'close']);
|
||||||
|
|
||||||
|
const store = useStore();
|
||||||
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const localVolumeMode = ref(props.volumeMode || VOLUME_MODE.FILE_SYSTEM);
|
||||||
|
const localAccessMode = ref(props.accessModes?.[0] || READ_WRITE_MANY);
|
||||||
|
|
||||||
|
const volumeModeOptions = VOLUME_MODE_OPTIONS.map((value) => ({ label: value, value }));
|
||||||
|
const accessModeOptions = ACCESS_MODE_OPTIONS.map((value) => ({ label: value, value }));
|
||||||
|
|
||||||
|
const apply = () => {
|
||||||
|
emit('apply', {
|
||||||
|
volumeMode: localVolumeMode.value,
|
||||||
|
accessModes: [localAccessMode.value],
|
||||||
|
});
|
||||||
|
emit('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Repopulate the dropdowns with the provider default values; the user then applies.
|
||||||
|
const reset = () => {
|
||||||
|
localVolumeMode.value = props.inheritedVolumeMode || VOLUME_MODE.FILE_SYSTEM;
|
||||||
|
localAccessMode.value = props.inheritedAccessModes?.[0] || READ_WRITE_MANY;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
emit('close');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<app-modal
|
||||||
|
class="storage-defaults-modal"
|
||||||
|
name="storageDefaultsDialog"
|
||||||
|
:width="620"
|
||||||
|
height="auto"
|
||||||
|
:click-to-close="false"
|
||||||
|
@close="cancel"
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
class="storage-defaults-card"
|
||||||
|
:show-highlight-border="false"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<h4 class="text-default-text">
|
||||||
|
{{ t('harvester.addons.vmMigration.storageDefaults.title', { name: storageClassName }) }}
|
||||||
|
</h4>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<p class="text-deemphasized description">
|
||||||
|
<template v-if="showInherited">
|
||||||
|
{{ t('harvester.addons.vmMigration.storageDefaults.descriptionInherited', { name: storageClassName, provider: providerName }) }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ t('harvester.addons.vmMigration.storageDefaults.description', { name: storageClassName }) }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="settings-box">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="localVolumeMode"
|
||||||
|
class="volume-mode"
|
||||||
|
:label="t('harvester.addons.vmMigration.storageDefaults.volumeMode')"
|
||||||
|
:options="volumeModeOptions"
|
||||||
|
:searchable="false"
|
||||||
|
:clearable="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="localAccessMode"
|
||||||
|
class="access-mode"
|
||||||
|
:label="t('harvester.addons.vmMigration.storageDefaults.accessMode')"
|
||||||
|
:options="accessModeOptions"
|
||||||
|
:searchable="false"
|
||||||
|
:clearable="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.vmMigration.storageDefaults.cancel') }}
|
||||||
|
</button>
|
||||||
|
<div class="right-buttons">
|
||||||
|
<button
|
||||||
|
v-if="showInherited"
|
||||||
|
class="btn role-secondary mr-10"
|
||||||
|
@click="reset"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.vmMigration.storageDefaults.resetToProviderDefault') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn role-primary"
|
||||||
|
:disabled="!localAccessMode"
|
||||||
|
@click="apply"
|
||||||
|
>
|
||||||
|
{{ showInherited ? t('harvester.addons.vmMigration.storageDefaults.applyOverride') : t('harvester.addons.vmMigration.storageDefaults.apply') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</app-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.storage-defaults-modal {
|
||||||
|
z-index: 45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.storage-defaults-card {
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--category-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.volume-mode {
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.access-mode {
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.right-buttons {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -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({
|
||||||
|
|||||||
@@ -86,4 +86,5 @@ export const HCI = {
|
|||||||
CDI_POPULATOR_KIND: 'cdi.kubevirt.io/storage.populator.kind',
|
CDI_POPULATOR_KIND: 'cdi.kubevirt.io/storage.populator.kind',
|
||||||
CNI_NETWORKS: 'k8s.v1.cni.cncf.io/networks',
|
CNI_NETWORKS: 'k8s.v1.cni.cncf.io/networks',
|
||||||
WINDOWS_SYSPREP: 'harvesterhci.io/windows-sysprep',
|
WINDOWS_SYSPREP: 'harvesterhci.io/windows-sysprep',
|
||||||
|
SKIP_MIG_CONFIGURATION: 'harvesterhci.io/skip-mig-configuration',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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' },
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ export const FORKLIFT_PLAN_VM_COUNT = {
|
|||||||
labelKey: 'harvester.tableHeaders.vmMigrationPlanVmCount',
|
labelKey: 'harvester.tableHeaders.vmMigrationPlanVmCount',
|
||||||
value: 'spec.vms.length',
|
value: 'spec.vms.length',
|
||||||
sort: 'spec.vms.length',
|
sort: 'spec.vms.length',
|
||||||
align: 'left',
|
align: 'center',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Plan reference column in forklift.konveyor.io.migration list page
|
// Plan reference column in forklift.konveyor.io.migration list page
|
||||||
|
|||||||
@@ -390,14 +390,17 @@ export default {
|
|||||||
:vmi-resource="vmi"
|
:vmi-resource="vmi"
|
||||||
:vmim-resource="vmim"
|
:vmim-resource="vmim"
|
||||||
/>
|
/>
|
||||||
<DashboardMetrics
|
<div
|
||||||
v-if="showVmMetrics && liveMigrationProgressEnabled"
|
v-if="showVmMetrics && liveMigrationProgressEnabled"
|
||||||
:detail-url="VM_MIGRATION_DETAIL_URL"
|
class="migration-metrics-scroll mb-30"
|
||||||
graph-height="640px"
|
>
|
||||||
:has-summary-and-detail="false"
|
<DashboardMetrics
|
||||||
:vars="graphVars"
|
:detail-url="VM_MIGRATION_DETAIL_URL"
|
||||||
class="mb-30"
|
graph-height="960px"
|
||||||
/>
|
:has-summary-and-detail="false"
|
||||||
|
:vars="graphVars"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Events
|
<Events
|
||||||
v-if="liveMigrationProgressEnabled"
|
v-if="liveMigrationProgressEnabled"
|
||||||
:resource="vmi"
|
:resource="vmi"
|
||||||
@@ -436,3 +439,10 @@ export default {
|
|||||||
</Tabbed>
|
</Tabbed>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.migration-metrics-scroll {
|
||||||
|
max-height: 640px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { Card } from '@components/Card';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
|
import { escapeHtml } from '@shell/utils/string';
|
||||||
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HarvesterEnableSriovGpuDevice',
|
||||||
|
|
||||||
|
emits: ['close'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
AsyncButton,
|
||||||
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
resources: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return { skipMigConfiguration: this.resources[0]?.metadata?.annotations?.[HCI_ANNOTATIONS.SKIP_MIG_CONFIGURATION] === 'true' };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: { ...mapGetters({ t: 'i18n/t' }) },
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
async save(buttonCb) {
|
||||||
|
const actionResource = this.resources[0];
|
||||||
|
const prevEnabled = actionResource.spec.enabled;
|
||||||
|
const prevAnnotation = actionResource.metadata.annotations?.[HCI_ANNOTATIONS.SKIP_MIG_CONFIGURATION];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.skipMigConfiguration) {
|
||||||
|
if (!actionResource.metadata.annotations) {
|
||||||
|
actionResource.metadata.annotations = {};
|
||||||
|
}
|
||||||
|
actionResource.metadata.annotations[HCI_ANNOTATIONS.SKIP_MIG_CONFIGURATION] = 'true';
|
||||||
|
} else {
|
||||||
|
if (actionResource.metadata.annotations) {
|
||||||
|
delete actionResource.metadata.annotations[HCI_ANNOTATIONS.SKIP_MIG_CONFIGURATION];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionResource.spec.enabled = true;
|
||||||
|
await actionResource.save();
|
||||||
|
buttonCb(true);
|
||||||
|
this.close();
|
||||||
|
} catch (err) {
|
||||||
|
actionResource.spec.enabled = prevEnabled;
|
||||||
|
if (prevAnnotation !== undefined) {
|
||||||
|
actionResource.metadata.annotations[HCI_ANNOTATIONS.SKIP_MIG_CONFIGURATION] = prevAnnotation;
|
||||||
|
} else if (actionResource.metadata.annotations) {
|
||||||
|
delete actionResource.metadata.annotations[HCI_ANNOTATIONS.SKIP_MIG_CONFIGURATION];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store.dispatch('growl/fromError', {
|
||||||
|
title: this.t('generic.notification.title.error', { name: escapeHtml(actionResource.metadata.name) }),
|
||||||
|
err,
|
||||||
|
}, { root: true });
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card :show-highlight-border="false">
|
||||||
|
<template #title>
|
||||||
|
<h4
|
||||||
|
v-clean-html="t('harvester.sriovgpu.enable.title')"
|
||||||
|
class="text-default-text"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<div class="body">
|
||||||
|
<Checkbox
|
||||||
|
v-model:value="skipMigConfiguration"
|
||||||
|
:label="t('harvester.sriovgpu.enable.skipMigConfiguration')"
|
||||||
|
/>
|
||||||
|
<p class="note mt-10">
|
||||||
|
{{ t('harvester.sriovgpu.enable.note') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<div class="buttons actions">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary mr-10"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<AsyncButton
|
||||||
|
mode="enable"
|
||||||
|
@click="save"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,13 +2,16 @@
|
|||||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
import { sortBy } from '@shell/utils/sort';
|
import { sortBy } from '@shell/utils/sort';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { PVC } from '@shell/config/types';
|
import { PVC, STORAGE_CLASS, LONGHORN_DRIVER } from '@shell/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 { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
|
import { HCI } from '@pkg/harvester/types';
|
||||||
import { Card } from '@components/Card';
|
import { Card } from '@components/Card';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import AsyncButton from '@shell/components/AsyncButton';
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
import { 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 { Checkbox } from '@components/Form/Checkbox';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HotplugVolumeModal',
|
name: 'HotplugVolumeModal',
|
||||||
@@ -16,7 +19,7 @@ export default {
|
|||||||
emits: ['close'],
|
emits: ['close'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
AsyncButton, Card, LabeledInput, LabeledSelect, Banner
|
AsyncButton, Card, LabeledInput, LabeledSelect, Banner, Checkbox
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -28,14 +31,19 @@ export default {
|
|||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
this.allPVCs = await this.$store.dispatch('harvester/findAll', { type: PVC });
|
this.allPVCs = await this.$store.dispatch('harvester/findAll', { type: PVC });
|
||||||
|
this.allStorageClasses = await this.$store.dispatch('harvester/findAll', { type: STORAGE_CLASS });
|
||||||
|
this.allVMs = await this.$store.dispatch('harvester/findAll', { type: HCI.VM });
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
diskName: '',
|
diskName: '',
|
||||||
volumeName: '',
|
volumeName: '',
|
||||||
errors: [],
|
shareable: false,
|
||||||
allPVCs: [],
|
errors: [],
|
||||||
|
allPVCs: [],
|
||||||
|
allStorageClasses: [],
|
||||||
|
allVMs: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -50,6 +58,31 @@ export default {
|
|||||||
return this.resources[0];
|
return this.resources[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// claim names already attached to the VM this modal was opened for
|
||||||
|
currentVMClaimNames() {
|
||||||
|
const volumes = this.actionResource?.spec?.template?.spec?.volumes || [];
|
||||||
|
|
||||||
|
return volumes.map((vol) => vol.persistentVolumeClaim?.claimName).filter((name) => !!name);
|
||||||
|
},
|
||||||
|
|
||||||
|
// claim names attached to other VMs in the same namespace
|
||||||
|
otherVMClaimNames() {
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
this.allVMs.forEach((vm) => {
|
||||||
|
if (vm.metadata.namespace !== this.actionResource?.metadata?.namespace || vm.id === this.actionResource?.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
(vm.spec?.template?.spec?.volumes || []).forEach((vol) => {
|
||||||
|
if (vol.persistentVolumeClaim?.claimName) {
|
||||||
|
out.push(vol.persistentVolumeClaim.claimName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return out;
|
||||||
|
},
|
||||||
|
|
||||||
volumeOption() {
|
volumeOption() {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
this.PVCs
|
this.PVCs
|
||||||
@@ -61,6 +94,14 @@ export default {
|
|||||||
if (pvc.isGoldenImageVolume) {
|
if (pvc.isGoldenImageVolume) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// a volume attached to this VM can never be attached to it again
|
||||||
|
if (this.currentVMClaimNames.includes(pvc.metadata.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// a volume attached to another VM can only be re-attached as a shareable disk
|
||||||
|
if (this.otherVMClaimNames.includes(pvc.metadata.name) && !this.isShareableCapablePVC(pvc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
@@ -73,19 +114,62 @@ export default {
|
|||||||
'label'
|
'label'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedPVC() {
|
||||||
|
return this.PVCs.find((P) => P.metadata.name === this.volumeName);
|
||||||
|
},
|
||||||
|
|
||||||
|
isShareableCapable() {
|
||||||
|
return this.selectedPVC ? this.isShareableCapablePVC(this.selectedPVC) : false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
isShareableCapable(neu) {
|
||||||
|
if (!neu) {
|
||||||
|
this.shareable = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
isShareableCapablePVC(pvc) {
|
||||||
|
const pvcSpec = pvc?.spec;
|
||||||
|
|
||||||
|
if (!pvcSpec) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRWX = (pvcSpec.accessModes || []).includes('ReadWriteMany');
|
||||||
|
const isBlock = pvcSpec.volumeMode === VOLUME_MODE.BLOCK;
|
||||||
|
const storageClass = this.allStorageClasses.find((sc) => sc.name === pvcSpec.storageClassName);
|
||||||
|
|
||||||
|
// fail closed: without a resolved StorageClass the provisioner
|
||||||
|
// requirement cannot be evaluated
|
||||||
|
if (!storageClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isRWX && isBlock && storageClass.provisioner !== LONGHORN_DRIVER;
|
||||||
|
},
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
this.diskName = '';
|
this.diskName = '';
|
||||||
this.volumeName = '';
|
this.volumeName = '';
|
||||||
|
this.shareable = false;
|
||||||
this.$emit('close');
|
this.$emit('close');
|
||||||
},
|
},
|
||||||
|
|
||||||
async save(buttonCb) {
|
async save(buttonCb) {
|
||||||
if (this.actionResource) {
|
if (this.actionResource) {
|
||||||
try {
|
try {
|
||||||
const res = await this.actionResource.doAction('addVolume', { volumeSourceName: this.volumeName, diskName: this.diskName }, {}, false);
|
const input = { volumeSourceName: this.volumeName, diskName: this.diskName };
|
||||||
|
|
||||||
|
if (this.isShareableCapable && this.shareable) {
|
||||||
|
input.shareable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.actionResource.doAction('addVolume', input, {}, false);
|
||||||
|
|
||||||
if (res._status === 200 || res._status === 204) {
|
if (res._status === 200 || res._status === 204) {
|
||||||
this.$store.dispatch('growl/success', {
|
this.$store.dispatch('growl/success', {
|
||||||
@@ -141,6 +225,19 @@ export default {
|
|||||||
class="mt-20"
|
class="mt-20"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
v-if="isShareableCapable"
|
||||||
|
v-model:value="shareable"
|
||||||
|
class="mt-20"
|
||||||
|
type="checkbox"
|
||||||
|
label-key="harvester.virtualMachine.volume.shareable.label"
|
||||||
|
tooltip-key="harvester.virtualMachine.volume.shareable.tip"
|
||||||
|
/>
|
||||||
|
<Banner
|
||||||
|
v-if="isShareableCapable && shareable"
|
||||||
|
color="warning"
|
||||||
|
:label="t('harvester.virtualMachine.volume.shareable.warning')"
|
||||||
|
/>
|
||||||
<Banner
|
<Banner
|
||||||
v-for="(err, i) in errors"
|
v-for="(err, i) in errors"
|
||||||
:key="i"
|
:key="i"
|
||||||
|
|||||||
@@ -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)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import Loading from '@shell/components/Loading';
|
|||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
import { RadioGroup } from '@components/Form/Radio';
|
import { RadioGroup } from '@components/Form/Radio';
|
||||||
import { NETWORK_PROTOCOL, NETWORK_TYPE } from '@pkg/harvester/config/types';
|
import { NETWORK_PROTOCOL, NETWORK_TYPE } from '@pkg/harvester/config/types';
|
||||||
import { set } from '@shell/utils/object';
|
import { set, remove } from '@shell/utils/object';
|
||||||
import ArrayList from '@shell/components/form/ArrayList';
|
import ArrayList from '@shell/components/form/ArrayList';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
@@ -142,10 +142,12 @@ export default {
|
|||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const vlans = this.$store.getters[`${ inStore }/all`](HCI.VLAN) || [];
|
const vlans = this.$store.getters[`${ inStore }/all`](HCI.VLAN) || [];
|
||||||
|
|
||||||
return vlans.map((vlan) => ({
|
const options = vlans.map((vlan) => ({
|
||||||
label: vlan.id,
|
label: vlan.id,
|
||||||
value: vlan.id,
|
value: vlan.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
return [{ label: this.t('generic.none'), value: '' }, ...options];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -160,6 +162,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onVlanChange(value) {
|
||||||
|
if (value === '') {
|
||||||
|
remove(this.value.spec, 'vlan');
|
||||||
|
} else {
|
||||||
|
set(this.value, 'spec.vlan', value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async saveSubnet(buttonCb) {
|
async saveSubnet(buttonCb) {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
const name = this.value?.metadata?.name;
|
const name = this.value?.metadata?.name;
|
||||||
@@ -283,12 +293,13 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
v-model:value="value.spec.vlan"
|
:value="value.spec.vlan ?? ''"
|
||||||
class="mb-20"
|
class="mb-20"
|
||||||
:options="vlanOptions"
|
:options="vlanOptions"
|
||||||
:placeholder="t('harvester.subnet.vlan.placeholder')"
|
:placeholder="t('harvester.subnet.vlan.placeholder')"
|
||||||
:label="t('harvester.subnet.vlan.label')"
|
:label="t('harvester.subnet.vlan.label')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
@update:value="onVlanChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
-6
@@ -68,12 +68,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
osType(neu) {
|
|
||||||
if (neu === 'windows') {
|
|
||||||
this.id = '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
value(neu) {
|
value(neu) {
|
||||||
this.yamlScript = neu;
|
this.yamlScript = neu;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-2
@@ -5,10 +5,12 @@ import LabelValue from '@shell/components/LabelValue';
|
|||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
import InputOrDisplay from '@shell/components/InputOrDisplay';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
import { sortBy } from '@shell/utils/sort';
|
import { sortBy } from '@shell/utils/sort';
|
||||||
import { PVC } from '@shell/config/types';
|
import { PVC, STORAGE_CLASS, LONGHORN_DRIVER } from '@shell/config/types';
|
||||||
import { _CREATE } from '@shell/config/query-params';
|
import { _CREATE } from '@shell/config/query-params';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
import { HCI } from '../../../../types';
|
import { HCI } from '../../../../types';
|
||||||
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
|
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
|
||||||
import { GIBIBYTE } from '../../../../utils/unit';
|
import { GIBIBYTE } from '../../../../utils/unit';
|
||||||
@@ -19,7 +21,7 @@ export default {
|
|||||||
emits: ['update'],
|
emits: ['update'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
UnitInput, LabeledInput, LabeledSelect, InputOrDisplay, LabelValue, Banner
|
UnitInput, LabeledInput, LabeledSelect, InputOrDisplay, LabelValue, Banner, Checkbox
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -94,6 +96,30 @@ export default {
|
|||||||
return this.allPVCs.find( (P) => P.metadata.name === this.value.volumeName );
|
return this.allPVCs.find( (P) => P.metadata.name === this.value.volumeName );
|
||||||
},
|
},
|
||||||
|
|
||||||
|
storageClasses() {
|
||||||
|
return this.$store.getters['harvester/all'](STORAGE_CLASS) || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
isShareableCapable() {
|
||||||
|
const pvcSpec = this.pvcResource?.spec;
|
||||||
|
|
||||||
|
if (!pvcSpec) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRWX = (pvcSpec.accessModes || []).includes('ReadWriteMany');
|
||||||
|
const isBlock = pvcSpec.volumeMode === VOLUME_MODE.BLOCK;
|
||||||
|
const storageClass = this.storageClasses.find((sc) => sc.name === pvcSpec.storageClassName);
|
||||||
|
|
||||||
|
// fail closed: without a resolved StorageClass the provisioner
|
||||||
|
// requirement cannot be evaluated
|
||||||
|
if (!storageClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isRWX && isBlock && storageClass.provisioner !== LONGHORN_DRIVER;
|
||||||
|
},
|
||||||
|
|
||||||
volumeOption() {
|
volumeOption() {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
this.allPVCs
|
this.allPVCs
|
||||||
@@ -145,6 +171,7 @@ export default {
|
|||||||
this.value.size = pvcResource.spec.resources.requests.storage;
|
this.value.size = pvcResource.spec.resources.requests.storage;
|
||||||
this.value.storageClassName = pvcResource.spec.storageClassName;
|
this.value.storageClassName = pvcResource.spec.storageClassName;
|
||||||
this.value.volumeMode = pvcResource.spec.volumeMode;
|
this.value.volumeMode = pvcResource.spec.volumeMode;
|
||||||
|
this.value.shareable = false;
|
||||||
this.update();
|
this.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -155,6 +182,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isShareableCapable(neu) {
|
||||||
|
if (!neu && this.value.shareable) {
|
||||||
|
this.value.shareable = false;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
pvcResource: {
|
pvcResource: {
|
||||||
handler(pvc) {
|
handler(pvc) {
|
||||||
if (!this.value.volumeName && pvc?.metadata?.name) {
|
if (!this.value.volumeName && pvc?.metadata?.name) {
|
||||||
@@ -303,6 +337,26 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</InputOrDisplay>
|
</InputOrDisplay>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isShareableCapable"
|
||||||
|
data-testid="input-hee-shareable"
|
||||||
|
class="col span-6"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
v-model:value="value.shareable"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
label-key="harvester.virtualMachine.volume.shareable.label"
|
||||||
|
tooltip-key="harvester.virtualMachine.volume.shareable.tip"
|
||||||
|
:mode="mode"
|
||||||
|
@update:value="update"
|
||||||
|
/>
|
||||||
|
<Banner
|
||||||
|
v-if="value.shareable"
|
||||||
|
color="warning"
|
||||||
|
:label="t('harvester.virtualMachine.volume.shareable.warning')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="value.volumeBackups"
|
v-if="value.volumeBackups"
|
||||||
class="col span-6"
|
class="col span-6"
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ export default {
|
|||||||
|
|
||||||
const hostname = this.value.spec.template.spec.hostname || '';
|
const hostname = this.value.spec.template.spec.hostname || '';
|
||||||
|
|
||||||
|
// Display name can contain arbitrary strings. There is no XSS risk because the value is
|
||||||
|
// rendered via Vue's {{ }} interpolation which auto-escapes HTML; v-html is never used for
|
||||||
|
// this field. See harvester/harvester#10423 for details.
|
||||||
const customizeDisplayName = !!(this.value.metadata?.annotations?.[HCI_ANNOTATIONS.VM_DISPLAY_NAME]);
|
const customizeDisplayName = !!(this.value.metadata?.annotations?.[HCI_ANNOTATIONS.VM_DISPLAY_NAME]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -484,6 +487,20 @@ export default {
|
|||||||
await value.save();
|
await value.save();
|
||||||
await this.applyHooks(AFTER_SAVE_HOOKS);
|
await this.applyHooks(AFTER_SAVE_HOOKS);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// Reset the generated secret name(s) so a retried create (e.g. after correcting an
|
||||||
|
// invalid/duplicate VM name) always regenerates them from the current name instead of
|
||||||
|
// reusing names derived from this failed attempt. Only do this for create: in edit mode
|
||||||
|
// secretName/sysprep.secretName may reference an existing secret loaded from the VM,
|
||||||
|
// which must not be discarded. See harvester/harvester#11174.
|
||||||
|
if (this.isCreate) {
|
||||||
|
this.secretName = '';
|
||||||
|
this.secretNamePrefixUsed = '';
|
||||||
|
if (this.sysprepSecretNamePrefixUsed !== '') {
|
||||||
|
this.sysprep.secretName = '';
|
||||||
|
this.sysprepSecretNamePrefixUsed = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.errors.push(...exceptionToErrorsArray(e));
|
this.errors.push(...exceptionToErrorsArray(e));
|
||||||
buttonCb(false);
|
buttonCb(false);
|
||||||
}
|
}
|
||||||
@@ -1045,7 +1062,6 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CloudConfig
|
<CloudConfig
|
||||||
v-if="!isWindows"
|
|
||||||
ref="yamlEditor"
|
ref="yamlEditor"
|
||||||
:user-script="userScript"
|
:user-script="userScript"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export default {
|
|||||||
RANCHER_TYPES,
|
RANCHER_TYPES,
|
||||||
fvFormRuleSets: [{ path: 'spec.displayName', rules: ['required'] }],
|
fvFormRuleSets: [{ path: 'spec.displayName', rules: ['required'] }],
|
||||||
canEditPSPBindings: true,
|
canEditPSPBindings: true,
|
||||||
|
isQuotasValid: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -190,15 +191,13 @@ export default {
|
|||||||
this['membershipUpdate'] = update;
|
this['membershipUpdate'] = update;
|
||||||
},
|
},
|
||||||
|
|
||||||
removeQuota(key) {
|
validateQuotas(isValid) {
|
||||||
['resourceQuota', 'namespaceDefaultResourceQuota'].forEach((specProp) => {
|
this.isQuotasValid = isValid;
|
||||||
if (this.value?.spec[specProp]?.limit && this.value?.spec[specProp]?.limit[key]) {
|
},
|
||||||
delete this.value?.spec[specProp]?.limit[key];
|
|
||||||
}
|
onQuotasInput({ projectLimit, nsLimit } = {}) {
|
||||||
if (this.value?.spec[specProp]?.usedLimit && this.value?.spec[specProp]?.usedLimit[key]) {
|
this.value.spec.resourceQuota = { ...this.value.spec.resourceQuota, limit: projectLimit };
|
||||||
delete this.value?.spec[specProp]?.usedLimit[key];
|
this.value.spec.namespaceDefaultResourceQuota = { ...this.value.spec.namespaceDefaultResourceQuota, limit: nsLimit };
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -212,7 +211,7 @@ export default {
|
|||||||
:resource="value"
|
:resource="value"
|
||||||
:subtypes="[]"
|
:subtypes="[]"
|
||||||
:can-yaml="false"
|
:can-yaml="false"
|
||||||
:validation-passed="fvFormIsValid"
|
:validation-passed="fvFormIsValid && isQuotasValid"
|
||||||
@error="e=>errors=e"
|
@error="e=>errors=e"
|
||||||
@finish="save"
|
@finish="save"
|
||||||
@cancel="done"
|
@cancel="done"
|
||||||
@@ -267,10 +266,11 @@ export default {
|
|||||||
:weight="9"
|
:weight="9"
|
||||||
>
|
>
|
||||||
<ResourceQuota
|
<ResourceQuota
|
||||||
v-model:value="value"
|
:value="value"
|
||||||
:mode="canEditTabElements"
|
:mode="canEditTabElements"
|
||||||
:types="isStandaloneHarvester ? HARVESTER_TYPES : RANCHER_TYPES"
|
:types="isStandaloneHarvester ? HARVESTER_TYPES : RANCHER_TYPES"
|
||||||
@remove="removeQuota"
|
@input="onQuotasInput"
|
||||||
|
@validationChanged="validateQuotas"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
|
import PercentageBar from '@shell/components/PercentageBar';
|
||||||
import { METRIC, NODE } from '@shell/config/types';
|
import { METRIC, NODE } from '@shell/config/types';
|
||||||
import { parseSi } from '@shell/utils/units';
|
import { parseSi } from '@shell/utils/units';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterCpuUsed',
|
name: 'HarvesterCpuUsed',
|
||||||
components: { ConsumptionGauge },
|
components: { PercentageBar },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
@@ -74,54 +74,89 @@ export default {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
|
||||||
|
reservedPercentage() {
|
||||||
|
return this.cpuTotal ? (this.reserved * 100) / this.cpuTotal : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
usedPercentage() {
|
||||||
|
return this.cpuTotal ? (this.used * 100) / this.cpuTotal : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
reservedAmountTemplateValues() {
|
||||||
|
return {
|
||||||
|
used: this.numberFormatter(this.reserved || 0),
|
||||||
|
total: this.numberFormatter(this.cpuTotal || 0),
|
||||||
|
unit: ` ${ this.cpuUnits }`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
usedAmountTemplateValues() {
|
||||||
|
return {
|
||||||
|
used: this.numberFormatter(this.used || 0),
|
||||||
|
total: this.numberFormatter(this.cpuTotal || 0),
|
||||||
|
unit: ` ${ this.cpuUnits }`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
numberFormatter(value) {
|
||||||
|
return Number.isInteger(value) ? value : value.toFixed(2);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ConsumptionGauge
|
<div class="consumption-gauge">
|
||||||
:capacity="cpuTotal"
|
<div class="numbers">
|
||||||
:used="reserved"
|
<span class="mr-5">
|
||||||
:units="cpuUnits"
|
|
||||||
:resource-name="resourceName"
|
|
||||||
>
|
|
||||||
<template #title="{amountTemplateValues, formattedPercentage}">
|
|
||||||
<span>
|
|
||||||
{{ t('harvester.formatters.hardwareResourceGauge.reserved') }}
|
{{ t('harvester.formatters.hardwareResourceGauge.reserved') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="precent-data">
|
<span class="precent-data">
|
||||||
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }}
|
{{ t('node.detail.glance.consumptionGauge.amount', reservedAmountTemplateValues) }}
|
||||||
<span class="ml-10 percentage">/ {{ formattedPercentage }}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</div>
|
||||||
</ConsumptionGauge>
|
<div class="mt-10">
|
||||||
|
<PercentageBar
|
||||||
|
:model-value="reservedPercentage"
|
||||||
|
show-percentage
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="showUsed"
|
v-if="showUsed"
|
||||||
class="mt-10"
|
class="consumption-gauge mt-10"
|
||||||
>
|
>
|
||||||
<ConsumptionGauge
|
<div class="numbers">
|
||||||
:capacity="cpuTotal"
|
<span>
|
||||||
:used="used"
|
{{ t('harvester.formatters.hardwareResourceGauge.used') }}
|
||||||
:units="cpuUnits"
|
</span>
|
||||||
>
|
<span class="precent-data">
|
||||||
<template #title="{amountTemplateValues, formattedPercentage}">
|
{{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }}
|
||||||
<span>
|
</span>
|
||||||
{{ t('harvester.formatters.hardwareResourceGauge.used') }}
|
</div>
|
||||||
</span>
|
<div class="mt-10">
|
||||||
<span class="precent-data">
|
<PercentageBar
|
||||||
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }}
|
:model-value="usedPercentage"
|
||||||
<span class="ml-10 percentage">/ {{ formattedPercentage }}
|
show-percentage
|
||||||
</span>
|
/>
|
||||||
</span>
|
</div>
|
||||||
</template>
|
|
||||||
</ConsumptionGauge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.consumption-gauge {
|
||||||
|
.numbers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.precent-data {
|
.precent-data {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,16 +132,28 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="showIP">
|
<div
|
||||||
<span
|
v-if="showIP"
|
||||||
|
class="ip-list"
|
||||||
|
>
|
||||||
|
<div
|
||||||
v-for="{ ip, name, isCustom } in ips"
|
v-for="{ ip, name, isCustom } in ips"
|
||||||
:key="`${ip}-${name}`"
|
:key="`${ip}-${name}`"
|
||||||
|
class="ip-item"
|
||||||
>
|
>
|
||||||
<CopyToClipboardText
|
<CopyToClipboardText
|
||||||
v-clean-tooltip="isCustom ? t('harvester.formatters.harvesterIpAddress.customIpTooltip') : name"
|
v-clean-tooltip="isCustom ? t('harvester.formatters.harvesterIpAddress.customIpTooltip') : name"
|
||||||
:text="ip"
|
:text="ip"
|
||||||
:plain="isCustom"
|
:plain="isCustom"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.ip-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
|
import PercentageBar from '@shell/components/PercentageBar';
|
||||||
import { METRIC, NODE } from '@shell/config/types';
|
import { METRIC, NODE } from '@shell/config/types';
|
||||||
import { formatSi, exponentNeeded, UNITS, parseSi } from '@shell/utils/units';
|
import { formatSi, exponentNeeded, UNITS, parseSi } from '@shell/utils/units';
|
||||||
import { UNIT_SUFFIX } from '../utils/unit';
|
import { UNIT_SUFFIX } from '../utils/unit';
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterMemoryUsed',
|
name: 'HarvesterMemoryUsed',
|
||||||
components: { ConsumptionGauge },
|
components: { PercentageBar },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
@@ -76,6 +76,30 @@ export default {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reservedPercentage() {
|
||||||
|
return this.memoryTotal ? (this.reserved * 100) / this.memoryTotal : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
usedPercentage() {
|
||||||
|
return this.memoryTotal ? (this.used * 100) / this.memoryTotal : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
reservedAmountTemplateValues() {
|
||||||
|
return {
|
||||||
|
used: this.memoryFormatter(this.reserved || 0),
|
||||||
|
total: this.memoryFormatter(this.memoryTotal || 0),
|
||||||
|
unit: ` ${ this.memoryUnits }`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
usedAmountTemplateValues() {
|
||||||
|
return {
|
||||||
|
used: this.memoryFormatter(this.used || 0),
|
||||||
|
total: this.memoryFormatter(this.memoryTotal || 0),
|
||||||
|
unit: ` ${ this.memoryUnits }`,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -96,50 +120,53 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ConsumptionGauge
|
<div class="consumption-gauge">
|
||||||
:capacity="memoryTotal"
|
<div class="numbers">
|
||||||
:used="reserved"
|
<span class="mr-10">
|
||||||
:units="memoryUnits"
|
|
||||||
:number-formatter="memoryFormatter"
|
|
||||||
:resource-name="resourceName"
|
|
||||||
>
|
|
||||||
<template #title="{amountTemplateValues, formattedPercentage}">
|
|
||||||
<span>
|
|
||||||
{{ t('harvester.formatters.hardwareResourceGauge.reserved') }}
|
{{ t('harvester.formatters.hardwareResourceGauge.reserved') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="precent-data">
|
<span class="precent-data">
|
||||||
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }}
|
{{ t('node.detail.glance.consumptionGauge.amount', reservedAmountTemplateValues) }}
|
||||||
<span class="ml-10 percentage">/ {{ formattedPercentage }}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</div>
|
||||||
</ConsumptionGauge>
|
<div class="mt-10">
|
||||||
|
<PercentageBar
|
||||||
|
:model-value="reservedPercentage"
|
||||||
|
show-percentage
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="showUsed"
|
v-if="showUsed"
|
||||||
class="mt-10"
|
class="consumption-gauge mt-10"
|
||||||
>
|
>
|
||||||
<ConsumptionGauge
|
<div class="numbers">
|
||||||
:capacity="memoryTotal"
|
<span>
|
||||||
:used="used"
|
{{ t('harvester.formatters.hardwareResourceGauge.used') }}
|
||||||
:units="memoryUnits"
|
</span>
|
||||||
:number-formatter="memoryFormatter"
|
<span class="precent-data">
|
||||||
>
|
{{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }}
|
||||||
<template #title="{amountTemplateValues, formattedPercentage}">
|
</span>
|
||||||
<span>
|
</div>
|
||||||
{{ t('harvester.formatters.hardwareResourceGauge.used') }}
|
<div class="mt-10">
|
||||||
</span>
|
<PercentageBar
|
||||||
<span class="precent-data">
|
:model-value="usedPercentage"
|
||||||
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }}
|
show-percentage
|
||||||
<span class="ml-10 percentage">/ {{ formattedPercentage }}
|
/>
|
||||||
</span>
|
</div>
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</ConsumptionGauge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.consumption-gauge {
|
||||||
|
.numbers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.precent-data {
|
.precent-data {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
|
import PercentageBar from '@shell/components/PercentageBar';
|
||||||
import { LONGHORN } from '@shell/config/types';
|
import { LONGHORN } from '@shell/config/types';
|
||||||
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
|
||||||
import { UNIT_SUFFIX } from '../utils/unit';
|
import { UNIT_SUFFIX } from '../utils/unit';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterStorageUsed',
|
name: 'HarvesterStorageUsed',
|
||||||
components: { ConsumptionGauge },
|
components: { PercentageBar },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
@@ -120,6 +120,14 @@ export default {
|
|||||||
unit: this.allocatedUnits,
|
unit: this.allocatedUnits,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
usedPercentage() {
|
||||||
|
return this.storageStats.maximum ? (this.storageStats.used * 100) / this.storageStats.maximum : 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
allocatedPercentage() {
|
||||||
|
return this.storageStats.total ? (this.storageStats.scheduled * 100) / this.storageStats.total : 0;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -144,53 +152,54 @@ export default {
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
v-if="showAllocated"
|
v-if="showAllocated"
|
||||||
|
class="consumption-gauge"
|
||||||
>
|
>
|
||||||
<ConsumptionGauge
|
<div class="numbers">
|
||||||
:capacity="storageStats.total"
|
<span class="mr-10">
|
||||||
:used="storageStats.scheduled"
|
{{ t('harvester.dashboard.hardwareResourceGauge.allocated') }}
|
||||||
:units="allocatedUnits"
|
</span>
|
||||||
:number-formatter="formatter"
|
<span class="precent-data">
|
||||||
:resource-name="resourceName"
|
{{ t('node.detail.glance.consumptionGauge.amount', allocatedAmountTemplateValues) }}
|
||||||
>
|
</span>
|
||||||
<template #title="{formattedPercentage}">
|
</div>
|
||||||
<span>
|
<div class="mt-10">
|
||||||
{{ t('harvester.dashboard.hardwareResourceGauge.allocated') }}
|
<PercentageBar
|
||||||
</span>
|
:model-value="allocatedPercentage"
|
||||||
<span class="precent-data">
|
show-percentage
|
||||||
{{ t('node.detail.glance.consumptionGauge.amount', allocatedAmountTemplateValues) }}
|
/>
|
||||||
<span class="ml-10 percentage">
|
</div>
|
||||||
/ {{ formattedPercentage }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</ConsumptionGauge>
|
|
||||||
</div>
|
</div>
|
||||||
<ConsumptionGauge
|
<div
|
||||||
:capacity="storageStats.maximum"
|
class="consumption-gauge"
|
||||||
:used="storageStats.used"
|
:class="{ 'mt-10': showAllocated }"
|
||||||
:units="usedUnits"
|
|
||||||
:number-formatter="formatter"
|
|
||||||
:resource-name="showAllocated ? '' : resourceName"
|
|
||||||
:class="{
|
|
||||||
'mt-10': showAllocated,
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<template #title="{formattedPercentage}">
|
<div class="numbers">
|
||||||
<span>
|
<span>
|
||||||
{{ t('node.detail.glance.consumptionGauge.used') }}
|
{{ t('node.detail.glance.consumptionGauge.used') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="precent-data">
|
<span class="precent-data">
|
||||||
{{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }}
|
{{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }}
|
||||||
<span class="ml-10 percentage">
|
|
||||||
/ {{ formattedPercentage }}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</div>
|
||||||
</ConsumptionGauge>
|
<div class="mt-10">
|
||||||
|
<PercentageBar
|
||||||
|
:model-value="usedPercentage"
|
||||||
|
show-percentage
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.consumption-gauge {
|
||||||
|
.numbers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.precent-data {
|
.precent-data {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -331,7 +333,8 @@ harvester:
|
|||||||
message: Message
|
message: Message
|
||||||
phase: Phase
|
phase: Phase
|
||||||
attachedVM: Attached Virtual Machine
|
attachedVM: Attached Virtual Machine
|
||||||
cpuManager: CPU Manager
|
cpuManager: CPU<br>Manager
|
||||||
|
diskState: Disk
|
||||||
routeConnectivityTooltip: Connectivity between the VM network and the management network, which the Harvester nodes are connected to.
|
routeConnectivityTooltip: Connectivity between the VM network and the management network, which the Harvester nodes are connected to.
|
||||||
fingerprint: Fingerprint
|
fingerprint: Fingerprint
|
||||||
value: Value
|
value: Value
|
||||||
@@ -487,7 +490,6 @@ harvester:
|
|||||||
volume:
|
volume:
|
||||||
upperType: Volume name
|
upperType: Volume name
|
||||||
lowerType: volume name
|
lowerType: volume name
|
||||||
needAtLeastOneBootable: 'At least one bootable volume is required!'
|
|
||||||
image:
|
image:
|
||||||
ruleTip: 'The URL you have entered ends in an extension that we do not support. We only accept image files that end in .img, .iso, .qcow, .qcow2, .raw.'
|
ruleTip: 'The URL you have entered ends in an extension that we do not support. We only accept image files that end in .img, .iso, .qcow, .qcow2, .raw.'
|
||||||
ruleFileTip: 'The file you have chosen ends in an extension that we do not support. We only accept image files that end in .img, .iso, .qcow, .qcow2, .raw.'
|
ruleFileTip: 'The file you have chosen ends in an extension that we do not support. We only accept image files that end in .img, .iso, .qcow, .qcow2, .raw.'
|
||||||
@@ -798,6 +800,10 @@ harvester:
|
|||||||
setFirst: Set as root volume
|
setFirst: Set as root volume
|
||||||
saveVolume: Update Volume
|
saveVolume: Update Volume
|
||||||
encryption: Encryption
|
encryption: Encryption
|
||||||
|
shareable:
|
||||||
|
label: Shareable
|
||||||
|
tip: Allow multiple virtual machines to attach and write to this volume simultaneously.
|
||||||
|
warning: Multiple virtual machines can write to this volume at the same time. The guest workloads must coordinate access to the shared block device, otherwise concurrent writes will corrupt the data. Workloads that fence through SCSI-3 Persistent Reservations (e.g. Windows Server Failover Clustering) also require persistent reservation support from the storage backend. Host cache and hypervisor disk locking are disabled, and backup or snapshot operations are rejected while the volume is shareable.
|
||||||
vmImageVolumeTip: Disk size ({diskSize}) should greater than selected image virtual size ({imageVirtualSize})
|
vmImageVolumeTip: Disk size ({diskSize}) should greater than selected image virtual size ({imageVirtualSize})
|
||||||
lockTooltip:
|
lockTooltip:
|
||||||
all: All volumes are encrypted.
|
all: All volumes are encrypted.
|
||||||
@@ -831,13 +837,13 @@ harvester:
|
|||||||
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>"
|
||||||
sysprep:
|
sysprep:
|
||||||
title: Windows Sysprep Configuration
|
title: Windows Unattended & Sysprep Configuration
|
||||||
description: "Configure Windows automated installation using autounattend.xml. The configuration will be stored in a Kubernetes Secret and mounted as a CD-ROM during installation. <a href='https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/automate-windows-setup' target='_blank'>Learn more</a>"
|
description: "Configure Windows automated installation using autounattend.xml. The configuration will be stored in a Kubernetes Secret and mounted as a CD-ROM during installation. <a href='https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/automate-windows-setup' target='_blank'>Learn more</a>"
|
||||||
createNew: Create new...
|
createNew: Create new...
|
||||||
createNewTitle: Create Windows Sysprep Template
|
createNewTitle: Create Windows Unattended & Sysprep Template
|
||||||
xmlContent: autounattend.xml content
|
xmlContent: autounattend.xml content
|
||||||
secret:
|
secret:
|
||||||
label: Windows Sysprep Template
|
label: Windows Unattended & Sysprep Template
|
||||||
validation:
|
validation:
|
||||||
invalidXml: Invalid XML syntax
|
invalidXml: Invalid XML syntax
|
||||||
invalidNamespace: Invalid unattend.xml namespace. Expected 'urn:schemas-microsoft-com:unattend'
|
invalidNamespace: Invalid unattend.xml namespace. Expected 'urn:schemas-microsoft-com:unattend'
|
||||||
@@ -1099,6 +1105,8 @@ harvester:
|
|||||||
downloadLog: Download Log
|
downloadLog: Download Log
|
||||||
logStatus: Log Download Status
|
logStatus: Log Download Status
|
||||||
dismissMessage: Dismiss it
|
dismissMessage: Dismiss it
|
||||||
|
upgradeAvailable: Upgrade Available
|
||||||
|
upgradeUnavailable: Upgrade Unavailable
|
||||||
upgradeInfo:
|
upgradeInfo:
|
||||||
warning: WARNING
|
warning: WARNING
|
||||||
doc: Read the <a href="{url}" target="_blank">documentation</a> before starting the upgrade process. Ensure that you complete procedures that are relevant to your environment and the version you are upgrading to.
|
doc: Read the <a href="{url}" target="_blank">documentation</a> before starting the upgrade process. Ensure that you complete procedures that are relevant to your environment and the version you are upgrading to.
|
||||||
@@ -1826,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.
|
||||||
@@ -1836,6 +1846,7 @@ harvester:
|
|||||||
unknown: Unknown
|
unknown: Unknown
|
||||||
identifier: Identifier
|
identifier: Identifier
|
||||||
podNetwork: Pod Network
|
podNetwork: Pod Network
|
||||||
|
ignored: Ignored
|
||||||
usedBy: "Used by:"
|
usedBy: "Used by:"
|
||||||
vlan: "VLAN {id}"
|
vlan: "VLAN {id}"
|
||||||
vCpu: "{count} vCPU"
|
vCpu: "{count} vCPU"
|
||||||
@@ -1890,11 +1901,14 @@ harvester:
|
|||||||
providerSelect: Provider
|
providerSelect: Provider
|
||||||
createNew: Create new provider
|
createNew: Create new provider
|
||||||
requirementsTitle: Requirements
|
requirementsTitle: Requirements
|
||||||
requirementsText: "To migrate virtual machines from VMware, you will need a vCenter Server running version 6.5 or higher with a user account that has at least read permissions, and the source environment must be network-accessible from Rancher."
|
requirementsText: "To migrate virtual machines from VMware, you will need a vCenter Server running version 6.5 or higher with a user account that has at least read permissions, and the source environment must be network-accessible from Harvester Node."
|
||||||
name: Name
|
name: Name
|
||||||
urlLabel: vCenter/ESXi URL
|
urlLabel: vCenter/ESXi URL
|
||||||
urlPlaceholder: "https://vcenter.example.com/sdk"
|
urlPlaceholder: "https://vcenter.example.com/sdk"
|
||||||
urlHint: Enter the full URL including https://
|
urlHint: Enter the full URL including https://
|
||||||
|
vddkImageLabel: VMware VDDK Image
|
||||||
|
vddkImagePlaceholder: "[your-private-registry]/vddk-image:latest"
|
||||||
|
vddkImageTooltip: Specify the VMware VDDK (Virtual Disk Development Kit) Image to accelerate and optimize VMware virtual machine migration.
|
||||||
skipSsl: Skip SSL certificate verification
|
skipSsl: Skip SSL certificate verification
|
||||||
skipSslHint: Not recommended for production environments
|
skipSslHint: Not recommended for production environments
|
||||||
testConnection:
|
testConnection:
|
||||||
@@ -1916,6 +1930,7 @@ harvester:
|
|||||||
refreshNow: Refresh now
|
refreshNow: Refresh now
|
||||||
availableVms: Available Virtual Machines
|
availableVms: Available Virtual Machines
|
||||||
selected: selected
|
selected: selected
|
||||||
|
clearAll: Clear All
|
||||||
saveSelection: Save Selection and Continue
|
saveSelection: Save Selection and Continue
|
||||||
selectAllBanner:
|
selectAllBanner:
|
||||||
pageOnly: "Your current selection includes only VMs currently shown on the page."
|
pageOnly: "Your current selection includes only VMs currently shown on the page."
|
||||||
@@ -1954,14 +1969,15 @@ harvester:
|
|||||||
initializingMigration: Initializing migration
|
initializingMigration: Initializing migration
|
||||||
finishedSuccessfully: Finished Successfully
|
finishedSuccessfully: Finished Successfully
|
||||||
step: "Step {index}"
|
step: "Step {index}"
|
||||||
vmCount: "{count} VMs"
|
vmCount: "{count, plural, one {# VM} other {# VMs}}"
|
||||||
vmId: "id: {id}"
|
vmId: "id: {id}"
|
||||||
|
checkMigratedVm: Click to check migrated VM details
|
||||||
configureMappings:
|
configureMappings:
|
||||||
title: Set Mappings
|
title: Set Mappings
|
||||||
description: Map VMware networks and datastores to Harvester and Longhorn target resources
|
description: Map VMware networks and datastores to Harvester and Longhorn target resources
|
||||||
save: Save Mappings and Continue
|
save: Save Mappings and Continue
|
||||||
noTemplate: Start from scratch
|
noTemplate: Start from scratch
|
||||||
removeMap: Remove Map
|
removeMap: Remove mapping
|
||||||
networkMapping:
|
networkMapping:
|
||||||
title: Network Mapping
|
title: Network Mapping
|
||||||
description: Map VMware port groups to Harvester networks
|
description: Map VMware port groups to Harvester networks
|
||||||
@@ -1969,27 +1985,62 @@ harvester:
|
|||||||
template: Use existing network mapping as template
|
template: Use existing network mapping as template
|
||||||
options:
|
options:
|
||||||
podNetworking: Management Network
|
podNetworking: Management Network
|
||||||
ignored: Ignored
|
ignored: Ignored (No NIC will be created)
|
||||||
storageMapping:
|
storageMapping:
|
||||||
title: Storage Mapping
|
title: Storage Mapping
|
||||||
description: Map VMware datastores to Harvester storage classes
|
description: Map VMware datastores to Harvester storage classes
|
||||||
placeholder: Choose a Longhorn Storage Volume
|
placeholder: Choose a Harvester Storage Class
|
||||||
template: Use existing storage mapping as template
|
template: Use existing storage mapping as template
|
||||||
|
storageDefaults:
|
||||||
|
title: 'Storage defaults — {name}'
|
||||||
|
description: 'Set the volume mode and access modes applied to every datastore in this storage map that maps to {name}.'
|
||||||
|
descriptionInherited: 'Applies to every datastore in this storage map that maps to {name}. The provider default for {provider} stays unchanged.'
|
||||||
|
summary: 'Volume mode: {volumeMode} · Access mode: {accessModes}'
|
||||||
|
inherited: 'Inherited from provider {provider}'
|
||||||
|
volumeMode: Volume Mode
|
||||||
|
accessMode: Access Mode
|
||||||
|
edit: Edit
|
||||||
|
cancel: Cancel
|
||||||
|
apply: Apply
|
||||||
|
resetToProviderDefault: Reset to Provider Default
|
||||||
|
applyOverride: Apply Override
|
||||||
reviewMigration:
|
reviewMigration:
|
||||||
title: Review Migration Plan
|
title: Review Migration Plan
|
||||||
description: Confirm your migration settings before starting the transfer of VMs to the target cluster.
|
description: Confirm your migration settings before starting the transfer of VMs to the target cluster.
|
||||||
planName: Name
|
planName: Name
|
||||||
planNamePlaceholder: "e.g. my-migration-plan"
|
planNamePlaceholder: "e.g. my-migration-plan"
|
||||||
|
planNameProviderConflict: 'The name “{name}” is already used by an existing provider. Please input a different plan name.'
|
||||||
|
planNamePlanConflict: 'The name “{name}” is already used by an existing migration plan. Please input a different plan name.'
|
||||||
migrationDetails: Migration Details
|
migrationDetails: Migration Details
|
||||||
|
migrationDetailsDescription: Name this plan and choose the namespace where migrated VMs will be created.
|
||||||
totalVms: Total VMs
|
totalVms: Total VMs
|
||||||
vcpu: vCPU
|
vcpu: vCPU
|
||||||
memory: Memory
|
memory: Memory
|
||||||
storage: Storage
|
storage: Storage
|
||||||
source: Source
|
source: Source
|
||||||
targetNamespace: Target Namespace
|
provider: Provider
|
||||||
|
targetNamespace: Namespace
|
||||||
|
targetNamespacePlaceholder: "Select target namespace"
|
||||||
migrationMode: Migration Mode
|
migrationMode: Migration Mode
|
||||||
coldMigration: Cold Migration
|
coldMigration: Cold Migration
|
||||||
|
coldMigrationDescription: All source VMs will be powered off during migration.
|
||||||
|
coldMigrationHint: Plan a maintenance window and notify relevant stakeholders before starting.
|
||||||
virtualMachines: Virtual Machines
|
virtualMachines: Virtual Machines
|
||||||
|
editSelection: Edit selection
|
||||||
|
noVmSelected: "No VMs selected. Return to "
|
||||||
|
noVmSelectedLink: "Step 2 to select virtual machines."
|
||||||
|
columns:
|
||||||
|
vmName: VM Name
|
||||||
|
os: Operating System
|
||||||
|
resources: Resources
|
||||||
|
powerState: Power State
|
||||||
|
networkMapping: Network Mapping
|
||||||
|
storageMapping: Storage Mapping
|
||||||
|
sourceTarget: "Source \u2192 Target"
|
||||||
|
actions:
|
||||||
|
menuLabel: Actions
|
||||||
|
editMappings: Edit Mappings
|
||||||
|
removeFromPlan: Remove from Plan
|
||||||
warningTitle: "Cold Migration Warning"
|
warningTitle: "Cold Migration Warning"
|
||||||
warningMessage: "All source VMs will be powered off, and downtime will occur during migration. Plan your maintenance window accordingly and notify relevant stakeholders"
|
warningMessage: "All source VMs will be powered off, and downtime will occur during migration. Plan your maintenance window accordingly and notify relevant stakeholders"
|
||||||
startMigration: Start Migration Plan
|
startMigration: Start Migration Plan
|
||||||
@@ -2089,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:
|
||||||
@@ -2254,6 +2313,12 @@ harvester:
|
|||||||
prefix: The nvidia-driver-toolkit add-on is not enabled, click
|
prefix: The nvidia-driver-toolkit add-on is not enabled, click
|
||||||
middle: here
|
middle: here
|
||||||
suffix: to enable it to manage your SR-IOV GPU devices.
|
suffix: to enable it to manage your SR-IOV GPU devices.
|
||||||
|
enable:
|
||||||
|
title: Enable SR-IOV GPU Device
|
||||||
|
skipMigConfiguration: Use timesliced mode (skip MIG configuration)
|
||||||
|
note: If unchecked, MIG mode will be attempted by default. If MIG mode is not supported or fails to initialize, it will fall back to timesliced mode. The actual mode depends on whether the GPU supports MIG.
|
||||||
|
timeslicedBadge: timesliced mode
|
||||||
|
timesliced: Timesliced
|
||||||
|
|
||||||
migconfiguration:
|
migconfiguration:
|
||||||
label: vGPU MIG Configurations
|
label: vGPU MIG Configurations
|
||||||
@@ -2364,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.
|
||||||
@@ -2374,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.
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ export default {
|
|||||||
value: 'spec.address',
|
value: 'spec.address',
|
||||||
sort: ['spec.address']
|
sort: ['spec.address']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'timesliced',
|
||||||
|
labelKey: 'harvester.sriovgpu.timesliced',
|
||||||
|
value: 'isTimesliced',
|
||||||
|
formatter: 'Checked',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'vfAddresses',
|
name: 'vfAddresses',
|
||||||
label: 'VF Addresses',
|
label: 'VF Addresses',
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
...NAME,
|
...NAME,
|
||||||
width: 130,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'host-ip',
|
name: 'host-ip',
|
||||||
@@ -136,14 +136,14 @@ export default {
|
|||||||
value: 'id',
|
value: 'id',
|
||||||
formatter: 'HarvesterCPUPinning',
|
formatter: 'HarvesterCPUPinning',
|
||||||
formatterOpts: { rows: this.rows },
|
formatterOpts: { rows: this.rows },
|
||||||
width: 150,
|
width: 75,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.hasLonghornSchema) {
|
if (this.hasLonghornSchema) {
|
||||||
out.push({
|
out.push({
|
||||||
name: 'diskState',
|
name: 'diskState',
|
||||||
labelKey: 'tableHeaders.diskState',
|
labelKey: 'harvester.tableHeaders.diskState',
|
||||||
value: 'diskState',
|
value: 'diskState',
|
||||||
formatter: 'HarvesterDiskState',
|
formatter: 'HarvesterDiskState',
|
||||||
width: 130,
|
width: 130,
|
||||||
@@ -156,7 +156,6 @@ export default {
|
|||||||
name: 'console',
|
name: 'console',
|
||||||
label: ' ',
|
label: ' ',
|
||||||
align: 'right',
|
align: 'right',
|
||||||
width: 80,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
@@ -239,6 +238,8 @@ export default {
|
|||||||
v-if="!row.consoleUrl"
|
v-if="!row.consoleUrl"
|
||||||
:href="consoleDocLink"
|
:href="consoleDocLink"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="console-info"
|
||||||
><i class="icon icon-info" /></a>
|
><i class="icon icon-info" /></a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -249,5 +250,13 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.console-button {
|
.console-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-info {
|
||||||
|
position: absolute;
|
||||||
|
top: -8px;
|
||||||
|
right: -3px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ export const SSH_EXISTING_TYPE = {
|
|||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
hasCloudConfigComment(userScript) {
|
hasCloudConfigComment(userScript) {
|
||||||
|
if (typeof userScript === 'string' && /^\s*#cloud-config(\s|$)/.test(userScript)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check that userData contains: #cloud-config
|
// Check that userData contains: #cloud-config
|
||||||
const userDataDoc = userScript ? YAML.parseDocument(userScript) : YAML.parseDocument({});
|
const userDataDoc = userScript ? YAML.parseDocument(userScript) : YAML.parseDocument({});
|
||||||
const items = userDataDoc?.contents?.items || [];
|
const items = userDataDoc?.contents?.items || [];
|
||||||
@@ -52,6 +56,14 @@ export default {
|
|||||||
exist = true;
|
exist = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userDataDoc?.contents?.commentBefore === 'cloud-config' || userDataDoc?.contents?.commentBefore?.includes('cloud-config\n')) {
|
||||||
|
exist = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userDataDoc?.contents?.comment === 'cloud-config' || userDataDoc?.contents?.comment?.includes('cloud-config\n')) {
|
||||||
|
exist = true;
|
||||||
|
}
|
||||||
|
|
||||||
items.map((item) => {
|
items.map((item) => {
|
||||||
const key = item.key;
|
const key = item.key;
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,12 @@ export default {
|
|||||||
machineType: '',
|
machineType: '',
|
||||||
machineTypes: [],
|
machineTypes: [],
|
||||||
secretName: '',
|
secretName: '',
|
||||||
|
// Tracks the name prefix that `secretName` was generated from, so it can be
|
||||||
|
// regenerated if the VM name changes (e.g. after a failed create attempt is retried
|
||||||
|
// with a different name). See harvester/harvester#11174.
|
||||||
|
secretNamePrefixUsed: '',
|
||||||
|
// Same purpose as secretNamePrefixUsed, but for the Windows sysprep secret name.
|
||||||
|
sysprepSecretNamePrefixUsed: '',
|
||||||
secretRef: null,
|
secretRef: null,
|
||||||
showAdvanced: false,
|
showAdvanced: false,
|
||||||
deleteAgent: true,
|
deleteAgent: true,
|
||||||
@@ -610,6 +616,7 @@ export default {
|
|||||||
type,
|
type,
|
||||||
storageClassName,
|
storageClassName,
|
||||||
hotpluggable,
|
hotpluggable,
|
||||||
|
shareable: DISK.shareable || false,
|
||||||
volumeStatus,
|
volumeStatus,
|
||||||
dataSource,
|
dataSource,
|
||||||
namespace,
|
namespace,
|
||||||
@@ -783,18 +790,41 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const staticIpPrefix = `${ HCI_ANNOTATIONS.STATIC_IP }/`;
|
const staticIpPrefix = `${ HCI_ANNOTATIONS.STATIC_IP }/`;
|
||||||
|
const annotations = vm.metadata.annotations;
|
||||||
|
|
||||||
Object.keys(vm.metadata.annotations).forEach((key) => {
|
const desired = {};
|
||||||
if (key.startsWith(staticIpPrefix)) {
|
|
||||||
delete vm.metadata.annotations[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.networkRows.forEach((row) => {
|
this.networkRows.forEach((row) => {
|
||||||
if (row.name && row.staticIp) {
|
if (row.name && row.staticIp) {
|
||||||
vm.metadata.annotations[`${ staticIpPrefix }${ row.name }`] = row.staticIp;
|
desired[`${ staticIpPrefix }${ row.name }`] = row.staticIp;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const current = {};
|
||||||
|
|
||||||
|
Object.keys(annotations).forEach((key) => {
|
||||||
|
if (key.startsWith(staticIpPrefix)) {
|
||||||
|
current[key] = annotations[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Skip mutation when already in sync to avoid triggering a reactive update loop.
|
||||||
|
const desiredKeys = Object.keys(desired);
|
||||||
|
const currentKeys = Object.keys(current);
|
||||||
|
const isSame = desiredKeys.length === currentKeys.length &&
|
||||||
|
desiredKeys.every((key) => current[key] === desired[key]);
|
||||||
|
|
||||||
|
if (isSame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentKeys.forEach((key) => {
|
||||||
|
delete annotations[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(desired).forEach(([key, value]) => {
|
||||||
|
annotations[key] = value;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setCPUAndMemory() {
|
setCPUAndMemory() {
|
||||||
@@ -866,18 +896,30 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.needNewSecret || !this.secretName) {
|
// Regenerate the cloud-init secret name whenever it hasn't been generated yet, or the VM
|
||||||
|
// name has changed since it was last generated (e.g. a previous create attempt failed with
|
||||||
|
// an invalid/duplicate name and the user corrected it). Without this, a stale secret name
|
||||||
|
// derived from a rejected attempt gets reused for the VM that's actually created.
|
||||||
|
// See harvester/harvester#11174.
|
||||||
|
if (this.needNewSecret || !this.secretName || (this.isCreate && this.secretNamePrefixUsed !== this.secretNamePrefix)) {
|
||||||
this.secretName = this.generateSecretName(this.secretNamePrefix);
|
this.secretName = this.generateSecretName(this.secretNamePrefix);
|
||||||
|
this.secretNamePrefixUsed = this.secretNamePrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disks.find((D) => D.name === 'sysprep') && this.isWindows) {
|
if (!disks.find((D) => D.name === 'sysprep') && this.isWindows) {
|
||||||
const hasSysprepContent = !!this.sysprep.xmlContent?.trim?.();
|
const hasSysprepContent = !!this.sysprep.xmlContent?.trim?.();
|
||||||
|
|
||||||
// If we have content but no secret name, it's a new secret that needs a name.
|
// If we have content but no secret name, it's a new secret that needs a name. Also
|
||||||
if (hasSysprepContent && !this.sysprep.secretName?.trim?.()) {
|
// regenerate when creating a VM if the name prefix has changed since it was last
|
||||||
|
// generated.
|
||||||
|
const sysprepNeedsNewSecretName = !this.sysprep.secretName?.trim?.() ||
|
||||||
|
(this.isCreate && this.sysprepSecretNamePrefixUsed !== '' && this.sysprepSecretNamePrefixUsed !== this.secretNamePrefix);
|
||||||
|
|
||||||
|
if (hasSysprepContent && sysprepNeedsNewSecretName) {
|
||||||
const prefix = this.secretNamePrefix ? `${ this.secretNamePrefix }-windows-sysprep` : 'windows-sysprep';
|
const prefix = this.secretNamePrefix ? `${ this.secretNamePrefix }-windows-sysprep` : 'windows-sysprep';
|
||||||
|
|
||||||
this.sysprep.secretName = `${ this.value.metadata.namespace }/${ this.generateSecretName(prefix) }`;
|
this.sysprep.secretName = `${ this.value.metadata.namespace }/${ this.generateSecretName(prefix) }`;
|
||||||
|
this.sysprepSecretNamePrefixUsed = this.secretNamePrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preserve/attach sysprep whenever a secret is selected/known.
|
// Preserve/attach sysprep whenever a secret is selected/known.
|
||||||
@@ -896,38 +938,44 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disks.find( (D) => D.name === 'cloudinitdisk') && (this.userData || this.networkData)) {
|
if (!disks.find( (D) => D.name === 'cloudinitdisk') && (this.userData || this.networkScript)) {
|
||||||
if (!this.isWindows) {
|
disks.push({
|
||||||
disks.push({
|
name: 'cloudinitdisk',
|
||||||
name: 'cloudinitdisk',
|
disk: { bus: 'virtio' }
|
||||||
disk: { bus: 'virtio' }
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
|
const userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
|
||||||
const cloudinitdisk = {
|
const cloudinitdisk = {
|
||||||
name: 'cloudinitdisk',
|
name: 'cloudinitdisk',
|
||||||
cloudInitNoCloud: {}
|
cloudInitNoCloud: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.saveUserDataAsClearText) {
|
if (this.saveUserDataAsClearText) {
|
||||||
cloudinitdisk.cloudInitNoCloud.userData = userData;
|
cloudinitdisk.cloudInitNoCloud.userData = userData;
|
||||||
} else {
|
} else {
|
||||||
cloudinitdisk.cloudInitNoCloud.secretRef = { name: this.secretName };
|
cloudinitdisk.cloudInitNoCloud.secretRef = { name: this.secretName };
|
||||||
}
|
|
||||||
|
|
||||||
if (this.saveNetworkDataAsClearText) {
|
|
||||||
cloudinitdisk.cloudInitNoCloud.networkData = this.networkScript;
|
|
||||||
} else {
|
|
||||||
cloudinitdisk.cloudInitNoCloud.networkDataSecretRef = { name: this.secretName };
|
|
||||||
}
|
|
||||||
|
|
||||||
volumes.push(cloudinitdisk);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.saveNetworkDataAsClearText) {
|
||||||
|
cloudinitdisk.cloudInitNoCloud.networkData = this.networkScript;
|
||||||
|
} else {
|
||||||
|
cloudinitdisk.cloudInitNoCloud.networkDataSecretRef = { name: this.secretName };
|
||||||
|
}
|
||||||
|
|
||||||
|
volumes.push(cloudinitdisk);
|
||||||
}
|
}
|
||||||
|
|
||||||
const specDisks = this.spec?.template?.spec?.domain?.devices?.disks;
|
const specDisks = this.spec?.template?.spec?.domain?.devices?.disks;
|
||||||
const mergedDisks = this.mergeDeviceList(specDisks, disks);
|
const mergedDisks = this.mergeDeviceList(specDisks, disks);
|
||||||
|
|
||||||
|
// `shareable` is an attachment-level opt-in, remove the field
|
||||||
|
// inherited from the old spec when the disk row no longer requests it
|
||||||
|
mergedDisks.forEach((disk) => {
|
||||||
|
if (disk.shareable && !disks.find((D) => D.name === disk.name)?.shareable) {
|
||||||
|
delete disk.shareable;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let spec = {
|
let spec = {
|
||||||
...this.spec,
|
...this.spec,
|
||||||
runStrategy: this.runStrategy,
|
runStrategy: this.runStrategy,
|
||||||
@@ -1124,6 +1172,16 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getInitUserData(config) {
|
getInitUserData(config) {
|
||||||
|
// Windows guests don't use qemu-guest-agent via systemd (VMDP installs
|
||||||
|
// the QGA as a Windows service), and the Linux `runcmd` recipe would
|
||||||
|
// fail on Cloudbase-Init. Return an empty string so `this.userData`
|
||||||
|
// stays falsy on Windows until the user actually enters cloud-config
|
||||||
|
// content — this prevents emitting a spurious cloudinitdisk volume
|
||||||
|
// and Secret for every Windows VM (Copilot review comment on #984).
|
||||||
|
if (config.osType === 'windows') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
const _QGA_JSON = this.getMatchQGA(config.osType);
|
const _QGA_JSON = this.getMatchQGA(config.osType);
|
||||||
|
|
||||||
const out = jsyaml.dump(_QGA_JSON);
|
const out = jsyaml.dump(_QGA_JSON);
|
||||||
@@ -1151,9 +1209,10 @@ export default {
|
|||||||
|
|
||||||
userDataDoc = config.installAgent ? this.mergeQGA({ userDataDoc, ...config }) : this.deleteQGA({ userDataDoc, ...config });
|
userDataDoc = config.installAgent ? this.mergeQGA({ userDataDoc, ...config }) : this.deleteQGA({ userDataDoc, ...config });
|
||||||
const userDataYaml = userDataDoc.toString();
|
const userDataYaml = userDataDoc.toString();
|
||||||
|
const userDataValue = userDataDoc.toJSON();
|
||||||
|
|
||||||
if (userDataYaml === '{}\n') {
|
if (userDataValue === null || (typeof userDataValue === 'object' && !Array.isArray(userDataValue) && isEmpty(userDataValue))) {
|
||||||
// When the YAML parsed value is '{}\n', it means that the userData is empty, then undefined is returned.
|
// Empty userData should not create cloud-init content like `null`.
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1202,6 +1261,10 @@ export default {
|
|||||||
out.cdrom = { bus: R.bus };
|
out.cdrom = { bus: R.bus };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (R.shareable) {
|
||||||
|
out.shareable = true;
|
||||||
|
}
|
||||||
|
|
||||||
out.bootOrder = index + 1;
|
out.bootOrder = index + 1;
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
@@ -1326,13 +1389,22 @@ export default {
|
|||||||
*/
|
*/
|
||||||
deleteYamlDocProp(doc, paths) {
|
deleteYamlDocProp(doc, paths) {
|
||||||
try {
|
try {
|
||||||
const item = doc.getIn([])?.items[0];
|
const items = doc.getIn([])?.items;
|
||||||
const key = item?.key;
|
const firstKey = items?.[0]?.key;
|
||||||
const hasCloudConfigComment = !!key?.commentBefore?.includes('cloud-config');
|
const hasCloudConfigComment = !!firstKey?.commentBefore?.includes('cloud-config');
|
||||||
const isMatchProp = key.source === paths[paths.length - 1];
|
const isFirstProp = firstKey?.source === paths[paths.length - 1];
|
||||||
|
|
||||||
if (key && hasCloudConfigComment && isMatchProp) {
|
if (firstKey && hasCloudConfigComment && isFirstProp) {
|
||||||
// Comments are mounted on the next node and we should not delete the node containing cloud-config
|
const comment = firstKey.commentBefore;
|
||||||
|
|
||||||
|
doc.deleteIn(paths);
|
||||||
|
|
||||||
|
// Move the comment to the new first key; if no keys remain the comment is lost.
|
||||||
|
const newFirstKey = doc.getIn([])?.items?.[0]?.key;
|
||||||
|
|
||||||
|
if (newFirstKey) {
|
||||||
|
newFirstKey.commentBefore = comment;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
doc.deleteIn(paths);
|
doc.deleteIn(paths);
|
||||||
}
|
}
|
||||||
@@ -1383,7 +1455,6 @@ export default {
|
|||||||
if (packages.length > 0) {
|
if (packages.length > 0) {
|
||||||
userDataDoc.setIn(['packages'], packages);
|
userDataDoc.setIn(['packages'], packages);
|
||||||
} else {
|
} else {
|
||||||
userDataDoc.setIn(['packages'], []); // It needs to be set empty first, as it is possible that cloud-init comments are mounted on this node
|
|
||||||
this.deleteYamlDocProp(userDataDoc, ['packages']);
|
this.deleteYamlDocProp(userDataDoc, ['packages']);
|
||||||
this.deleteYamlDocProp(userDataDoc, ['package_update']);
|
this.deleteYamlDocProp(userDataDoc, ['package_update']);
|
||||||
}
|
}
|
||||||
@@ -1398,7 +1469,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
deleteQGA(config) {
|
deleteQGA(config) {
|
||||||
const { osType, userDataDoc, deletePackage = false } = config;
|
const { osType, userDataDoc } = config;
|
||||||
|
|
||||||
const userDataTemplateValue = this.$store.getters['harvester/byId'](CONFIG_MAP, this.userDataTemplateId)?.data?.cloudInit || '';
|
const userDataTemplateValue = this.$store.getters['harvester/byId'](CONFIG_MAP, this.userDataTemplateId)?.data?.cloudInit || '';
|
||||||
|
|
||||||
@@ -1407,6 +1478,15 @@ export default {
|
|||||||
const packages = userDataJSON?.packages || [];
|
const packages = userDataJSON?.packages || [];
|
||||||
const runcmd = userDataJSON?.runcmd || [];
|
const runcmd = userDataJSON?.runcmd || [];
|
||||||
|
|
||||||
|
// Special handling of OS types.
|
||||||
|
let deletePackage = config.deletePackage ?? false;
|
||||||
|
|
||||||
|
switch (osType) {
|
||||||
|
case 'windows':
|
||||||
|
deletePackage = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(packages) && deletePackage) {
|
if (Array.isArray(packages) && deletePackage) {
|
||||||
const templateHasQGAPackage = this.convertToJson(userDataTemplateValue);
|
const templateHasQGAPackage = this.convertToJson(userDataTemplateValue);
|
||||||
|
|
||||||
@@ -1432,7 +1512,6 @@ export default {
|
|||||||
if (packages.length > 0) {
|
if (packages.length > 0) {
|
||||||
userDataDoc.setIn(['packages'], packages);
|
userDataDoc.setIn(['packages'], packages);
|
||||||
} else {
|
} else {
|
||||||
userDataDoc.setIn(['packages'], []);
|
|
||||||
this.deleteYamlDocProp(userDataDoc, ['packages']);
|
this.deleteYamlDocProp(userDataDoc, ['packages']);
|
||||||
this.deleteYamlDocProp(userDataDoc, ['package_update']);
|
this.deleteYamlDocProp(userDataDoc, ['package_update']);
|
||||||
}
|
}
|
||||||
@@ -1465,7 +1544,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async saveSecret(vm) {
|
async saveSecret(vm) {
|
||||||
if (!vm?.spec || !this.secretName || this.isWindows) {
|
if (!vm?.spec || !this.secretName) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1886,8 +1965,6 @@ export default {
|
|||||||
isWindows(val) {
|
isWindows(val) {
|
||||||
if (val) {
|
if (val) {
|
||||||
this['sshKey'] = [];
|
this['sshKey'] = [];
|
||||||
this['userScript'] = undefined;
|
|
||||||
this['networkScript'] = undefined;
|
|
||||||
this['installAgent'] = false;
|
this['installAgent'] = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1947,9 +2024,8 @@ export default {
|
|||||||
|
|
||||||
osType(neu, old) {
|
osType(neu, old) {
|
||||||
this.installAgent = old === 'windows' ? true : this.installAgent;
|
this.installAgent = old === 'windows' ? true : this.installAgent;
|
||||||
const out = old === 'windows' ? this.getInitUserData({ osType: neu }) : this.getUserData({ installAgent: this.installAgent, osType: neu });
|
this.userScript = this.getUserData({ installAgent: this.installAgent, osType: neu });
|
||||||
|
|
||||||
this['userScript'] = out;
|
|
||||||
this.refreshYamlEditor();
|
this.refreshYamlEditor();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -69,16 +69,15 @@ export default class SRIOVDevice extends SteveModel {
|
|||||||
return this.spec.enabled && this.status?.vfAddresses?.length > 0 && this.status?.vGPUDevices?.length > 0;
|
return this.spec.enabled && this.status?.vfAddresses?.length > 0 && this.status?.vGPUDevices?.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableDevice() {
|
get isTimesliced() {
|
||||||
try {
|
return this.metadata?.annotations?.[HCI_ANNOTATIONS.SKIP_MIG_CONFIGURATION] === 'true';
|
||||||
this.spec.enabled = true;
|
}
|
||||||
await this.save();
|
|
||||||
} catch (err) {
|
enableDevice() {
|
||||||
this.$dispatch('growl/fromError', {
|
this.$dispatch('promptModal', {
|
||||||
title: this.t('generic.notification.title.error', { name: escapeHtml(this.metadata.name) }),
|
resources: [this],
|
||||||
err,
|
component: 'EnableSriovGpuDevice'
|
||||||
}, { root: true });
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async disableDevice() {
|
async disableDevice() {
|
||||||
|
|||||||
@@ -69,6 +69,11 @@ export default class HarvesterResource extends SteveModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable Resources/ Extras cards in resource details pages
|
||||||
|
get resourcesCardRows() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
cleanForSave(data, _forNew) {
|
cleanForSave(data, _forNew) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import SYSTEM_NAMESPACES from '@shell/config/system-namespaces';
|
|||||||
import { get } from '@shell/utils/object';
|
import { get } from '@shell/utils/object';
|
||||||
import { NAMESPACE } from '@shell/config/types';
|
import { NAMESPACE } from '@shell/config/types';
|
||||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
|
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
|
||||||
|
import { FORKLIFT_NAMESPACE } from '@pkg/harvester/config/harvester-map';
|
||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
|
|
||||||
const OBSCURE_NAMESPACE_PREFIX = [
|
const OBSCURE_NAMESPACE_PREFIX = [
|
||||||
@@ -93,6 +94,10 @@ export default class HciNamespace extends namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isSystem() {
|
get isSystem() {
|
||||||
|
if (this.metadata?.name === FORKLIFT_NAMESPACE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const systemNamespaces = ['fleet-default'];
|
const systemNamespaces = ['fleet-default'];
|
||||||
|
|
||||||
if (systemNamespaces.includes(this.metadata.name)) {
|
if (systemNamespaces.includes(this.metadata.name)) {
|
||||||
|
|||||||
@@ -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-rc2",
|
"version": "1.9.1-rc1",
|
||||||
"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,10 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import Loading from '@shell/components/Loading';
|
|
||||||
import { HCI } from '../../../../../types';
|
import { HCI } from '../../../../../types';
|
||||||
import SerialConsole from '../../../../../components/SerialConsole';
|
import SerialConsole from '../../../../../components/SerialConsole';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { SerialConsole, Loading },
|
components: { SerialConsole },
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
this.rows = await this.$store.dispatch('harvester/findAll', { type: HCI.VMI });
|
this.rows = await this.$store.dispatch('harvester/findAll', { type: HCI.VMI });
|
||||||
@@ -38,16 +37,44 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Loading v-if="$fetchState.pending" />
|
<div class="serial-console-page">
|
||||||
<SerialConsole
|
<SerialConsole
|
||||||
v-else
|
ref="serialConsole"
|
||||||
ref="serialConsole"
|
:value="vmi || {}"
|
||||||
v-model:value="vmi"
|
/>
|
||||||
/>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
body, #__nuxt, #__layout, main {
|
html,
|
||||||
|
body,
|
||||||
|
#__nuxt,
|
||||||
|
#__layout,
|
||||||
|
main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.serial-console-page {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
min-height: 0;
|
||||||
|
background: var(--body-bg);
|
||||||
|
color: var(--terminal-text);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 6px;
|
||||||
|
pointer-events: none;
|
||||||
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
.harvester-shell-container {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--terminal-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const inStore = computed(() => store.getters['currentProduct'].inStore);
|
|||||||
const allPlans = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_PLAN));
|
const allPlans = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_PLAN));
|
||||||
const allNetworkMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_NETWORK_MAP));
|
const allNetworkMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_NETWORK_MAP));
|
||||||
const allStorageMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_STORAGE_MAP));
|
const allStorageMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_STORAGE_MAP));
|
||||||
|
const allVMs = computed(() => store.getters[`${ inStore.value }/all`](HCI.VM));
|
||||||
|
|
||||||
const escapeHtml = (value = '') => String(value)
|
const escapeHtml = (value = '') => String(value)
|
||||||
.replaceAll('&', '&')
|
.replaceAll('&', '&')
|
||||||
@@ -98,12 +99,12 @@ const formatPipelineTooltip = (pipeline = []) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatDuration = (started, completed) => {
|
const formatDuration = (started, completed) => {
|
||||||
if (!started || !completed) {
|
if (!started) {
|
||||||
return { display: null, seconds: -1 };
|
return { display: null, seconds: -1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const startMs = new Date(started).getTime();
|
const startMs = new Date(started).getTime();
|
||||||
const endMs = new Date(completed).getTime();
|
const endMs = completed ? new Date(completed).getTime() : Date.now();
|
||||||
|
|
||||||
if (isNaN(startMs) || isNaN(endMs) || endMs < startMs) {
|
if (isNaN(startMs) || isNaN(endMs) || endMs < startMs) {
|
||||||
return { display: null, seconds: -1 };
|
return { display: null, seconds: -1 };
|
||||||
@@ -129,8 +130,8 @@ const rows = computed(() => {
|
|||||||
const netMap = allNetworkMaps.value.find((m) => m.metadata.name === netMapName && m.metadata.namespace === netMapNs);
|
const netMap = allNetworkMaps.value.find((m) => m.metadata.name === netMapName && m.metadata.namespace === netMapNs);
|
||||||
const storMap = allStorageMaps.value.find((m) => m.metadata.name === storMapName && m.metadata.namespace === storMapNs);
|
const storMap = allStorageMaps.value.find((m) => m.metadata.name === storMapName && m.metadata.namespace === storMapNs);
|
||||||
|
|
||||||
plan.networkEntries = (netMap?.spec?.map || []).map((e) => `${ e.source?.id || '-' } → ${ e.destination?.type === 'pod' ? t('harvester.addons.vmMigration.generic.podNetwork') : (e.destination?.name || '-') }`);
|
plan.networkEntries = (netMap?.spec?.map || []).map((e) => `${ e.source?.id || 'Ignored' } → ${ e.destination?.type === 'pod' ? t('harvester.addons.vmMigration.configureMappings.networkMapping.options.podNetworking') : (e.destination?.name || 'Ignored') }`);
|
||||||
plan.storageEntries = (storMap?.spec?.map || []).map((e) => `${ e.source?.id || '-' } → ${ e.destination?.storageClass || '-' }`);
|
plan.storageEntries = (storMap?.spec?.map || []).map((e) => `${ e.source?.id || 'Ignored' } → ${ e.destination?.storageClass || 'Ignored' }`);
|
||||||
|
|
||||||
plan.vmIdsDisplay = (plan.spec?.vms || []).map((vm) => vm.id || vm.name || '').filter(Boolean).join(', ') || '-';
|
plan.vmIdsDisplay = (plan.spec?.vms || []).map((vm) => vm.id || vm.name || '').filter(Boolean).join(', ') || '-';
|
||||||
|
|
||||||
@@ -178,11 +179,35 @@ const rows = computed(() => {
|
|||||||
|
|
||||||
overallProgress = Math.round(overallProgress * 10) / 10;
|
overallProgress = Math.round(overallProgress * 10) / 10;
|
||||||
|
|
||||||
// If no step-level error but the plan itself is failed, surface it
|
// If no step-level error but the plan itself is failed, surface it.
|
||||||
if (!errorMsg && plan.planFailed) {
|
// Skip VMs that already reached 100% (they finished successfully before
|
||||||
|
// the plan failed) so their bar stays green instead of turning red.
|
||||||
|
if (!errorMsg && plan.planFailed && overallProgress < 100) {
|
||||||
errorMsg = `${ currentStep || t('harvester.addons.vmMigration.dashboard.progress.migration') }: ${ t('harvester.addons.vmMigration.dashboard.progress.failed') }`;
|
errorMsg = `${ currentStep || t('harvester.addons.vmMigration.dashboard.progress.migration') }: ${ t('harvester.addons.vmMigration.dashboard.progress.failed') }`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const vmNamespace = vm.namespace || plan.spec?.targetNamespace || plan.metadata?.namespace;
|
||||||
|
const routeParams = currentRouter().currentRoute?.value?.params || {};
|
||||||
|
const product = routeParams.product || store.getters['productId'];
|
||||||
|
const cluster = routeParams.cluster || store.getters['clusterId'];
|
||||||
|
const vmNameCandidates = [vm.targetName, vm.name, vm.id].filter(Boolean);
|
||||||
|
const targetVm = allVMs.value.find((item) => vmNameCandidates.includes(item.metadata?.name) && (!vmNamespace || item.metadata?.namespace === vmNamespace));
|
||||||
|
// A VM that individually reached 100% without error should be navigable
|
||||||
|
// even when the overall plan failed (other VMs in the plan may have
|
||||||
|
// failed); `overallProgress >= 100 && !errorMsg && targetVm` already
|
||||||
|
// guarantees this VM migrated successfully.
|
||||||
|
const canNavigateToVm = overallProgress >= 100 && !errorMsg && !plan.planCanceled && !!targetVm && !!product && !!cluster;
|
||||||
|
const vmDetailLocation = canNavigateToVm ? {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-resource-namespace-id`,
|
||||||
|
params: {
|
||||||
|
product,
|
||||||
|
cluster,
|
||||||
|
resource: HCI.VM,
|
||||||
|
namespace: targetVm.metadata.namespace,
|
||||||
|
id: targetVm.metadata.name,
|
||||||
|
}
|
||||||
|
} : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vmName: vm.name || vm.id || t('harvester.addons.vmMigration.generic.unknown'),
|
vmName: vm.name || vm.id || t('harvester.addons.vmMigration.generic.unknown'),
|
||||||
vmId: vm.id || '',
|
vmId: vm.id || '',
|
||||||
@@ -191,6 +216,7 @@ const rows = computed(() => {
|
|||||||
pipelineTooltip: formatPipelineTooltip(pipeline),
|
pipelineTooltip: formatPipelineTooltip(pipeline),
|
||||||
errorMsg,
|
errorMsg,
|
||||||
canceled: plan.planCanceled,
|
canceled: plan.planCanceled,
|
||||||
|
vmDetailLocation,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,7 +274,7 @@ const headers = [
|
|||||||
labelKey: 'harvester.addons.vmMigration.dashboard.columns.duration',
|
labelKey: 'harvester.addons.vmMigration.dashboard.columns.duration',
|
||||||
value: 'duration',
|
value: 'duration',
|
||||||
sort: 'durationSeconds',
|
sort: 'durationSeconds',
|
||||||
width: 120,
|
width: 90,
|
||||||
},
|
},
|
||||||
{ ...AGE },
|
{ ...AGE },
|
||||||
];
|
];
|
||||||
@@ -283,6 +309,17 @@ const init = async() => {
|
|||||||
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP }),
|
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP }),
|
||||||
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_STORAGE_MAP }),
|
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_STORAGE_MAP }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// VMs are only needed to conditionally show the post-migration VM-detail
|
||||||
|
// link, so load them separately and non-fatally: a failure here (or the
|
||||||
|
// cost of findAll on large clusters) should not block plans/maps from
|
||||||
|
// rendering.
|
||||||
|
try {
|
||||||
|
await store.dispatch(`${ inStore.value }/findAll`, { type: HCI.VM });
|
||||||
|
} catch (e) {
|
||||||
|
// Intentionally ignored: the dashboard still works without the VM list,
|
||||||
|
// the migrated VM-detail link simply won't be shown.
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.value = [e?.message || t('harvester.addons.vmMigration.errors.failedLoadPlans')];
|
errors.value = [e?.message || t('harvester.addons.vmMigration.errors.failedLoadPlans')];
|
||||||
} finally {
|
} finally {
|
||||||
@@ -366,7 +403,17 @@ init();
|
|||||||
>
|
>
|
||||||
<div class="vm-progress-header">
|
<div class="vm-progress-header">
|
||||||
<div class="vm-name-block">
|
<div class="vm-name-block">
|
||||||
<span class="vm-name">{{ vm.vmName }}</span>
|
<span class="vm-name">
|
||||||
|
{{ vm.vmName }}
|
||||||
|
<router-link
|
||||||
|
v-if="vm.vmDetailLocation"
|
||||||
|
v-clean-tooltip="t('harvester.addons.vmMigration.dashboard.progress.checkMigratedVm')"
|
||||||
|
:to="vm.vmDetailLocation"
|
||||||
|
class="vm-detail-link"
|
||||||
|
>
|
||||||
|
<i class="icon icon-external-link" />
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
<span class="text-muted vm-id">{{ t('harvester.addons.vmMigration.dashboard.progress.vmId', { id: vm.vmId }) }}</span>
|
<span class="text-muted vm-id">{{ t('harvester.addons.vmMigration.dashboard.progress.vmId', { id: vm.vmId }) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -457,6 +504,14 @@ init();
|
|||||||
|
|
||||||
.vm-name {
|
.vm-name {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-detail-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-name-block {
|
.vm-name-block {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import CruResource from '@shell/components/CruResource';
|
|||||||
import Loading from '@shell/components/Loading';
|
import Loading from '@shell/components/Loading';
|
||||||
import { SECRET } from '@shell/config/types';
|
import { SECRET } from '@shell/config/types';
|
||||||
import { useI18n } from '@shell/composables/useI18n';
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { exceptionToErrorsArray, stringify } from '@shell/utils/error';
|
||||||
import ConfigureProviderStep from '@pkg/harvester/components/vm-migration/ConfigureProviderStep.vue';
|
import ConfigureProviderStep from '@pkg/harvester/components/vm-migration/ConfigureProviderStep.vue';
|
||||||
import ConfigureMappingsStep from '@pkg/harvester/components/vm-migration/ConfigureMappingsStep.vue';
|
import ConfigureMappingsStep from '@pkg/harvester/components/vm-migration/ConfigureMappingsStep.vue';
|
||||||
import { PRODUCT_NAME } from '@pkg/harvester/config/harvester';
|
import { PRODUCT_NAME } from '@pkg/harvester/config/harvester';
|
||||||
@@ -46,6 +47,7 @@ const stepData = reactive({
|
|||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
skipTlsVerify: false,
|
skipTlsVerify: false,
|
||||||
|
vddkInitImage: '',
|
||||||
testPassed: false,
|
testPassed: false,
|
||||||
testResult: null,
|
testResult: null,
|
||||||
testError: null,
|
testError: null,
|
||||||
@@ -162,7 +164,7 @@ const onFinish = async(buttonCb) => {
|
|||||||
buttonCb(true);
|
buttonCb(true);
|
||||||
currentRouter().push(providerListLocation);
|
currentRouter().push(providerListLocation);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors.value = [err instanceof Error ? err.message : String(err)];
|
errors.value = exceptionToErrorsArray(err).map((e) => (typeof e === 'string' ? e : stringify(e)));
|
||||||
buttonCb(false);
|
buttonCb(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -193,6 +195,7 @@ const init = async() => {
|
|||||||
stepData.provider.selectedProvider = fetchedProvider.metadata.name;
|
stepData.provider.selectedProvider = fetchedProvider.metadata.name;
|
||||||
stepData.provider.providerName = fetchedProvider.metadata.name;
|
stepData.provider.providerName = fetchedProvider.metadata.name;
|
||||||
stepData.provider.url = fetchedProvider.spec?.url || '';
|
stepData.provider.url = fetchedProvider.spec?.url || '';
|
||||||
|
stepData.provider.vddkInitImage = fetchedProvider.spec?.settings?.vddkInitImage || '';
|
||||||
stepData.provider.createdProvider = fetchedProvider;
|
stepData.provider.createdProvider = fetchedProvider;
|
||||||
|
|
||||||
const secretRef = fetchedProvider.spec?.secret;
|
const secretRef = fetchedProvider.spec?.secret;
|
||||||
@@ -231,7 +234,7 @@ const init = async() => {
|
|||||||
// Maps may not exist yet
|
// Maps may not exist yet
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errors.value = [t('harvester.addons.vmMigration.errors.failedLoadProvider', { error: err.message || err })];
|
errors.value = [t('harvester.addons.vmMigration.errors.failedLoadProvider', { error: err.message || stringify(err) })];
|
||||||
}
|
}
|
||||||
|
|
||||||
initialLoading.value = false;
|
initialLoading.value = false;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const stepData = reactive({
|
|||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
skipTlsVerify: false,
|
skipTlsVerify: false,
|
||||||
|
vddkInitImage: '',
|
||||||
testPassed: false,
|
testPassed: false,
|
||||||
testResult: null,
|
testResult: null,
|
||||||
testError: null,
|
testError: null,
|
||||||
@@ -56,7 +57,7 @@ const stepData = reactive({
|
|||||||
networkEntries: [],
|
networkEntries: [],
|
||||||
storageEntries: [],
|
storageEntries: [],
|
||||||
},
|
},
|
||||||
review: { planName: '' },
|
review: { planName: '', targetNamespace: '' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const steps = reactive([
|
const steps = reactive([
|
||||||
@@ -173,6 +174,24 @@ const onReviewReady = (ready) => {
|
|||||||
reviewReady.value = ready;
|
reviewReady.value = ready;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onEditSelection = () => {
|
||||||
|
wizardComponent.value?.goToStep(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEditMappings = () => {
|
||||||
|
wizardComponent.value?.goToStep(3);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRemoveVm = (id) => {
|
||||||
|
selectedVMs.value = selectedVMs.value.filter((vm) => vm.id !== id);
|
||||||
|
stepData.vms.selectedVMIds.delete(id);
|
||||||
|
|
||||||
|
vmsReady.value = selectedVMs.value.length > 0;
|
||||||
|
if (!vmsReady.value) {
|
||||||
|
reviewReady.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Clear downstream data when provider changes
|
// Clear downstream data when provider changes
|
||||||
watch(() => stepData.provider.providerName, (newVal, oldVal) => {
|
watch(() => stepData.provider.providerName, (newVal, oldVal) => {
|
||||||
if (oldVal && newVal !== oldVal) {
|
if (oldVal && newVal !== oldVal) {
|
||||||
@@ -191,13 +210,13 @@ watch(() => stepData.provider.providerName, (newVal, oldVal) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear mappings and review when VM selection changes
|
// Clear mappings and review when VMs are added to the selection. Pure removals
|
||||||
|
// (e.g. "Remove from Plan" on the review step) keep the existing mappings and plan name.
|
||||||
watch(selectedVMs, (newVal, oldVal) => {
|
watch(selectedVMs, (newVal, oldVal) => {
|
||||||
const newIds = new Set(newVal.map((v) => v.id));
|
|
||||||
const oldIds = new Set(oldVal.map((v) => v.id));
|
const oldIds = new Set(oldVal.map((v) => v.id));
|
||||||
const changed = newIds.size !== oldIds.size || [...newIds].some((id) => !oldIds.has(id));
|
const added = newVal.some((v) => !oldIds.has(v.id));
|
||||||
|
|
||||||
if (oldVal.length > 0 && changed) {
|
if (oldVal.length > 0 && added) {
|
||||||
stepData.mappings.networkEntries = [];
|
stepData.mappings.networkEntries = [];
|
||||||
stepData.mappings.storageEntries = [];
|
stepData.mappings.storageEntries = [];
|
||||||
mappingsReady.value = false;
|
mappingsReady.value = false;
|
||||||
@@ -290,6 +309,9 @@ const onCancel = () => {
|
|||||||
:mapping-entries="stepData.mappings"
|
:mapping-entries="stepData.mappings"
|
||||||
:step-data="stepData.review"
|
:step-data="stepData.review"
|
||||||
@ready="onReviewReady"
|
@ready="onReviewReady"
|
||||||
|
@edit-selection="onEditSelection"
|
||||||
|
@edit-mappings="onEditMappings"
|
||||||
|
@remove-vm="onRemoveVm"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</CruResource>
|
</CruResource>
|
||||||
|
|||||||
@@ -68,41 +68,35 @@ export default defineComponent({
|
|||||||
return (this.value || []).map((plan) => {
|
return (this.value || []).map((plan) => {
|
||||||
const planName = plan?.metadata?.name || '-';
|
const planName = plan?.metadata?.name || '-';
|
||||||
const namespace = plan?.metadata?.namespace || '';
|
const namespace = plan?.metadata?.namespace || '';
|
||||||
const networkMap = plan?.spec?.map?.network;
|
const networkMapRef = this.getPlanMapRef(plan, 'network');
|
||||||
const storageMap = plan?.spec?.map?.storage;
|
const storageMapRef = this.getPlanMapRef(plan, 'storage');
|
||||||
const history = plan?.status?.migration?.history || [];
|
|
||||||
|
|
||||||
const migrationsMap = new Map();
|
const migrations = existing[HCI.FORKLIFT_MIGRATION]
|
||||||
|
.filter((resource) => this.ownedByPlan(resource, plan))
|
||||||
|
.map((resource) => resource?.metadata?.name)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
history
|
const matchedNetworkMap = networkMapRef ? existing[HCI.FORKLIFT_NETWORK_MAP].find((resource) => resource?.metadata?.name === networkMapRef.name && resource?.metadata?.namespace === networkMapRef.namespace) : null;
|
||||||
.map((entry) => ({
|
const matchedStorageMap = storageMapRef ? existing[HCI.FORKLIFT_STORAGE_MAP].find((resource) => resource?.metadata?.name === storageMapRef.name && resource?.metadata?.namespace === storageMapRef.namespace) : null;
|
||||||
name: entry?.migration?.name,
|
|
||||||
namespace: entry?.migration?.namespace || namespace,
|
|
||||||
}))
|
|
||||||
.filter((entry) => !!entry.name)
|
|
||||||
.forEach((entry) => {
|
|
||||||
const matched = existing[HCI.FORKLIFT_MIGRATION].find((resource) => resource?.metadata?.name === entry.name && resource?.metadata?.namespace === entry.namespace);
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
migrationsMap.set(`${ entry.namespace }/${ entry.name }`, entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const matchedNetworkMap = networkMap?.name && existing[HCI.FORKLIFT_NETWORK_MAP].find((resource) => resource?.metadata?.name === networkMap.name && resource?.metadata?.namespace === (networkMap.namespace || namespace));
|
|
||||||
const matchedStorageMap = storageMap?.name && existing[HCI.FORKLIFT_STORAGE_MAP].find((resource) => resource?.metadata?.name === storageMap.name && resource?.metadata?.namespace === (storageMap.namespace || namespace));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
planName,
|
planName,
|
||||||
namespace,
|
namespace,
|
||||||
migrations: [...migrationsMap.values()],
|
migrations,
|
||||||
networkMap: matchedNetworkMap ? {
|
networkMap: matchedNetworkMap ? {
|
||||||
namespace: networkMap.namespace || namespace,
|
namespace: matchedNetworkMap?.metadata?.namespace || networkMapRef?.namespace || namespace,
|
||||||
name: networkMap.name,
|
name: matchedNetworkMap?.metadata?.name,
|
||||||
} : null,
|
} : (networkMapRef ? {
|
||||||
|
namespace: networkMapRef.namespace,
|
||||||
|
name: networkMapRef.name,
|
||||||
|
} : null),
|
||||||
storageMap: matchedStorageMap ? {
|
storageMap: matchedStorageMap ? {
|
||||||
namespace: storageMap.namespace || namespace,
|
namespace: matchedStorageMap?.metadata?.namespace || storageMapRef?.namespace || namespace,
|
||||||
name: storageMap.name,
|
name: matchedStorageMap?.metadata?.name,
|
||||||
} : null,
|
} : (storageMapRef ? {
|
||||||
|
namespace: storageMapRef.namespace,
|
||||||
|
name: storageMapRef.name,
|
||||||
|
} : null),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -125,53 +119,73 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
resourceNames,
|
resourceNames,
|
||||||
|
|
||||||
|
getPlanMapRef(plan, mapType) {
|
||||||
|
const ref = plan?.spec?.map?.[mapType];
|
||||||
|
const name = ref?.name;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
namespace: ref?.namespace || plan?.metadata?.namespace || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a related resource (migration / network map / storage map)
|
||||||
|
* is owned by the given plan by inspecting its `metadata.ownerReferences`.
|
||||||
|
* Matches on `kind: Plan` and the plan name; when both sides expose a uid it
|
||||||
|
* must match too, to disambiguate same-named plans.
|
||||||
|
*/
|
||||||
|
ownedByPlan(resource, plan) {
|
||||||
|
const planName = plan?.metadata?.name;
|
||||||
|
const planUid = plan?.metadata?.uid;
|
||||||
|
const owners = resource?.metadata?.ownerReferences || [];
|
||||||
|
|
||||||
|
if (!planName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return owners.some((owner) => owner?.kind === 'Plan' &&
|
||||||
|
owner?.name === planName &&
|
||||||
|
(!planUid || !owner?.uid || owner.uid === planUid));
|
||||||
|
},
|
||||||
|
|
||||||
buildDeleteTargets() {
|
buildDeleteTargets() {
|
||||||
|
const existing = this.existingRelatedResources;
|
||||||
const targets = new Map();
|
const targets = new Map();
|
||||||
|
|
||||||
|
const addTarget = (type, name, namespace) => {
|
||||||
|
const ns = namespace || '';
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = `${ type }|${ ns }|${ name }`;
|
||||||
|
|
||||||
|
targets.set(key, {
|
||||||
|
type, name, namespace: ns
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
for (const plan of this.value || []) {
|
for (const plan of this.value || []) {
|
||||||
const namespace = plan?.metadata?.namespace || '';
|
const networkMapRef = this.getPlanMapRef(plan, 'network');
|
||||||
const networkMap = plan?.spec?.map?.network;
|
const storageMapRef = this.getPlanMapRef(plan, 'storage');
|
||||||
const storageMap = plan?.spec?.map?.storage;
|
|
||||||
const history = plan?.status?.migration?.history || [];
|
|
||||||
|
|
||||||
if (networkMap?.name) {
|
if (networkMapRef) {
|
||||||
const ns = networkMap.namespace || namespace;
|
addTarget(HCI.FORKLIFT_NETWORK_MAP, networkMapRef.name, networkMapRef.namespace);
|
||||||
const key = `${ HCI.FORKLIFT_NETWORK_MAP }|${ ns }|${ networkMap.name }`;
|
|
||||||
|
|
||||||
targets.set(key, {
|
|
||||||
type: HCI.FORKLIFT_NETWORK_MAP,
|
|
||||||
name: networkMap.name,
|
|
||||||
namespace: ns,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storageMap?.name) {
|
if (storageMapRef) {
|
||||||
const ns = storageMap.namespace || namespace;
|
addTarget(HCI.FORKLIFT_STORAGE_MAP, storageMapRef.name, storageMapRef.namespace);
|
||||||
const key = `${ HCI.FORKLIFT_STORAGE_MAP }|${ ns }|${ storageMap.name }`;
|
|
||||||
|
|
||||||
targets.set(key, {
|
|
||||||
type: HCI.FORKLIFT_STORAGE_MAP,
|
|
||||||
name: storageMap.name,
|
|
||||||
namespace: ns,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of history) {
|
existing[HCI.FORKLIFT_MIGRATION]
|
||||||
const migrationName = entry?.migration?.name;
|
.filter((resource) => this.ownedByPlan(resource, plan))
|
||||||
|
.forEach((resource) => addTarget(HCI.FORKLIFT_MIGRATION, resource?.metadata?.name, resource?.metadata?.namespace));
|
||||||
if (!migrationName) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const migrationNs = entry?.migration?.namespace || namespace;
|
|
||||||
const key = `${ HCI.FORKLIFT_MIGRATION }|${ migrationNs }|${ migrationName }`;
|
|
||||||
|
|
||||||
targets.set(key, {
|
|
||||||
type: HCI.FORKLIFT_MIGRATION,
|
|
||||||
name: migrationName,
|
|
||||||
namespace: migrationNs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...targets.values()];
|
return [...targets.values()];
|
||||||
@@ -264,11 +278,11 @@ export default defineComponent({
|
|||||||
<div>• {{ t('harvester.addons.vmMigration.labels.migration') }}:</div>
|
<div>• {{ t('harvester.addons.vmMigration.labels.migration') }}:</div>
|
||||||
<template v-if="summary.migrations.length">
|
<template v-if="summary.migrations.length">
|
||||||
<div
|
<div
|
||||||
v-for="migration in summary.migrations"
|
v-for="migrationName in summary.migrations"
|
||||||
:key="`${ migration.namespace }/${ migration.name }`"
|
:key="migrationName"
|
||||||
>
|
>
|
||||||
<div class="ml-20">
|
<div class="ml-20">
|
||||||
- {{ migration.name }}
|
- {{ migrationName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
@@ -24,13 +26,17 @@ export function registerAddonSideNav(store, productName, {
|
|||||||
const kickSideNav = () => {
|
const kickSideNav = () => {
|
||||||
const TRIGGER = 'ui.refresh.trigger';
|
const TRIGGER = 'ui.refresh.trigger';
|
||||||
|
|
||||||
store.dispatch('type-map/addFavorite', TRIGGER);
|
// 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.
|
||||||
|
// Use the prefs/load mutation (in-memory only)
|
||||||
|
[0, 600, 1500].forEach((delay) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const base = (store.getters['prefs/get'](FAVORITE_TYPES) || []).filter((t) => t !== TRIGGER);
|
||||||
|
|
||||||
// SideNav component seem to ignore rapid state changes.
|
store.commit('prefs/load', { key: FAVORITE_TYPES, value: [...base, TRIGGER] });
|
||||||
// Wait 600ms to ensure the toggle event triggers a re-render.
|
setTimeout(() => store.commit('prefs/load', { key: FAVORITE_TYPES, value: [...base] }), 300);
|
||||||
setTimeout(() => {
|
}, delay);
|
||||||
store.dispatch('type-map/removeFavorite', TRIGGER);
|
});
|
||||||
}, 600);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasAccessibleSchema = (t) => {
|
const hasAccessibleSchema = (t) => {
|
||||||
@@ -92,16 +98,25 @@ export function registerAddonSideNav(store, productName, {
|
|||||||
// Store is ready. Stop polling.
|
// Store is ready. Stop polling.
|
||||||
clearInterval(waitForStore);
|
clearInterval(waitForStore);
|
||||||
|
|
||||||
// Watch the specific addon resource for changes to its enabled status.
|
// Watch the addon's enabled status together with the schema availability
|
||||||
|
// of the gated types. Schemas (e.g. forklift CRDs) can load after the
|
||||||
|
// addon is already enabled, so the watcher must also re-run when they
|
||||||
|
// become accessible; otherwise the menu never updates until a refresh.
|
||||||
store.watch(
|
store.watch(
|
||||||
(state, getters) => {
|
(state, getters) => {
|
||||||
const addons = getters[`${ productName }/all`](resourceType);
|
const addons = getters[`${ productName }/all`](resourceType);
|
||||||
const addon = addons.find((a) => a.metadata.name === addonName);
|
const addon = addons.find((a) => a.metadata.name === addonName);
|
||||||
|
const isEnabled = addon?.spec?.enabled === true;
|
||||||
|
|
||||||
return addon?.spec?.enabled === true;
|
const schemaReady = requireSchema ? types.every(hasAccessibleSchema) : true;
|
||||||
|
|
||||||
|
return `${ isEnabled }:${ schemaReady }`;
|
||||||
},
|
},
|
||||||
(isEnabled) => {
|
() => {
|
||||||
setMenuVisibility(isEnabled);
|
const addons = store.getters[`${ productName }/all`](resourceType);
|
||||||
|
const addon = addons.find((a) => a.metadata.name === addonName);
|
||||||
|
|
||||||
|
setMenuVisibility(addon?.spec?.enabled === true);
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -73,8 +73,21 @@ export function buildNetworkMapEntries(entries = [], defaultNamespace) {
|
|||||||
export function buildStorageMapEntries(entries = []) {
|
export function buildStorageMapEntries(entries = []) {
|
||||||
return entries
|
return entries
|
||||||
.filter((entry) => !!entry.target)
|
.filter((entry) => !!entry.target)
|
||||||
.map((entry) => ({
|
.map((entry) => {
|
||||||
source: { name: entry.name, id: entry.id },
|
const destination = { storageClass: entry.target };
|
||||||
destination: { storageClass: entry.target },
|
|
||||||
}));
|
if (entry.volumeMode) {
|
||||||
|
destination.volumeMode = entry.volumeMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forklift StorageMap destination expects a single `accessMode` value.
|
||||||
|
if (Array.isArray(entry.accessModes) && entry.accessModes.length) {
|
||||||
|
destination.accessMode = entry.accessModes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: { name: entry.name, id: entry.id },
|
||||||
|
destination,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,17 +69,11 @@ export function vmDisks(spec, getters, errors, validatorArgs, displayKey, value)
|
|||||||
validName(getters, errors, D.name, diskNames, prefix, type, lowerType, upperType);
|
validName(getters, errors, D.name, diskNames, prefix, type, lowerType, upperType);
|
||||||
});
|
});
|
||||||
|
|
||||||
let hasBootableVolume = false;
|
|
||||||
|
|
||||||
_volumes.forEach((V, idx) => {
|
_volumes.forEach((V, idx) => {
|
||||||
const { type, typeValue } = getVolumeType(getters, V, _volumeClaimTemplates, value);
|
const { type, typeValue } = getVolumeType(getters, V, _volumeClaimTemplates, value);
|
||||||
|
|
||||||
const prefix = V.name || idx + 1;
|
const prefix = V.name || idx + 1;
|
||||||
|
|
||||||
if ([SOURCE_TYPE.IMAGE, SOURCE_TYPE.ATTACH_VOLUME, SOURCE_TYPE.CONTAINER].includes(type)) {
|
|
||||||
hasBootableVolume = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === SOURCE_TYPE.NEW || type === SOURCE_TYPE.IMAGE) {
|
if (type === SOURCE_TYPE.NEW || type === SOURCE_TYPE.IMAGE) {
|
||||||
if (!/([1-9]|[1-9][0-9]+)[a-zA-Z]+/.test(typeValue?.spec?.resources?.requests?.storage)) {
|
if (!/([1-9]|[1-9][0-9]+)[a-zA-Z]+/.test(typeValue?.spec?.resources?.requests?.storage)) {
|
||||||
const key = getters['i18n/t']('harvester.fields.size');
|
const key = getters['i18n/t']('harvester.fields.size');
|
||||||
@@ -136,13 +130,6 @@ export function vmDisks(spec, getters, errors, validatorArgs, displayKey, value)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* At least one bootable volume must be provided. (Verify only when create.)
|
|
||||||
*/
|
|
||||||
if (!hasBootableVolume && !value.links) {
|
|
||||||
errors.push(getters['i18n/t']('harvester.validation.vm.volume.needAtLeastOneBootable'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ if (process.argv.length < 3) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const prTitle = process.argv[2];
|
const prTitle = process.argv[2];
|
||||||
const prLabel = prTitle.split(':')[0].trim();
|
|
||||||
|
// Parse the conventional-commit prefix `type(scope)!:`, keeping only the type
|
||||||
|
// and dropping an optional scope and breaking-change marker.
|
||||||
|
const prefixMatch = prTitle.match(/^\s*([a-zA-Z]+)(?:\([^)]*\))?!?:/);
|
||||||
|
const prLabel = prefixMatch ? prefixMatch[1].toLowerCase() : '';
|
||||||
|
|
||||||
const config = await load({ extends: ["./commitlint.config.js"] });
|
const config = await load({ extends: ["./commitlint.config.js"] });
|
||||||
const commitPrefix = config?.rules?.['type-enum']?.[2] || [];
|
const commitPrefix = config?.rules?.['type-enum']?.[2] || [];
|
||||||
|
|||||||
@@ -524,12 +524,12 @@
|
|||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
|
|
||||||
"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0":
|
"@babel/compat-data@^7.28.6", "@babel/compat-data@^7.29.0", "@babel/compat-data@^7.29.7":
|
||||||
version "7.29.0"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d"
|
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
|
||||||
integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==
|
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
|
||||||
|
|
||||||
"@babel/core@^7.1.0", "@babel/core@^7.12.16", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
|
"@babel/core@^7.1.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
|
||||||
version "7.29.0"
|
version "7.29.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322"
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322"
|
||||||
integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==
|
integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==
|
||||||
@@ -550,7 +550,28 @@
|
|||||||
json5 "^2.2.3"
|
json5 "^2.2.3"
|
||||||
semver "^6.3.1"
|
semver "^6.3.1"
|
||||||
|
|
||||||
"@babel/generator@^7.29.0", "@babel/generator@^7.29.7", "@babel/generator@^7.7.2":
|
"@babel/core@^7.12.16", "@babel/core@^7.12.3", "@babel/core@^7.23.9":
|
||||||
|
version "7.29.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
|
||||||
|
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "^7.29.7"
|
||||||
|
"@babel/generator" "^7.29.7"
|
||||||
|
"@babel/helper-compilation-targets" "^7.29.7"
|
||||||
|
"@babel/helper-module-transforms" "^7.29.7"
|
||||||
|
"@babel/helpers" "^7.29.7"
|
||||||
|
"@babel/parser" "^7.29.7"
|
||||||
|
"@babel/template" "^7.29.7"
|
||||||
|
"@babel/traverse" "^7.29.7"
|
||||||
|
"@babel/types" "^7.29.7"
|
||||||
|
"@jridgewell/remapping" "^2.3.5"
|
||||||
|
convert-source-map "^2.0.0"
|
||||||
|
debug "^4.1.0"
|
||||||
|
gensync "^1.0.0-beta.2"
|
||||||
|
json5 "^2.2.3"
|
||||||
|
semver "^6.3.1"
|
||||||
|
|
||||||
|
"@babel/generator@^7.29.0":
|
||||||
version "7.29.7"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
|
||||||
integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
|
integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
|
||||||
@@ -561,6 +582,17 @@
|
|||||||
"@jridgewell/trace-mapping" "^0.3.28"
|
"@jridgewell/trace-mapping" "^0.3.28"
|
||||||
jsesc "^3.0.2"
|
jsesc "^3.0.2"
|
||||||
|
|
||||||
|
"@babel/generator@^7.29.7", "@babel/generator@^7.29.8", "@babel/generator@^7.7.2":
|
||||||
|
version "7.29.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.8.tgz#4b0b887885422643339e09022148a4c4ebaa4979"
|
||||||
|
integrity sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/parser" "^7.29.8"
|
||||||
|
"@babel/types" "^7.29.8"
|
||||||
|
"@jridgewell/gen-mapping" "^0.3.12"
|
||||||
|
"@jridgewell/trace-mapping" "^0.3.28"
|
||||||
|
jsesc "^3.0.2"
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure@^7.14.5", "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3", "@babel/helper-annotate-as-pure@^7.29.7":
|
"@babel/helper-annotate-as-pure@^7.14.5", "@babel/helper-annotate-as-pure@^7.27.1", "@babel/helper-annotate-as-pure@^7.27.3", "@babel/helper-annotate-as-pure@^7.29.7":
|
||||||
version "7.29.7"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz#c70fe3c6ecbdc3fd2dd1b0f498428b88b82ce47f"
|
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz#c70fe3c6ecbdc3fd2dd1b0f498428b88b82ce47f"
|
||||||
@@ -569,12 +601,12 @@
|
|||||||
"@babel/types" "^7.29.7"
|
"@babel/types" "^7.29.7"
|
||||||
|
|
||||||
"@babel/helper-compilation-targets@^7.12.16", "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.28.6":
|
"@babel/helper-compilation-targets@^7.12.16", "@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.28.6":
|
||||||
version "7.28.6"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25"
|
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
|
||||||
integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==
|
integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/compat-data" "^7.28.6"
|
"@babel/compat-data" "^7.29.7"
|
||||||
"@babel/helper-validator-option" "^7.27.1"
|
"@babel/helper-validator-option" "^7.29.7"
|
||||||
browserslist "^4.24.0"
|
browserslist "^4.24.0"
|
||||||
lru-cache "^5.1.1"
|
lru-cache "^5.1.1"
|
||||||
semver "^6.3.1"
|
semver "^6.3.1"
|
||||||
@@ -625,22 +657,22 @@
|
|||||||
"@babel/traverse" "^7.29.7"
|
"@babel/traverse" "^7.29.7"
|
||||||
"@babel/types" "^7.29.7"
|
"@babel/types" "^7.29.7"
|
||||||
|
|
||||||
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.27.1", "@babel/helper-module-imports@^7.28.6":
|
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.27.1", "@babel/helper-module-imports@^7.28.6", "@babel/helper-module-imports@^7.29.7":
|
||||||
version "7.28.6"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c"
|
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396"
|
||||||
integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==
|
integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/traverse" "^7.28.6"
|
"@babel/traverse" "^7.29.7"
|
||||||
"@babel/types" "^7.28.6"
|
"@babel/types" "^7.29.7"
|
||||||
|
|
||||||
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
|
"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6":
|
||||||
version "7.28.6"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e"
|
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
|
||||||
integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==
|
integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-module-imports" "^7.28.6"
|
"@babel/helper-module-imports" "^7.29.7"
|
||||||
"@babel/helper-validator-identifier" "^7.28.5"
|
"@babel/helper-validator-identifier" "^7.29.7"
|
||||||
"@babel/traverse" "^7.28.6"
|
"@babel/traverse" "^7.29.7"
|
||||||
|
|
||||||
"@babel/helper-optimise-call-expression@^7.29.7":
|
"@babel/helper-optimise-call-expression@^7.29.7":
|
||||||
version "7.29.7"
|
version "7.29.7"
|
||||||
@@ -690,10 +722,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
|
||||||
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
|
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
|
||||||
|
|
||||||
"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.27.1":
|
"@babel/helper-validator-option@^7.16.7", "@babel/helper-validator-option@^7.27.1", "@babel/helper-validator-option@^7.29.7":
|
||||||
version "7.27.1"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
|
||||||
integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
|
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
|
||||||
|
|
||||||
"@babel/helper-wrap-function@^7.27.1":
|
"@babel/helper-wrap-function@^7.27.1":
|
||||||
version "7.28.6"
|
version "7.28.6"
|
||||||
@@ -722,7 +754,14 @@
|
|||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.0", "@babel/parser@^7.29.0", "@babel/parser@^7.29.7", "@babel/parser@^7.7.0":
|
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.0", "@babel/parser@^7.29.0", "@babel/parser@^7.29.7", "@babel/parser@^7.29.8":
|
||||||
|
version "7.29.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.8.tgz#9653716a2f10c677b98fbc63d4bfb000c302cf17"
|
||||||
|
integrity sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.29.8"
|
||||||
|
|
||||||
|
"@babel/parser@^7.7.0":
|
||||||
version "7.29.7"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
|
||||||
integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
|
integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
|
||||||
@@ -888,11 +927,11 @@
|
|||||||
"@babel/helper-plugin-utils" "^7.8.0"
|
"@babel/helper-plugin-utils" "^7.8.0"
|
||||||
|
|
||||||
"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.27.1":
|
"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.27.1":
|
||||||
version "7.28.6"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee"
|
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz#622c16f9ad63782fe6e83dadc7e40330744b7f1e"
|
||||||
integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==
|
integrity sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-plugin-utils" "^7.28.6"
|
"@babel/helper-plugin-utils" "^7.29.7"
|
||||||
|
|
||||||
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
|
"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
@@ -1493,7 +1532,20 @@
|
|||||||
"@babel/parser" "^7.29.7"
|
"@babel/parser" "^7.29.7"
|
||||||
"@babel/types" "^7.29.7"
|
"@babel/types" "^7.29.7"
|
||||||
|
|
||||||
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
|
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7":
|
||||||
|
version "7.29.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.8.tgz#4111014cdc71a0f95d9471907590baa0b8a6b28a"
|
||||||
|
integrity sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "^7.29.7"
|
||||||
|
"@babel/generator" "^7.29.8"
|
||||||
|
"@babel/helper-globals" "^7.29.7"
|
||||||
|
"@babel/parser" "^7.29.8"
|
||||||
|
"@babel/template" "^7.29.7"
|
||||||
|
"@babel/types" "^7.29.8"
|
||||||
|
debug "^4.3.1"
|
||||||
|
|
||||||
|
"@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2":
|
||||||
version "7.29.7"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
|
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
|
||||||
integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
|
integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==
|
||||||
@@ -1506,7 +1558,15 @@
|
|||||||
"@babel/types" "^7.29.7"
|
"@babel/types" "^7.29.7"
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
|
|
||||||
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
|
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.7", "@babel/types@^7.29.8", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
|
||||||
|
version "7.29.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863"
|
||||||
|
integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-string-parser" "^7.29.7"
|
||||||
|
"@babel/helper-validator-identifier" "^7.29.7"
|
||||||
|
|
||||||
|
"@babel/types@^7.29.0", "@babel/types@^7.7.0":
|
||||||
version "7.29.7"
|
version "7.29.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
|
||||||
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
|
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
|
||||||
@@ -1679,13 +1739,20 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
|
||||||
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
"@eslint-community/eslint-utils@^4.2.0":
|
||||||
version "4.9.1"
|
version "4.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
|
||||||
integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
|
integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^3.4.3"
|
eslint-visitor-keys "^3.4.3"
|
||||||
|
|
||||||
|
"@eslint-community/eslint-utils@^4.4.0":
|
||||||
|
version "4.10.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6"
|
||||||
|
integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==
|
||||||
|
dependencies:
|
||||||
|
eslint-visitor-keys "^3.4.3"
|
||||||
|
|
||||||
"@eslint-community/regexpp@^4.4.0":
|
"@eslint-community/regexpp@^4.4.0":
|
||||||
version "4.12.2"
|
version "4.12.2"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b"
|
||||||
@@ -2181,15 +2248,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||||
|
|
||||||
"@rancher/icons@2.0.60":
|
"@rancher/icons@2.0.62":
|
||||||
version "2.0.60"
|
version "2.0.62"
|
||||||
resolved "https://registry.yarnpkg.com/@rancher/icons/-/icons-2.0.60.tgz#a7d8bd39a007ef8ecf1cb7bfe27d82f64f9cd75c"
|
resolved "https://registry.yarnpkg.com/@rancher/icons/-/icons-2.0.62.tgz#42d3aa89d03a2e0e551332e99c8102dfdd422746"
|
||||||
integrity sha512-5bbXVOc4Uk2OmACX67i1e1jOTeLg87T22u+alhwEZX+/WzBEH/B0DPVqSbuYQtKYkH0Me4ojnOVCzZ2HuuLHWQ==
|
integrity sha512-ywGk1NSrIdN1zllpbDRU3ESVW/D0ExRZFBz+YPGAo9G08VzWxKIcT5GG5Nd2GXLTcUestlq1JwkKDaVAM8hkmQ==
|
||||||
|
|
||||||
"@rancher/shell@3.0.12-rc.3":
|
"@rancher/shell@3.0.12-rc.7":
|
||||||
version "3.0.12-rc.3"
|
version "3.0.12-rc.7"
|
||||||
resolved "https://registry.yarnpkg.com/@rancher/shell/-/shell-3.0.12-rc.3.tgz#566ea39fac704fbed2e0bc070f47b5bb3b6a92fb"
|
resolved "https://registry.yarnpkg.com/@rancher/shell/-/shell-3.0.12-rc.7.tgz#509b42d1a40fc8f399dc8c15fd07f2ea4361af54"
|
||||||
integrity sha512-4mKX3Ou2t/P8ko5Tb8UuuxgttgQPPX66Rlq8vC6IftGowJ+ExtM6FJDg4bKy1Lu3DrDERHoQLmTHehDScRnSEw==
|
integrity sha512-aA2uX5roVhJtj8p09OQNREhFraa9MqSrjXjbAC+3uRLniQkhqx+INKpyBDvhkDy0wd7XWIGbX3cfl+Kz6mwSYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@aws-sdk/client-ec2" "3.1041.0"
|
"@aws-sdk/client-ec2" "3.1041.0"
|
||||||
"@aws-sdk/client-eks" "3.1041.0"
|
"@aws-sdk/client-eks" "3.1041.0"
|
||||||
@@ -2201,21 +2268,28 @@
|
|||||||
"@babel/preset-typescript" "7.16.7"
|
"@babel/preset-typescript" "7.16.7"
|
||||||
"@novnc/novnc" "1.2.0"
|
"@novnc/novnc" "1.2.0"
|
||||||
"@popperjs/core" "2.11.8"
|
"@popperjs/core" "2.11.8"
|
||||||
"@rancher/icons" "2.0.60"
|
"@rancher/icons" "2.0.62"
|
||||||
"@smithy/fetch-http-handler" "5.1.1"
|
"@smithy/fetch-http-handler" "5.1.1"
|
||||||
"@types/is-url" "1.2.30"
|
"@types/is-url" "1.2.30"
|
||||||
"@types/node" "25.3.3"
|
"@types/node" "25.3.3"
|
||||||
"@types/semver" "^7.5.8"
|
"@types/semver" "^7.5.8"
|
||||||
"@typescript-eslint/eslint-plugin" "5.62.0"
|
"@typescript-eslint/eslint-plugin" "5.62.0"
|
||||||
"@typescript-eslint/parser" "5.62.0"
|
"@typescript-eslint/parser" "5.62.0"
|
||||||
|
"@vee-validate/zod" "4.15.0"
|
||||||
"@vue/cli-plugin-babel" "~5.0.0"
|
"@vue/cli-plugin-babel" "~5.0.0"
|
||||||
"@vue/cli-plugin-typescript" "~5.0.0"
|
"@vue/cli-plugin-typescript" "~5.0.0"
|
||||||
"@vue/cli-service" "5.0.8"
|
"@vue/cli-service" "5.0.8"
|
||||||
"@vue/test-utils" "2.4.6"
|
"@vue/test-utils" "2.4.6"
|
||||||
"@vue/vue3-jest" "27.0.0"
|
"@vue/vue3-jest" "27.0.0"
|
||||||
|
"@xterm/addon-canvas" "0.7.0"
|
||||||
|
"@xterm/addon-fit" "0.11.0"
|
||||||
|
"@xterm/addon-search" "0.16.0"
|
||||||
|
"@xterm/addon-web-links" "0.12.0"
|
||||||
|
"@xterm/addon-webgl" "0.19.0"
|
||||||
|
"@xterm/xterm" "6.0.0"
|
||||||
add "2.0.6"
|
add "2.0.6"
|
||||||
ansi_up "5.0.0"
|
ansi_up "5.0.0"
|
||||||
axios "1.15.2"
|
axios "1.16.0"
|
||||||
axios-retry "3.1.9"
|
axios-retry "3.1.9"
|
||||||
babel-eslint "10.1.0"
|
babel-eslint "10.1.0"
|
||||||
babel-preset-vue "2.0.2"
|
babel-preset-vue "2.0.2"
|
||||||
@@ -2248,14 +2322,13 @@
|
|||||||
eslint-plugin-n "15.2.0"
|
eslint-plugin-n "15.2.0"
|
||||||
eslint-plugin-vue "9.32.0"
|
eslint-plugin-vue "9.32.0"
|
||||||
event-target-shim "5.0.1"
|
event-target-shim "5.0.1"
|
||||||
express "4.17.1"
|
express "4.22.2"
|
||||||
file-saver "2.0.2"
|
file-saver "2.0.5"
|
||||||
floating-vue "5.2.2"
|
floating-vue "5.2.2"
|
||||||
focus-trap "7.6.5"
|
focus-trap "7.6.5"
|
||||||
frontmatter-markdown-loader "3.7.0"
|
frontmatter-markdown-loader "3.7.0"
|
||||||
identicon.js "2.3.3"
|
identicon.js "2.3.3"
|
||||||
intl-messageformat "7.8.4"
|
intl-messageformat "7.8.4"
|
||||||
ip "2.0.1"
|
|
||||||
is-url "1.2.4"
|
is-url "1.2.4"
|
||||||
jest "27.5.1"
|
jest "27.5.1"
|
||||||
jest-serializer-vue "2.0.2"
|
jest-serializer-vue "2.0.2"
|
||||||
@@ -2292,13 +2365,8 @@
|
|||||||
webpack-virtual-modules "0.6.2"
|
webpack-virtual-modules "0.6.2"
|
||||||
worker-loader "3.0.8"
|
worker-loader "3.0.8"
|
||||||
xmlbuilder2 "4.0.1"
|
xmlbuilder2 "4.0.1"
|
||||||
xterm "5.2.1"
|
|
||||||
xterm-addon-canvas "0.5.0"
|
|
||||||
xterm-addon-fit "0.8.0"
|
|
||||||
xterm-addon-search "0.13.0"
|
|
||||||
xterm-addon-web-links "0.9.0"
|
|
||||||
xterm-addon-webgl "0.16.0"
|
|
||||||
yarn "1.22.22"
|
yarn "1.22.22"
|
||||||
|
zod "3.24.3"
|
||||||
|
|
||||||
"@rtsao/scc@^1.1.0":
|
"@rtsao/scc@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
@@ -2742,9 +2810,9 @@
|
|||||||
"@types/json-schema" "*"
|
"@types/json-schema" "*"
|
||||||
|
|
||||||
"@types/estree@*", "@types/estree@^1.0.8":
|
"@types/estree@*", "@types/estree@^1.0.8":
|
||||||
version "1.0.8"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24"
|
||||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
|
||||||
|
|
||||||
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33":
|
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33":
|
||||||
version "4.19.8"
|
version "4.19.8"
|
||||||
@@ -2824,10 +2892,10 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
|
||||||
|
|
||||||
"@types/lodash@4.17.24":
|
"@types/lodash@4.17.25":
|
||||||
version "4.17.24"
|
version "4.17.25"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.25.tgz#69765ac7bcddb0eb072961cf292524a8f5b3c2c0"
|
||||||
integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==
|
integrity sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==
|
||||||
|
|
||||||
"@types/mime@^1":
|
"@types/mime@^1":
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
@@ -2846,10 +2914,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/node@*", "@types/node@25.3.3", "@types/node@25.9.4":
|
"@types/node@*", "@types/node@25.3.3", "@types/node@25.9.5":
|
||||||
version "25.9.4"
|
version "25.9.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.4.tgz#18b63c47f88c1fbbed9d55ea2b66ffd494a47001"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.5.tgz#0fefc09e6e82e94cde291bacf43522e989eb01a4"
|
||||||
integrity sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==
|
integrity sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types ">=7.24.0 <7.24.7"
|
undici-types ">=7.24.0 <7.24.7"
|
||||||
|
|
||||||
@@ -3098,6 +3166,14 @@
|
|||||||
"@typescript-eslint/types" "5.62.0"
|
"@typescript-eslint/types" "5.62.0"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
|
"@vee-validate/zod@4.15.0":
|
||||||
|
version "4.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vee-validate/zod/-/zod-4.15.0.tgz#2de193da114eda9f873b5a677f13c23bcecb58aa"
|
||||||
|
integrity sha512-MpvIKiyg9X5yD8bJW0no2AU7wtR2T5mrvD9tuPRiie951sU2n6QKgMV38qKKOiqFBCxsMSjIuLLLV3V5kVE4nQ==
|
||||||
|
dependencies:
|
||||||
|
type-fest "^4.8.3"
|
||||||
|
vee-validate "4.15.0"
|
||||||
|
|
||||||
"@vue-flow/background@^1.3.0":
|
"@vue-flow/background@^1.3.0":
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@vue-flow/background/-/background-1.3.2.tgz#0c90cd05e5d60da017bbaf5a1c3eb6af7ed9b778"
|
resolved "https://registry.yarnpkg.com/@vue-flow/background/-/background-1.3.2.tgz#0c90cd05e5d60da017bbaf5a1c3eb6af7ed9b778"
|
||||||
@@ -3690,6 +3766,36 @@
|
|||||||
"@webassemblyjs/ast" "1.14.1"
|
"@webassemblyjs/ast" "1.14.1"
|
||||||
"@xtuc/long" "4.2.2"
|
"@xtuc/long" "4.2.2"
|
||||||
|
|
||||||
|
"@xterm/addon-canvas@0.7.0":
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.7.0.tgz#d3a3835f3634e0106e108661315ae14da23e8dd7"
|
||||||
|
integrity sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw==
|
||||||
|
|
||||||
|
"@xterm/addon-fit@0.11.0":
|
||||||
|
version "0.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@xterm/addon-fit/-/addon-fit-0.11.0.tgz#ba4778b69fcc9044a060c2176bbe077657d7b37e"
|
||||||
|
integrity sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==
|
||||||
|
|
||||||
|
"@xterm/addon-search@0.16.0":
|
||||||
|
version "0.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.16.0.tgz#ddadcbc6e556e45fc4b153f8a0efdf2aa1456b82"
|
||||||
|
integrity sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==
|
||||||
|
|
||||||
|
"@xterm/addon-web-links@0.12.0":
|
||||||
|
version "0.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz#bc34ae46f4c12012256d451ac2b9be791c0b6bfa"
|
||||||
|
integrity sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==
|
||||||
|
|
||||||
|
"@xterm/addon-webgl@0.19.0":
|
||||||
|
version "0.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.19.0.tgz#02533c3f7c0af3ac9a44bbb095cbd47d48e90c25"
|
||||||
|
integrity sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A==
|
||||||
|
|
||||||
|
"@xterm/xterm@6.0.0":
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-6.0.0.tgz#93637b0f2ee3a70718b5746a27c9c506af16745b"
|
||||||
|
integrity sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==
|
||||||
|
|
||||||
"@xtuc/ieee754@^1.2.0":
|
"@xtuc/ieee754@^1.2.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||||
@@ -3730,7 +3836,7 @@ abort-controller@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
event-target-shim "^5.0.0"
|
event-target-shim "^5.0.0"
|
||||||
|
|
||||||
accepts@~1.3.7, accepts@~1.3.8:
|
accepts@~1.3.8:
|
||||||
version "1.3.8"
|
version "1.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
|
||||||
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
|
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
|
||||||
@@ -3773,7 +3879,12 @@ acorn@^7.1.1, acorn@^7.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||||
|
|
||||||
acorn@^8.0.4, acorn@^8.0.5, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0, acorn@^8.2.4, acorn@^8.9.0:
|
acorn@^8.0.4, acorn@^8.0.5, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0:
|
||||||
|
version "8.18.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.18.0.tgz#4faf01b2d6d326bfeed97aea1f52220b5f4c1940"
|
||||||
|
integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==
|
||||||
|
|
||||||
|
acorn@^8.2.4, acorn@^8.9.0:
|
||||||
version "8.16.0"
|
version "8.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
|
||||||
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
|
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
|
||||||
@@ -3822,7 +3933,7 @@ ajv-keywords@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal "^3.1.3"
|
fast-deep-equal "^3.1.3"
|
||||||
|
|
||||||
ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5:
|
ajv@^6.10.0, ajv@^6.12.4:
|
||||||
version "6.14.0"
|
version "6.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a"
|
||||||
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
|
integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==
|
||||||
@@ -3832,6 +3943,16 @@ ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5:
|
|||||||
json-schema-traverse "^0.4.1"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
|
ajv@^6.12.2, ajv@^6.12.5:
|
||||||
|
version "6.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492"
|
||||||
|
integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal "^3.1.1"
|
||||||
|
fast-json-stable-stringify "^2.0.0"
|
||||||
|
json-schema-traverse "^0.4.1"
|
||||||
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
ajv@^8.0.0, ajv@^8.0.1, ajv@^8.11.0, ajv@^8.9.0:
|
ajv@^8.0.0, ajv@^8.0.1, ajv@^8.11.0, ajv@^8.9.0:
|
||||||
version "8.18.0"
|
version "8.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.18.0.tgz#8864186b6738d003eb3a933172bb3833e10cefbc"
|
||||||
@@ -4093,12 +4214,12 @@ axios-retry@3.1.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-retry-allowed "^1.1.0"
|
is-retry-allowed "^1.1.0"
|
||||||
|
|
||||||
axios@1.15.2:
|
axios@1.16.0:
|
||||||
version "1.15.2"
|
version "1.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.2.tgz#eb8fb6d30349abace6ade5b4cb4d9e8a0dc23e5b"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.16.0.tgz#f8e5dd931cef2a5f8c32216d5784eda2f8750eb7"
|
||||||
integrity sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==
|
integrity sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects "^1.15.11"
|
follow-redirects "^1.16.0"
|
||||||
form-data "^4.0.5"
|
form-data "^4.0.5"
|
||||||
proxy-from-env "^2.1.0"
|
proxy-from-env "^2.1.0"
|
||||||
|
|
||||||
@@ -4328,26 +4449,10 @@ bn.js@^5.2.1, bn.js@^5.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.3.tgz#16a9e409616b23fef3ccbedb8d42f13bff80295e"
|
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.3.tgz#16a9e409616b23fef3ccbedb8d42f13bff80295e"
|
||||||
integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==
|
integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==
|
||||||
|
|
||||||
body-parser@1.19.0:
|
body-parser@~1.20.5:
|
||||||
version "1.19.0"
|
version "1.20.6"
|
||||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76"
|
||||||
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==
|
||||||
dependencies:
|
|
||||||
bytes "3.1.0"
|
|
||||||
content-type "~1.0.4"
|
|
||||||
debug "2.6.9"
|
|
||||||
depd "~1.1.2"
|
|
||||||
http-errors "1.7.2"
|
|
||||||
iconv-lite "0.4.24"
|
|
||||||
on-finished "~2.3.0"
|
|
||||||
qs "6.7.0"
|
|
||||||
raw-body "2.4.0"
|
|
||||||
type-is "~1.6.17"
|
|
||||||
|
|
||||||
body-parser@~1.20.3:
|
|
||||||
version "1.20.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f"
|
|
||||||
integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
bytes "~3.1.2"
|
bytes "~3.1.2"
|
||||||
content-type "~1.0.5"
|
content-type "~1.0.5"
|
||||||
@@ -4357,7 +4462,7 @@ body-parser@~1.20.3:
|
|||||||
http-errors "~2.0.1"
|
http-errors "~2.0.1"
|
||||||
iconv-lite "~0.4.24"
|
iconv-lite "~0.4.24"
|
||||||
on-finished "~2.4.1"
|
on-finished "~2.4.1"
|
||||||
qs "~6.14.0"
|
qs "~6.15.1"
|
||||||
raw-body "~2.5.3"
|
raw-body "~2.5.3"
|
||||||
type-is "~1.6.18"
|
type-is "~1.6.18"
|
||||||
unpipe "~1.0.0"
|
unpipe "~1.0.0"
|
||||||
@@ -4535,11 +4640,6 @@ builtins@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^7.0.0"
|
semver "^7.0.0"
|
||||||
|
|
||||||
bytes@3.1.0:
|
|
||||||
version "3.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
|
||||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
|
||||||
|
|
||||||
bytes@3.1.2, bytes@~3.1.2:
|
bytes@3.1.2, bytes@~3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
|
||||||
@@ -5026,13 +5126,6 @@ constants-browserify@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
|
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
|
||||||
integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==
|
integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==
|
||||||
|
|
||||||
content-disposition@0.5.3:
|
|
||||||
version "0.5.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
|
|
||||||
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
|
|
||||||
dependencies:
|
|
||||||
safe-buffer "5.1.2"
|
|
||||||
|
|
||||||
content-disposition@~0.5.4:
|
content-disposition@~0.5.4:
|
||||||
version "0.5.4"
|
version "0.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
|
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
|
||||||
@@ -5079,7 +5172,7 @@ convert-source-map@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||||
|
|
||||||
cookie-signature@1.0.6, cookie-signature@~1.0.6:
|
cookie-signature@~1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||||
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
|
||||||
@@ -5092,7 +5185,7 @@ cookie-universal@2.2.2:
|
|||||||
"@types/cookie" "^0.3.3"
|
"@types/cookie" "^0.3.3"
|
||||||
cookie "^0.4.0"
|
cookie "^0.4.0"
|
||||||
|
|
||||||
cookie@0.4.0, cookie@^0.4.0:
|
cookie@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||||
@@ -6110,12 +6203,12 @@ end-of-stream@^1.1.0:
|
|||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
|
|
||||||
enhanced-resolve@^5.0.0, enhanced-resolve@^5.20.0:
|
enhanced-resolve@^5.0.0, enhanced-resolve@^5.20.0:
|
||||||
version "5.20.1"
|
version "5.24.5"
|
||||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz#eeeb3966bea62c348c40a0cc9e7912e2557d0be0"
|
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz#b4dad3255b7545f07ba5535189868e9f85f47573"
|
||||||
integrity sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==
|
integrity sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs "^4.2.4"
|
graceful-fs "^4.2.4"
|
||||||
tapable "^2.3.0"
|
tapable "^2.3.3"
|
||||||
|
|
||||||
enquirer@^2.3.5:
|
enquirer@^2.3.5:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
@@ -6656,50 +6749,14 @@ expect@^27.5.1:
|
|||||||
jest-matcher-utils "^27.5.1"
|
jest-matcher-utils "^27.5.1"
|
||||||
jest-message-util "^27.5.1"
|
jest-message-util "^27.5.1"
|
||||||
|
|
||||||
express@4.17.1:
|
express@4.22.2, express@^4.17.3:
|
||||||
version "4.17.1"
|
version "4.22.2"
|
||||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
resolved "https://registry.yarnpkg.com/express/-/express-4.22.2.tgz#c17ae0981e5efc24b22272f0e041c4662503b700"
|
||||||
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
integrity sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==
|
||||||
dependencies:
|
|
||||||
accepts "~1.3.7"
|
|
||||||
array-flatten "1.1.1"
|
|
||||||
body-parser "1.19.0"
|
|
||||||
content-disposition "0.5.3"
|
|
||||||
content-type "~1.0.4"
|
|
||||||
cookie "0.4.0"
|
|
||||||
cookie-signature "1.0.6"
|
|
||||||
debug "2.6.9"
|
|
||||||
depd "~1.1.2"
|
|
||||||
encodeurl "~1.0.2"
|
|
||||||
escape-html "~1.0.3"
|
|
||||||
etag "~1.8.1"
|
|
||||||
finalhandler "~1.1.2"
|
|
||||||
fresh "0.5.2"
|
|
||||||
merge-descriptors "1.0.1"
|
|
||||||
methods "~1.1.2"
|
|
||||||
on-finished "~2.3.0"
|
|
||||||
parseurl "~1.3.3"
|
|
||||||
path-to-regexp "0.1.7"
|
|
||||||
proxy-addr "~2.0.5"
|
|
||||||
qs "6.7.0"
|
|
||||||
range-parser "~1.2.1"
|
|
||||||
safe-buffer "5.1.2"
|
|
||||||
send "0.17.1"
|
|
||||||
serve-static "1.14.1"
|
|
||||||
setprototypeof "1.1.1"
|
|
||||||
statuses "~1.5.0"
|
|
||||||
type-is "~1.6.18"
|
|
||||||
utils-merge "1.0.1"
|
|
||||||
vary "~1.1.2"
|
|
||||||
|
|
||||||
express@^4.17.3:
|
|
||||||
version "4.22.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069"
|
|
||||||
integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
accepts "~1.3.8"
|
accepts "~1.3.8"
|
||||||
array-flatten "1.1.1"
|
array-flatten "1.1.1"
|
||||||
body-parser "~1.20.3"
|
body-parser "~1.20.5"
|
||||||
content-disposition "~0.5.4"
|
content-disposition "~0.5.4"
|
||||||
content-type "~1.0.4"
|
content-type "~1.0.4"
|
||||||
cookie "~0.7.1"
|
cookie "~0.7.1"
|
||||||
@@ -6718,7 +6775,7 @@ express@^4.17.3:
|
|||||||
parseurl "~1.3.3"
|
parseurl "~1.3.3"
|
||||||
path-to-regexp "~0.1.12"
|
path-to-regexp "~0.1.12"
|
||||||
proxy-addr "~2.0.7"
|
proxy-addr "~2.0.7"
|
||||||
qs "~6.14.0"
|
qs "~6.15.1"
|
||||||
range-parser "~1.2.1"
|
range-parser "~1.2.1"
|
||||||
safe-buffer "5.2.1"
|
safe-buffer "5.2.1"
|
||||||
send "~0.19.0"
|
send "~0.19.0"
|
||||||
@@ -6825,10 +6882,10 @@ file-entry-cache@^6.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flat-cache "^3.0.4"
|
flat-cache "^3.0.4"
|
||||||
|
|
||||||
file-saver@2.0.2:
|
file-saver@2.0.5:
|
||||||
version "2.0.2"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.2.tgz#06d6e728a9ea2df2cce2f8d9e84dfcdc338ec17a"
|
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
|
||||||
integrity sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==
|
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
|
||||||
|
|
||||||
filelist@^1.0.4:
|
filelist@^1.0.4:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
@@ -6844,19 +6901,6 @@ fill-range@^7.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range "^5.0.1"
|
to-regex-range "^5.0.1"
|
||||||
|
|
||||||
finalhandler@~1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
|
||||||
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
|
||||||
dependencies:
|
|
||||||
debug "2.6.9"
|
|
||||||
encodeurl "~1.0.2"
|
|
||||||
escape-html "~1.0.3"
|
|
||||||
on-finished "~2.3.0"
|
|
||||||
parseurl "~1.3.3"
|
|
||||||
statuses "~1.5.0"
|
|
||||||
unpipe "~1.0.0"
|
|
||||||
|
|
||||||
finalhandler@~1.3.1:
|
finalhandler@~1.3.1:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88"
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88"
|
||||||
@@ -6937,7 +6981,7 @@ focus-trap@7.6.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tabbable "^6.2.0"
|
tabbable "^6.2.0"
|
||||||
|
|
||||||
follow-redirects@1.16.0, follow-redirects@^1.0.0, follow-redirects@^1.15.11:
|
follow-redirects@1.16.0, follow-redirects@^1.0.0, follow-redirects@^1.16.0:
|
||||||
version "1.16.0"
|
version "1.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc"
|
||||||
integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==
|
integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==
|
||||||
@@ -7231,7 +7275,7 @@ gopd@^1.0.1, gopd@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
|
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
|
||||||
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
|
||||||
|
|
||||||
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
|
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
|
||||||
version "4.2.11"
|
version "4.2.11"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||||
@@ -7338,10 +7382,10 @@ hasha@^5.0.0:
|
|||||||
is-stream "^2.0.0"
|
is-stream "^2.0.0"
|
||||||
type-fest "^0.8.0"
|
type-fest "^0.8.0"
|
||||||
|
|
||||||
hasown@^2.0.2:
|
hasown@^2.0.2, hasown@^2.0.3:
|
||||||
version "2.0.2"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
|
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
|
||||||
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.2"
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
@@ -7433,9 +7477,9 @@ html-tags@^2.0.0:
|
|||||||
integrity sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==
|
integrity sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==
|
||||||
|
|
||||||
html-webpack-plugin@^5.0.0, html-webpack-plugin@^5.1.0:
|
html-webpack-plugin@^5.0.0, html-webpack-plugin@^5.1.0:
|
||||||
version "5.6.7"
|
version "5.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz#429bab4e12abf3c07e1c608886608e2df2c06b11"
|
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.8.tgz#5425dbc63eb533ffa59e92342be94894a125fc23"
|
||||||
integrity sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==
|
integrity sha512-MZmKQcTnhEh1SPSyMiEytIeDZDUoBZVorNHivQGXMASHf/BSGGOrKa2xQ5bGx3TCe1n109ecCt+cpww7wwWhKA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/html-minifier-terser" "^6.0.0"
|
"@types/html-minifier-terser" "^6.0.0"
|
||||||
html-minifier-terser "^6.0.2"
|
html-minifier-terser "^6.0.2"
|
||||||
@@ -7458,7 +7502,7 @@ http-deceiver@^1.2.7:
|
|||||||
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
||||||
integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==
|
integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==
|
||||||
|
|
||||||
http-errors@1.7.2, http-errors@~1.7.2:
|
http-errors@~1.7.2:
|
||||||
version "1.7.2"
|
version "1.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||||
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
|
||||||
@@ -7780,7 +7824,14 @@ is-callable@^1.2.7:
|
|||||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
|
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
|
||||||
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
|
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
|
||||||
|
|
||||||
is-core-module@^2.13.0, is-core-module@^2.16.1, is-core-module@^2.3.0:
|
is-core-module@^2.13.0, is-core-module@^2.16.1:
|
||||||
|
version "2.16.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082"
|
||||||
|
integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==
|
||||||
|
dependencies:
|
||||||
|
hasown "^2.0.3"
|
||||||
|
|
||||||
|
is-core-module@^2.3.0:
|
||||||
version "2.16.1"
|
version "2.16.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
|
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
|
||||||
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
|
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
|
||||||
@@ -8630,7 +8681,7 @@ js-yaml-loader@1.2.2:
|
|||||||
loader-utils "^1.2.3"
|
loader-utils "^1.2.3"
|
||||||
un-eval "^1.2.0"
|
un-eval "^1.2.0"
|
||||||
|
|
||||||
js-yaml@4.1.1, js-yaml@^4.1.0, js-yaml@^4.1.1:
|
js-yaml@4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
|
||||||
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
|
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
|
||||||
@@ -8645,6 +8696,13 @@ js-yaml@^3.13.1:
|
|||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
|
|
||||||
|
js-yaml@^4.1.0, js-yaml@^4.1.1:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.0.tgz#d1900572a7f7cf0b5f540c83673e60bad3436592"
|
||||||
|
integrity sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==
|
||||||
|
dependencies:
|
||||||
|
argparse "^2.0.1"
|
||||||
|
|
||||||
jsdom@^16.6.0:
|
jsdom@^16.6.0:
|
||||||
version "16.7.0"
|
version "16.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
|
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
|
||||||
@@ -9103,11 +9161,6 @@ meow@^12.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6"
|
resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6"
|
||||||
integrity sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==
|
integrity sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==
|
||||||
|
|
||||||
merge-descriptors@1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
|
||||||
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
|
|
||||||
|
|
||||||
merge-descriptors@1.0.3:
|
merge-descriptors@1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
|
||||||
@@ -9876,11 +9929,6 @@ path-parse@^1.0.7:
|
|||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
|
|
||||||
path-to-regexp@0.1.7:
|
|
||||||
version "0.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
|
||||||
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
|
|
||||||
|
|
||||||
path-to-regexp@~0.1.12:
|
path-to-regexp@~0.1.12:
|
||||||
version "0.1.13"
|
version "0.1.13"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz#9b22ec16bc3ab88d05a0c7e369869421401ab17d"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz#9b22ec16bc3ab88d05a0c7e369869421401ab17d"
|
||||||
@@ -9924,9 +9972,9 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1:
|
|||||||
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
|
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
|
||||||
|
|
||||||
picomatch@^4.0.3:
|
picomatch@^4.0.3:
|
||||||
version "4.0.4"
|
version "4.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f"
|
||||||
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
|
integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==
|
||||||
|
|
||||||
pirates@^4.0.4:
|
pirates@^4.0.4:
|
||||||
version "4.0.7"
|
version "4.0.7"
|
||||||
@@ -10193,9 +10241,9 @@ postcss-selector-parser@^6.0.15, postcss-selector-parser@^6.0.2, postcss-selecto
|
|||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
postcss-selector-parser@^7.0.0:
|
postcss-selector-parser@^7.0.0:
|
||||||
version "7.1.1"
|
version "7.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz#e75d2e0d843f620e5df69076166f4e16f891cb9f"
|
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz#ee7b090724eb60739b530660e5b402200e694a97"
|
||||||
integrity sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==
|
integrity sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==
|
||||||
dependencies:
|
dependencies:
|
||||||
cssesc "^3.0.0"
|
cssesc "^3.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
@@ -10317,7 +10365,7 @@ proto-list@~1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
|
||||||
integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
|
integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
|
||||||
|
|
||||||
proxy-addr@~2.0.5, proxy-addr@~2.0.7:
|
proxy-addr@~2.0.7:
|
||||||
version "2.0.7"
|
version "2.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
|
||||||
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
|
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
|
||||||
@@ -10377,10 +10425,10 @@ punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.0, punycode@^2.3.1:
|
|||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
|
||||||
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
|
||||||
|
|
||||||
qs@6.15.3, qs@6.7.0, qs@^6.12.3, qs@~6.14.0:
|
qs@6.16.0, qs@^6.12.3, qs@~6.15.1:
|
||||||
version "6.15.3"
|
version "6.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.16.0.tgz#c22c723a28a920f3aacdce8289fabd43eccb79fd"
|
||||||
integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==
|
integrity sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==
|
||||||
dependencies:
|
dependencies:
|
||||||
es-define-property "^1.0.1"
|
es-define-property "^1.0.1"
|
||||||
side-channel "^1.1.1"
|
side-channel "^1.1.1"
|
||||||
@@ -10420,16 +10468,6 @@ range-parser@^1.2.1, range-parser@~1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||||
|
|
||||||
raw-body@2.4.0:
|
|
||||||
version "2.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
|
||||||
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
|
|
||||||
dependencies:
|
|
||||||
bytes "3.1.0"
|
|
||||||
http-errors "1.7.2"
|
|
||||||
iconv-lite "0.4.24"
|
|
||||||
unpipe "1.0.0"
|
|
||||||
|
|
||||||
raw-body@~2.5.3:
|
raw-body@~2.5.3:
|
||||||
version "2.5.3"
|
version "2.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2"
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2"
|
||||||
@@ -10697,10 +10735,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.3:
|
|||||||
hash-base "^3.1.2"
|
hash-base "^3.1.2"
|
||||||
inherits "^2.0.4"
|
inherits "^2.0.4"
|
||||||
|
|
||||||
roarr@7.21.6:
|
roarr@7.21.7:
|
||||||
version "7.21.6"
|
version "7.21.7"
|
||||||
resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.21.6.tgz#91c7f0c4b83de49a76b801e73782086baf4a7c8d"
|
resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.21.7.tgz#b77c762660378234e1370c3e333596a744c0f6b1"
|
||||||
integrity sha512-bE9gaDGzWu4BxkyxUEVHfnHruvF4gX9NAZBz86EVJoun2J18exVR/3Wwk0hhu8MWXtabkTVveNcOk1lzHaZbVw==
|
integrity sha512-6X9ID9jb83kPkDxm5xLb9rZdGm6ksdDjpeL0iW5weKelJFi+NVBSowRpEuH8CdpX2x+7RW0TZaezibE7yQB7pg==
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-printf "^1.6.9"
|
fast-printf "^1.6.9"
|
||||||
safe-stable-stringify "^2.4.3"
|
safe-stable-stringify "^2.4.3"
|
||||||
@@ -10734,16 +10772,16 @@ safe-array-concat@^1.1.3:
|
|||||||
has-symbols "^1.1.0"
|
has-symbols "^1.1.0"
|
||||||
isarray "^2.0.5"
|
isarray "^2.0.5"
|
||||||
|
|
||||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
|
||||||
version "5.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
|
||||||
|
|
||||||
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
|
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
|
|
||||||
|
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
safe-push-apply@^1.0.0:
|
safe-push-apply@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5"
|
resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5"
|
||||||
@@ -11519,9 +11557,9 @@ tapable@^1.0.0:
|
|||||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||||
|
|
||||||
tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0:
|
tapable@^2.0.0, tapable@^2.2.1, tapable@^2.3.0:
|
||||||
version "2.3.2"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.2.tgz#86755feabad08d82a26b891db044808c6ad00f15"
|
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160"
|
||||||
integrity sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==
|
integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==
|
||||||
|
|
||||||
terminal-link@^2.0.0:
|
terminal-link@^2.0.0:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
@@ -11775,7 +11813,7 @@ type-fest@^4.4.0, type-fest@^4.8.3:
|
|||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58"
|
||||||
integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==
|
integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==
|
||||||
|
|
||||||
type-is@~1.6.17, type-is@~1.6.18:
|
type-is@~1.6.18:
|
||||||
version "1.6.18"
|
version "1.6.18"
|
||||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||||
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
|
||||||
@@ -11918,7 +11956,7 @@ universalify@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||||
|
|
||||||
unpipe@1.0.0, unpipe@~1.0.0:
|
unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||||
@@ -12012,6 +12050,14 @@ vary@~1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||||
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
|
||||||
|
|
||||||
|
vee-validate@4.15.0:
|
||||||
|
version "4.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vee-validate/-/vee-validate-4.15.0.tgz#eb77a9c867669d34abbc33ca5e16f2a991eb7ad5"
|
||||||
|
integrity sha512-PGJh1QCFwCBjbHu5aN6vB8macYVWrajbDvgo1Y/8fz9n/RVIkLmZCJDpUgu7+mUmCOPMxeyq7vXUOhbwAqdXcA==
|
||||||
|
dependencies:
|
||||||
|
"@vue/devtools-api" "^7.5.2"
|
||||||
|
type-fest "^4.8.3"
|
||||||
|
|
||||||
vee-validate@^4.15.1:
|
vee-validate@^4.15.1:
|
||||||
version "4.15.1"
|
version "4.15.1"
|
||||||
resolved "https://registry.yarnpkg.com/vee-validate/-/vee-validate-4.15.1.tgz#66e787e88916baa05f42e53bc0848e0878d50f91"
|
resolved "https://registry.yarnpkg.com/vee-validate/-/vee-validate-4.15.1.tgz#66e787e88916baa05f42e53bc0848e0878d50f91"
|
||||||
@@ -12542,36 +12588,6 @@ xtend@^4.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
xterm-addon-canvas@0.5.0:
|
|
||||||
version "0.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.5.0.tgz#95d056cec6da42a51b2c47746a011409020c388c"
|
|
||||||
integrity sha512-QOo/eZCMrCleAgMimfdbaZCgmQRWOml63Ued6RwQ+UTPvQj3Av9QKx3xksmyYrDGRO/AVRXa9oNuzlYvLdmoLQ==
|
|
||||||
|
|
||||||
xterm-addon-fit@0.8.0:
|
|
||||||
version "0.8.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.8.0.tgz#48ca99015385141918f955ca7819e85f3691d35f"
|
|
||||||
integrity sha512-yj3Np7XlvxxhYF/EJ7p3KHaMt6OdwQ+HDu573Vx1lRXsVxOcnVJs51RgjZOouIZOczTsskaS+CpXspK81/DLqw==
|
|
||||||
|
|
||||||
xterm-addon-search@0.13.0:
|
|
||||||
version "0.13.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.13.0.tgz#21286f4db48aa949fbefce34bb8bc0c9d3cec627"
|
|
||||||
integrity sha512-sDUwG4CnqxUjSEFh676DlS3gsh3XYCzAvBPSvJ5OPgF3MRL3iHLPfsb06doRicLC2xXNpeG2cWk8x1qpESWJMA==
|
|
||||||
|
|
||||||
xterm-addon-web-links@0.9.0:
|
|
||||||
version "0.9.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.9.0.tgz#c65b18588d1f613e703eb6feb7f129e7ff1c63e7"
|
|
||||||
integrity sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q==
|
|
||||||
|
|
||||||
xterm-addon-webgl@0.16.0:
|
|
||||||
version "0.16.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.16.0.tgz#9872d08a64136f893b27ef9a6412136d3bf563c4"
|
|
||||||
integrity sha512-E8cq1AiqNOv0M/FghPT+zPAEnvIQRDbAbkb04rRYSxUym69elPWVJ4sv22FCLBqM/3LcrmBLl/pELnBebVFKgA==
|
|
||||||
|
|
||||||
xterm@5.2.1:
|
|
||||||
version "5.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.1.tgz#b3fea7bdb55b9be1d4b31f4cd1091f26ac42afb8"
|
|
||||||
integrity sha512-cs5Y1fFevgcdoh2hJROMVIWwoBHD80P1fIP79gopLHJIE4kTzzblanoivxTiQ4+92YM9IxS36H1q0MxIJXQBcA==
|
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||||
@@ -12656,9 +12672,9 @@ yargs@^16.0.0, yargs@^16.2.0:
|
|||||||
yargs-parser "^20.2.2"
|
yargs-parser "^20.2.2"
|
||||||
|
|
||||||
yargs@^17.0.0:
|
yargs@^17.0.0:
|
||||||
version "17.7.2"
|
version "17.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.3.tgz#779dffe6bcafec596a7172e983289a588647faaa"
|
||||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
integrity sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui "^8.0.1"
|
cliui "^8.0.1"
|
||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
@@ -12677,3 +12693,8 @@ yocto-queue@^1.0.0:
|
|||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.2.tgz#3e09c95d3f1aa89a58c114c99223edf639152c00"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.2.tgz#3e09c95d3f1aa89a58c114c99223edf639152c00"
|
||||||
integrity sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==
|
integrity sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==
|
||||||
|
|
||||||
|
zod@3.24.3:
|
||||||
|
version "3.24.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.3.tgz#1f40f750a05e477396da64438e0e1c0995dafd87"
|
||||||
|
integrity sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==
|
||||||
|
|||||||
Reference in New Issue
Block a user