mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-04 21:26:08 +08:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
07f2b02a9d | ||
|
|
35b6949cdc | ||
|
|
c577f589e7 | ||
|
|
b1387820ed | ||
|
|
45f72749f5 | ||
|
|
a65b3f443e | ||
|
|
21d415b5f3 | ||
|
|
7493fe2dfc | ||
|
|
e71d12f67f | ||
|
|
d3b77e2e41 | ||
|
|
73126b953c | ||
|
|
937afd9c9a | ||
|
|
3658caf067 | ||
|
|
a067751cba | ||
|
|
94566b2b54 | ||
|
|
d47bf1d712 |
@@ -6,7 +6,6 @@ on:
|
|||||||
- main
|
- main
|
||||||
- 'release-harvester-v*'
|
- 'release-harvester-v*'
|
||||||
- '*-dev'
|
- '*-dev'
|
||||||
- access_volume_mode_demo
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|||||||
+9
-3
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester-ui-extension",
|
"name": "harvester-ui-extension",
|
||||||
"version": "1.10.0-dev",
|
"version": "1.9.0-rc4",
|
||||||
"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,6 +15,12 @@
|
|||||||
"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",
|
||||||
@@ -34,7 +40,7 @@
|
|||||||
"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.15.3",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,13 +15,6 @@ export const VOLUME_MODE = {
|
|||||||
FILE_SYSTEM: 'Filesystem'
|
FILE_SYSTEM: 'Filesystem'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ACCESS_MODE = {
|
|
||||||
READ_WRITE_MANY: 'ReadWriteMany',
|
|
||||||
READ_WRITE_ONCE: 'ReadWriteOnce',
|
|
||||||
READ_ONLY_MANY: 'ReadOnlyMany',
|
|
||||||
READ_WRITE_ONCE_POD: 'ReadWriteOncePod',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NETWORK_PROTOCOL = {
|
export const NETWORK_PROTOCOL = {
|
||||||
IPv4: 'IPv4',
|
IPv4: 'IPv4',
|
||||||
IPv6: 'IPv6',
|
IPv6: 'IPv6',
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
import ArrayList from '@shell/components/form/ArrayList';
|
import ArrayList from '@shell/components/form/ArrayList';
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
@@ -10,10 +10,6 @@ import { HCI_SETTING } from '../../config/settings';
|
|||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import { _EDIT, _CREATE } from '@shell/config/query-params';
|
import { _EDIT, _CREATE } from '@shell/config/query-params';
|
||||||
|
|
||||||
const {
|
|
||||||
READ_WRITE_ONCE, READ_ONLY_MANY, READ_WRITE_MANY, READ_WRITE_ONCE_POD
|
|
||||||
} = ACCESS_MODE;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CDISettings',
|
name: 'CDISettings',
|
||||||
|
|
||||||
@@ -94,10 +90,10 @@ export default {
|
|||||||
|
|
||||||
accessModeOptions() {
|
accessModeOptions() {
|
||||||
return [
|
return [
|
||||||
{ label: READ_WRITE_ONCE, value: READ_WRITE_ONCE },
|
{ label: 'ReadWriteOnce', value: 'ReadWriteOnce' },
|
||||||
{ label: READ_ONLY_MANY, value: READ_ONLY_MANY },
|
{ label: 'ReadOnlyMany', value: 'ReadOnlyMany' },
|
||||||
{ label: READ_WRITE_MANY, value: READ_WRITE_MANY },
|
{ label: 'ReadWriteMany', value: 'ReadWriteMany' },
|
||||||
{ label: READ_WRITE_ONCE_POD, value: READ_WRITE_ONCE_POD },
|
{ label: 'ReadWriteOncePod', value: 'ReadWriteOncePod' },
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,9 @@ import { HCI, VOLUME_SNAPSHOT } from '../types';
|
|||||||
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
|
||||||
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
|
||||||
import { GIBIBYTE } from '../utils/unit';
|
import { GIBIBYTE } from '../utils/unit';
|
||||||
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
import { isInternalStorageClass } from '../utils/storage-class';
|
import { isInternalStorageClass } from '../utils/storage-class';
|
||||||
|
|
||||||
const { READ_WRITE_MANY, READ_WRITE_ONCE, READ_ONLY_MANY } = ACCESS_MODE;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterVolume',
|
name: 'HarvesterVolume',
|
||||||
|
|
||||||
@@ -85,7 +83,7 @@ export default {
|
|||||||
if (this.mode === _CREATE) {
|
if (this.mode === _CREATE) {
|
||||||
// default volumeMode to Block
|
// default volumeMode to Block
|
||||||
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
|
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
|
||||||
this.value.spec.accessModes = [READ_WRITE_MANY];
|
this.value.spec.accessModes = ['ReadWriteMany'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const storage = this.value?.spec?.resources?.requests?.storage || null;
|
const storage = this.value?.spec?.resources?.requests?.storage || null;
|
||||||
@@ -162,7 +160,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
accessModeOptions() {
|
accessModeOptions() {
|
||||||
return [READ_WRITE_ONCE, READ_WRITE_MANY, READ_ONLY_MANY];
|
return ['ReadWriteOnce', 'ReadWriteMany', 'ReadOnlyMany'];
|
||||||
},
|
},
|
||||||
|
|
||||||
imageOption() {
|
imageOption() {
|
||||||
@@ -357,7 +355,7 @@ export default {
|
|||||||
|
|
||||||
getAccessMode() {
|
getAccessMode() {
|
||||||
if (!this.longhornV2LVMSupport) {
|
if (!this.longhornV2LVMSupport) {
|
||||||
return [READ_WRITE_MANY];
|
return ['ReadWriteMany'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.value?.spec?.accessModes && this.value?.spec?.accessModes?.length > 0) {
|
if (this.value?.spec?.accessModes && this.value?.spec?.accessModes?.length > 0) {
|
||||||
@@ -373,7 +371,7 @@ export default {
|
|||||||
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.value.thirdPartyStorageFeatureEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.value.thirdPartyStorageFeatureEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readWriteOnce ? [READ_WRITE_ONCE] : [READ_WRITE_MANY];
|
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
|
||||||
},
|
},
|
||||||
buildDataVolumeObj() {
|
buildDataVolumeObj() {
|
||||||
const storage = {
|
const storage = {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -16,12 +16,10 @@ import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
|
|||||||
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
|
import { VOLUME_HOTPLUG_ACTION, SOURCE_TYPE } from '../../../config/harvester-map';
|
||||||
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
|
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
|
||||||
import { HCI } from '../../../types';
|
import { HCI } from '../../../types';
|
||||||
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
import { OFF } from '../../../models/kubevirt.io.virtualmachine';
|
import { OFF } from '../../../models/kubevirt.io.virtualmachine';
|
||||||
import { EMPTY_IMAGE } from '../../../utils/vm';
|
import { EMPTY_IMAGE } from '../../../utils/vm';
|
||||||
|
|
||||||
const { READ_WRITE_MANY } = ACCESS_MODE;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
emits: ['update:value'],
|
emits: ['update:value'],
|
||||||
|
|
||||||
@@ -71,7 +69,7 @@ export default {
|
|||||||
|
|
||||||
customAccessMode: {
|
customAccessMode: {
|
||||||
type: String,
|
type: String,
|
||||||
default: READ_WRITE_MANY
|
default: 'ReadWriteMany'
|
||||||
},
|
},
|
||||||
|
|
||||||
resourceType: {
|
resourceType: {
|
||||||
|
|||||||
+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"
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storagec
|
|||||||
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
|
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
|
||||||
import { GIBIBYTE } from '../../../../utils/unit';
|
import { GIBIBYTE } from '../../../../utils/unit';
|
||||||
import { isInternalStorageClass } from '../../../../utils/storage-class';
|
import { isInternalStorageClass } from '../../../../utils/storage-class';
|
||||||
import { VOLUME_MODE, ACCESS_MODE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
const { READ_WRITE_MANY, READ_WRITE_ONCE } = ACCESS_MODE;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditVolume',
|
name: 'HarvesterEditVolume',
|
||||||
@@ -207,7 +205,7 @@ export default {
|
|||||||
|
|
||||||
getAccessMode(storageClassName) {
|
getAccessMode(storageClassName) {
|
||||||
if (!this.longhornV2LVMSupport) {
|
if (!this.longhornV2LVMSupport) {
|
||||||
return READ_WRITE_MANY;
|
return 'ReadWriteMany';
|
||||||
}
|
}
|
||||||
|
|
||||||
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
|
||||||
@@ -218,7 +216,7 @@ export default {
|
|||||||
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.thirdPartyStorageClassEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.thirdPartyStorageClassEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return readWriteOnce ? READ_WRITE_ONCE : READ_WRITE_MANY;
|
return readWriteOnce ? 'ReadWriteOnce' : 'ReadWriteMany';
|
||||||
},
|
},
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -798,6 +798,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.
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ import { HCI } from '../../types';
|
|||||||
import { parseVolumeClaimTemplates, EMPTY_IMAGE } from '../../utils/vm';
|
import { parseVolumeClaimTemplates, EMPTY_IMAGE } from '../../utils/vm';
|
||||||
import impl, { QGA_JSON, USB_TABLET } from './impl';
|
import impl, { QGA_JSON, USB_TABLET } from './impl';
|
||||||
import { GIBIBYTE } from '../../utils/unit';
|
import { GIBIBYTE } from '../../utils/unit';
|
||||||
import { VOLUME_MODE, ACCESS_MODE, FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types';
|
import { VOLUME_MODE, FILESYSTEM_SOURCE_TYPE } from '@pkg/harvester/config/types';
|
||||||
|
|
||||||
const { READ_WRITE_MANY } = ACCESS_MODE;
|
|
||||||
|
|
||||||
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
|
||||||
|
|
||||||
@@ -278,7 +276,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
customAccessMode() {
|
customAccessMode() {
|
||||||
return this.storageClassSetting.accessModes || READ_WRITE_MANY;
|
return this.storageClassSetting.accessModes || 'ReadWriteMany';
|
||||||
},
|
},
|
||||||
|
|
||||||
isWindows() {
|
isWindows() {
|
||||||
@@ -506,7 +504,7 @@ export default {
|
|||||||
id: randomStr(5),
|
id: randomStr(5),
|
||||||
source: SOURCE_TYPE.IMAGE,
|
source: SOURCE_TYPE.IMAGE,
|
||||||
name: 'disk-0',
|
name: 'disk-0',
|
||||||
accessMode: READ_WRITE_MANY, // root disk only support LHv1 volume, should be RWX
|
accessMode: 'ReadWriteMany', // root disk only support LHv1 volume, should be RWX
|
||||||
bus,
|
bus,
|
||||||
volumeName: '',
|
volumeName: '',
|
||||||
size,
|
size,
|
||||||
@@ -572,7 +570,7 @@ export default {
|
|||||||
const pvcResource = allPVCs.find( (O) => O.id === `${ namespace }/${ volume?.persistentVolumeClaim?.claimName }`);
|
const pvcResource = allPVCs.find( (O) => O.id === `${ namespace }/${ volume?.persistentVolumeClaim?.claimName }`);
|
||||||
|
|
||||||
source = SOURCE_TYPE.ATTACH_VOLUME;
|
source = SOURCE_TYPE.ATTACH_VOLUME;
|
||||||
accessMode = pvcResource?.spec?.accessModes?.[0] || READ_WRITE_MANY;
|
accessMode = pvcResource?.spec?.accessModes?.[0] || 'ReadWriteMany';
|
||||||
size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
|
size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
|
||||||
storageClassName = pvcResource?.spec?.storageClassName;
|
storageClassName = pvcResource?.spec?.storageClassName;
|
||||||
volumeMode = pvcResource?.spec?.volumeMode || VOLUME_MODE.BLOCK;
|
volumeMode = pvcResource?.spec?.volumeMode || VOLUME_MODE.BLOCK;
|
||||||
@@ -618,6 +616,7 @@ export default {
|
|||||||
type,
|
type,
|
||||||
storageClassName,
|
storageClassName,
|
||||||
hotpluggable,
|
hotpluggable,
|
||||||
|
shareable: DISK.shareable || false,
|
||||||
volumeStatus,
|
volumeStatus,
|
||||||
dataSource,
|
dataSource,
|
||||||
namespace,
|
namespace,
|
||||||
@@ -969,6 +968,14 @@ export default {
|
|||||||
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,
|
||||||
@@ -1253,6 +1260,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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "harvester",
|
"name": "harvester",
|
||||||
"description": "Rancher UI Extension for Harvester",
|
"description": "Rancher UI Extension for Harvester",
|
||||||
"version": "1.10.0-dev",
|
"version": "1.9.0-rc4",
|
||||||
"private": false,
|
"private": false,
|
||||||
"rancher": {
|
"rancher": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -2181,15 +2181,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 +2201,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 +2255,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 +2298,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"
|
||||||
@@ -3098,6 +3099,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 +3699,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 +3769,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==
|
||||||
@@ -4093,12 +4132,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,22 +4367,6 @@ 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:
|
|
||||||
version "1.19.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
|
||||||
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
|
||||||
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:
|
body-parser@~1.20.3:
|
||||||
version "1.20.4"
|
version "1.20.4"
|
||||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f"
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f"
|
||||||
@@ -4362,6 +4385,24 @@ body-parser@~1.20.3:
|
|||||||
type-is "~1.6.18"
|
type-is "~1.6.18"
|
||||||
unpipe "~1.0.0"
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
|
body-parser@~1.20.5:
|
||||||
|
version "1.20.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76"
|
||||||
|
integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==
|
||||||
|
dependencies:
|
||||||
|
bytes "~3.1.2"
|
||||||
|
content-type "~1.0.5"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "2.0.0"
|
||||||
|
destroy "~1.2.0"
|
||||||
|
http-errors "~2.0.1"
|
||||||
|
iconv-lite "~0.4.24"
|
||||||
|
on-finished "~2.4.1"
|
||||||
|
qs "~6.15.1"
|
||||||
|
raw-body "~2.5.3"
|
||||||
|
type-is "~1.6.18"
|
||||||
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
bonjour-service@^1.0.11:
|
bonjour-service@^1.0.11:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722"
|
resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722"
|
||||||
@@ -4535,11 +4576,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 +5062,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 +5108,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 +5121,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==
|
||||||
@@ -6656,38 +6685,39 @@ 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:
|
||||||
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:
|
dependencies:
|
||||||
accepts "~1.3.7"
|
accepts "~1.3.8"
|
||||||
array-flatten "1.1.1"
|
array-flatten "1.1.1"
|
||||||
body-parser "1.19.0"
|
body-parser "~1.20.5"
|
||||||
content-disposition "0.5.3"
|
content-disposition "~0.5.4"
|
||||||
content-type "~1.0.4"
|
content-type "~1.0.4"
|
||||||
cookie "0.4.0"
|
cookie "~0.7.1"
|
||||||
cookie-signature "1.0.6"
|
cookie-signature "~1.0.6"
|
||||||
debug "2.6.9"
|
debug "2.6.9"
|
||||||
depd "~1.1.2"
|
depd "2.0.0"
|
||||||
encodeurl "~1.0.2"
|
encodeurl "~2.0.0"
|
||||||
escape-html "~1.0.3"
|
escape-html "~1.0.3"
|
||||||
etag "~1.8.1"
|
etag "~1.8.1"
|
||||||
finalhandler "~1.1.2"
|
finalhandler "~1.3.1"
|
||||||
fresh "0.5.2"
|
fresh "~0.5.2"
|
||||||
merge-descriptors "1.0.1"
|
http-errors "~2.0.0"
|
||||||
|
merge-descriptors "1.0.3"
|
||||||
methods "~1.1.2"
|
methods "~1.1.2"
|
||||||
on-finished "~2.3.0"
|
on-finished "~2.4.1"
|
||||||
parseurl "~1.3.3"
|
parseurl "~1.3.3"
|
||||||
path-to-regexp "0.1.7"
|
path-to-regexp "~0.1.12"
|
||||||
proxy-addr "~2.0.5"
|
proxy-addr "~2.0.7"
|
||||||
qs "6.7.0"
|
qs "~6.15.1"
|
||||||
range-parser "~1.2.1"
|
range-parser "~1.2.1"
|
||||||
safe-buffer "5.1.2"
|
safe-buffer "5.2.1"
|
||||||
send "0.17.1"
|
send "~0.19.0"
|
||||||
serve-static "1.14.1"
|
serve-static "~1.16.2"
|
||||||
setprototypeof "1.1.1"
|
setprototypeof "1.2.0"
|
||||||
statuses "~1.5.0"
|
statuses "~2.0.1"
|
||||||
type-is "~1.6.18"
|
type-is "~1.6.18"
|
||||||
utils-merge "1.0.1"
|
utils-merge "1.0.1"
|
||||||
vary "~1.1.2"
|
vary "~1.1.2"
|
||||||
@@ -6825,10 +6855,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 +6874,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 +6954,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==
|
||||||
@@ -7433,9 +7450,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 +7475,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==
|
||||||
@@ -9103,11 +9120,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 +9888,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"
|
||||||
@@ -10317,7 +10324,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,7 +10384,7 @@ 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.15.3, qs@^6.12.3, qs@~6.14.0, qs@~6.15.1:
|
||||||
version "6.15.3"
|
version "6.15.3"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b"
|
||||||
integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==
|
integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==
|
||||||
@@ -10420,16 +10427,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 +10694,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 +10731,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"
|
||||||
@@ -11775,7 +11772,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 +11915,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 +12009,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 +12547,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"
|
||||||
@@ -12677,3 +12652,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