Compare commits

..
Author SHA1 Message Date
Andy Lee 12a51b2451 refactor: fine tune serial console
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-08-04 00:35:25 +08:00
Andy Lee 9f9069fb63 refactor: clear searach result after close
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-08-03 23:21:37 +08:00
Andy Lee 57ee92051e feat(serial-console): add terminal search toolbar and highlight tuning
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-08-03 19:35:49 +08:00
Andy Lee ad3f91f298 build: artifact
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-08-03 18:32:32 +08:00
Andy Lee bc4f70e889 feat: improve serial console
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-08-03 18:31:43 +08:00
Andy Lee 423a721cb8 fix: move filter labels/schedule buttons position
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-08-03 16:42:07 +08:00
Andy Lee a4c28e6dff deps: update rancher/shell to 3.0.12-rc.7
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-08-03 16:42:06 +08:00
8 changed files with 20 additions and 123 deletions
+3 -7
View File
@@ -8,16 +8,12 @@
- [ ] Yes, the backend owner is @ - [ ] Yes, the backend owner is @
- [ ] No, frontend-only. - [ ] No, frontend-only.
### Related Issue ### Related Issue #
<!-- Link the issue as harvester/harvester#<issue_number> --> <!-- Link the issue as harvester/harvester#<issue_number> -->
harvester/harvester#<issue_number> harvester/harvester#<issue_number>
### Test Steps ### Test screenshot or video (Required)
<!-- Provide the test steps for reviewer to verify the issue --> <!-- Attach screenshot or video of the changes and eventual comparison if you find it necessary -->
1. Go to ... page
### Test screenshot or video
<!-- Attach screenshot or video of the changes and eventual comparison -->
@@ -1,12 +1,12 @@
name: Build Standalone on PR Merge name: Build Standalone on PR Merge
on: on:
workflow_dispatch: # manually trigger on Github Actions only support PR branch pushed to harvester/harvester-ui-extension
push: push:
branches: branches:
- main - main
- 'release-harvester-v*' - 'release-harvester-v*'
- '*-dev' - '*-dev'
- bump_shell_rc7_demo
pull_request: pull_request:
branches: branches:
- main - main
@@ -30,35 +30,3 @@ jobs:
id-token: write id-token: write
with: with:
CI_BRANCH: ${{github.ref_name}} CI_BRANCH: ${{github.ref_name}}
comment-on-pr:
name: Comment build result on PR
# Only for manual runs so the existing push / pull_request flows are untouched.
if: github.event_name == 'workflow_dispatch'
needs:
- build
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Add build artifact comment to the PR opened from this branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: ${{ github.ref_name }}
run: |
set -euo pipefail
# Match scripts/version: sanitize the branch and map main -> latest.
if [ "$BRANCH" = "main" ]; then
DIR="latest"
else
DIR=$(printf '%s' "$BRANCH" | sed -E 's/[^a-zA-Z0-9.-]+/-/g')
fi
URL="https://releases.rancher.com/harvester-ui/dashboard/${DIR}/index.html"
PR=$(gh pr list --repo "$GITHUB_REPOSITORY" --state open --base main --head "$BRANCH" --json number --jq '.[0].number // empty')
if [ -z "$PR" ]; then
echo "No open PR found for branch $BRANCH, skipping comment."
exit 0
fi
BODY=$(printf '🚀 **UI Artifact Build Status:** `SUCCESS` ✅\n> ui-index: %s\n> ui-source: external' "$URL")
gh pr comment "$PR" --repo "$GITHUB_REPOSITORY" --body "$BODY"
@@ -213,15 +213,9 @@ export default {
}, },
clearSearchHighlights() { clearSearchHighlights() {
this.terminal?.clearSelection();
this.searchAddon?.clearActiveDecoration(); this.searchAddon?.clearActiveDecoration();
this.searchAddon?.clearDecorations(); 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() { findNext() {
@@ -1,5 +1,4 @@
<script> <script>
import jsyaml from 'js-yaml';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import Tabbed from '@shell/components/Tabbed'; import Tabbed from '@shell/components/Tabbed';
import Tab from '@shell/components/Tabbed/Tab'; import Tab from '@shell/components/Tabbed/Tab';
@@ -178,17 +177,8 @@ export default {
}, },
async saveVMT(buttonCb) { async saveVMT(buttonCb) {
this.errors = [];
this.parseVM(); this.parseVM();
this.validateCloudInit();
if (this.errors.length) {
buttonCb(false);
return;
}
const templates = await this.$store.dispatch('harvester/findAll', { type: HCI.VM_TEMPLATE }); const templates = await this.$store.dispatch('harvester/findAll', { type: HCI.VM_TEMPLATE });
const template = templates.find( (O) => O.metadata.name === this.templateValue.metadata.name); const template = templates.find( (O) => O.metadata.name === this.templateValue.metadata.name);
@@ -247,24 +237,6 @@ export default {
this.$refs.yamlEditor?.refresh(); this.$refs.yamlEditor?.refresh();
} }
}, },
validateCloudInit() {
if (this.userScript) {
try {
jsyaml.load(this.userScript);
} catch (e) {
this.errors.push(this.t('harvester.virtualMachine.cloudConfig.user.invalidYaml'));
}
}
if (this.networkScript) {
try {
jsyaml.load(this.networkScript);
} catch (e) {
this.errors.push(this.t('harvester.virtualMachine.cloudConfig.network.invalidYaml'));
}
}
},
}, },
}; };
</script> </script>
+3 -14
View File
@@ -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, remove } from '@shell/utils/object'; import { set } 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,12 +142,10 @@ 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) || [];
const options = vlans.map((vlan) => ({ return vlans.map((vlan) => ({
label: vlan.id, label: vlan.id,
value: vlan.id, value: vlan.id,
})); }));
return [{ label: this.t('generic.none'), value: '' }, ...options];
} }
}, },
@@ -162,14 +160,6 @@ 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;
@@ -293,13 +283,12 @@ export default {
</div> </div>
<div class="col span-6"> <div class="col span-6">
<LabeledSelect <LabeledSelect
:value="value.spec.vlan ?? ''" v-model: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>
@@ -1,5 +1,4 @@
<script> <script>
import jsyaml from 'js-yaml';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import Tabbed from '@shell/components/Tabbed'; import Tabbed from '@shell/components/Tabbed';
@@ -380,7 +379,6 @@ export default {
this.validateCPUMemory(); this.validateCPUMemory();
this.validateWindowsSysprep(); this.validateWindowsSysprep();
this.validateCloudInit();
// block create VM flow if has validation errors // block create VM flow if has validation errors
if (this.errors.length) { if (this.errors.length) {
@@ -426,24 +424,6 @@ export default {
} }
}, },
validateCloudInit() {
if (this.userScript) {
try {
jsyaml.load(this.userScript);
} catch (e) {
this.errors.push(this.t('harvester.virtualMachine.cloudConfig.user.invalidYaml'));
}
}
if (this.networkScript) {
try {
jsyaml.load(this.networkScript);
} catch (e) {
this.errors.push(this.t('harvester.virtualMachine.cloudConfig.network.invalidYaml'));
}
}
},
async saveSingle(buttonCb) { async saveSingle(buttonCb) {
this.parseVM(); this.parseVM();
this.value.spec.template.spec.hostname = this.hostname ? this.hostname : this.value.metadata.name; this.value.spec.template.spec.hostname = this.hostname ? this.hostname : this.value.metadata.name;
@@ -63,7 +63,6 @@ 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: {
@@ -191,13 +190,15 @@ export default {
this['membershipUpdate'] = update; this['membershipUpdate'] = update;
}, },
validateQuotas(isValid) { removeQuota(key) {
this.isQuotasValid = isValid; ['resourceQuota', 'namespaceDefaultResourceQuota'].forEach((specProp) => {
}, if (this.value?.spec[specProp]?.limit && this.value?.spec[specProp]?.limit[key]) {
delete this.value?.spec[specProp]?.limit[key];
onQuotasInput({ projectLimit, nsLimit } = {}) { }
this.value.spec.resourceQuota = { ...this.value.spec.resourceQuota, limit: projectLimit }; if (this.value?.spec[specProp]?.usedLimit && this.value?.spec[specProp]?.usedLimit[key]) {
this.value.spec.namespaceDefaultResourceQuota = { ...this.value.spec.namespaceDefaultResourceQuota, limit: nsLimit }; delete this.value?.spec[specProp]?.usedLimit[key];
}
});
} }
}, },
}; };
@@ -211,7 +212,7 @@ export default {
:resource="value" :resource="value"
:subtypes="[]" :subtypes="[]"
:can-yaml="false" :can-yaml="false"
:validation-passed="fvFormIsValid && isQuotasValid" :validation-passed="fvFormIsValid"
@error="e=>errors=e" @error="e=>errors=e"
@finish="save" @finish="save"
@cancel="done" @cancel="done"
@@ -266,11 +267,10 @@ export default {
:weight="9" :weight="9"
> >
<ResourceQuota <ResourceQuota
:value="value" v-model:value="value"
:mode="canEditTabElements" :mode="canEditTabElements"
:types="isStandaloneHarvester ? HARVESTER_TYPES : RANCHER_TYPES" :types="isStandaloneHarvester ? HARVESTER_TYPES : RANCHER_TYPES"
@input="onQuotasInput" @remove="removeQuota"
@validationChanged="validateQuotas"
/> />
</Tab> </Tab>
<Tab <Tab
-2
View File
@@ -830,12 +830,10 @@ harvester:
label: User Data Template label: User Data Template
title: "User Data:" title: "User Data:"
tip: "You can specify user data to configure an instance or run a configuration script during launch. If you launch more than one instance at a time, the user data is available to all the instances in that reservation. <a href='https://cloudinit.readthedocs.io/en/latest/topics/examples.html' target='_blank'>Learn more</a>" tip: "You can specify user data to configure an instance or run a configuration script during launch. If you launch more than one instance at a time, the user data is available to all the instances in that reservation. <a href='https://cloudinit.readthedocs.io/en/latest/topics/examples.html' target='_blank'>Learn more</a>"
invalidYaml: 'Invalid User Data YAML syntax'
network: network:
label: Network Data Template label: Network Data Template
title: "Network Data:" title: "Network Data:"
tip: "The network-data configuration allows you to customize the instance's networking interfaces by assigning subnet configuration, virtual device creation (bonds, bridges, VLANs) routes and DNS configuration. <a href='https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v1.html' target='_blank'>Learn more</a>" tip: "The network-data configuration allows you to customize the instance's networking interfaces by assigning subnet configuration, virtual device creation (bonds, bridges, VLANs) routes and DNS configuration. <a href='https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v1.html' target='_blank'>Learn more</a>"
invalidYaml: 'Invalid Network Data YAML syntax'
sysprep: sysprep:
title: Windows Sysprep Configuration title: Windows Sysprep Configuration
description: "Configure Windows automated installation using autounattend.xml. The configuration will be stored in a Kubernetes Secret and mounted as a CD-ROM during installation. <a href='https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/automate-windows-setup' target='_blank'>Learn more</a>" description: "Configure Windows automated installation using autounattend.xml. The configuration will be stored in a Kubernetes Secret and mounted as a CD-ROM during installation. <a href='https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/automate-windows-setup' target='_blank'>Learn more</a>"