Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ Web Console preserves compatible API paths, schema and resource names, action na

Visible branding, product-owned assets, icon identifiers, package metadata, and operator documentation use PastureStack. Historical identifiers remain only where they are server data or protocol contracts and must not be mechanically replaced.

Candidate `1.6.174` changes VM image guidance and defaults only: it does not
implement a VM runtime or image whitelist. Existing/custom/last-used VM images,
ordinary container defaults and required validation remain supported. Shared
image-required errors are localized in English and Traditional Chinese, with
the existing English fallback for other locales. Backend capabilities,
payloads, permissions and authentication contracts are unchanged; dependency
versions and the graph are unchanged. The five added Ember regressions and
formal release gates remain pending; 817 is only an expected test count.
Historical releases and HOLDs are not promoted, and the full matrix remains
INCOMPLETE. See the [candidate release note](docs/releases/web-console-1.6.174.md).

Published `1.6.173` reads native ProjectTemplate card labels from the model's
existing `name`, not `localizedName`, which native ProjectTemplate does not implement.
Name-dependent sorting, rename reactivity and exact-ID selection remain native;
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ PastureStack is an independent community effort to preserve, audit, and moderniz

## Project status

Candidate `1.6.174` removes the ordinary-container default and quick picks from
the VM image field. Custom images, existing image values and the last-used VM
image remain supported; ordinary container defaults are unchanged. A blank
image still blocks the existing save lifecycle. VM boot-image guidance and
required errors for both VM and container images use reviewed English and
Traditional Chinese copy with the existing English fallback.
Dependency versions and the dependency graph are unchanged. Five added Ember
regressions have not yet run; 817 total tests is an expectation, not a result.
Exact-source CI, reproducible artifacts, signed publication, Server packaging
and packaged VM acceptance remain pending. Historical releases and HOLDs are
unchanged; the full matrix remains INCOMPLETE. See the
[candidate release note](docs/releases/web-console-1.6.174.md).

Published `1.6.173` repairs empty environment-template choice labels. These
choices are native `ProjectTemplate` resources, not Catalog templates: their
authoritative label is `name`, and selection remains bound to the template ID.
Expand Down
23 changes: 13 additions & 10 deletions app/components/form-image/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import ManageLabels from 'ui/mixins/manage-labels';

// Remember the last value and use that for new one
var lastContainer = 'ubuntu:26.04';
var lastVm = 'ubuntu:26.04';
var lastVm = null;
var lastWindows = 'microsoft/nanoserver';

export default Component.extend(ManageLabels, {
intl: service(),
settings: service(),
projects: service(),

Expand Down Expand Up @@ -44,10 +45,12 @@ export default Component.extend(ManageLabels, {

if ( !initial )
{
if ( this.get('projects.current.isWindows') ) {
if ( this.get('isVm') ) {
initial = lastVm;
} else if ( this.get('projects.current.isWindows') ) {
initial = lastWindows;
} else {
initial = ( this.get('isVm') ? lastVm : lastContainer);
initial = lastContainer;
}
}

Expand Down Expand Up @@ -83,11 +86,11 @@ export default Component.extend(ManageLabels, {
}
else if ( input && input.length )
{
if ( this.get('projects.current.isWindows') ) {
lastWindows = input;
} else if ( this.get('isVm') )
{
if ( this.get('isVm') ) {
lastVm = input;
} else if ( this.get('projects.current.isWindows') )
{
lastWindows = input;
}
else
{
Expand All @@ -105,13 +108,13 @@ export default Component.extend(ManageLabels, {
this.validate();
}.observes('userInput'),

validate() {
validate: function() {
var errors = [];
if ( !this.get('value') )
{
errors.push('Image is required');
errors.push(this.get('intl').t(this.get('isVm') ? 'formImage.vm.bootImageRequired' : 'formImage.container.imageRequired'));
}

this.set('errors', errors);
},
}.observes('intl._locale'),
});
13 changes: 2 additions & 11 deletions app/components/form-image/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,8 @@
<div class="row">
<div class="col-xs-12">
{{#if this.isVm}}
<div class="input-group">
{{input type="text" value=this.userInput placeholder=(t 'formImage.vm.placeholder') class="form-control"}}
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"><i class="icon icon-chevron-down"></i></button>
<ul class="dropdown-menu dropdown-menu-end" role="menu" style="min-width: 250px; max-height: 300px; overflow-y: auto;">
<li role="presentation" class="dropdown-header">{{t 'formImage.vm.dropdownLabel' appName=this.settings.appName}}</li>
<li><a href="#" {{action "setInput" "ubuntu:26.04"}}>ubuntu:26.04</a></li>
<li><a href="#" {{action "setInput" "alpine:3.22"}}>alpine:3.22</a></li>
</ul>
</div>
</div>
{{input type="text" value=this.userInput placeholder=(t 'formImage.vm.bootImagePlaceholder') class="form-control"}}
<p class="help-block vm-boot-image-help">{{t 'formImage.vm.bootImageHelp'}}</p>
{{else}}
{{input type="text" class="form-control" value=this.userInput placeholder=(t 'formImage.container.placeholder')}}
{{/if}}
Expand Down
5 changes: 4 additions & 1 deletion config/translation-fallback-prefixes.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@ module.exports = Object.freeze([
'newSecret.refreshFailed',
'accountsPage.new.error.',
'editAccount.error.',
'viewEditProject.error.'
'viewEditProject.error.',
// VM boot-image guidance uses reviewed English fallback outside zh-tw.
'formImage.vm.bootImage',
'formImage.container.imageRequired'
]);
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "@pasturestack/web-console",
"version": "1.6.173",
"version": "1.6.174",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pasturestack/web-console",
"version": "1.6.173",
"version": "1.6.174",
"license": "Apache-2.0",
"dependencies": {
"sass": "1.103.1"
Expand Down
53 changes: 53 additions & 0 deletions docs/releases/web-console-1.6.174.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Web Console 1.6.174

Candidate only. Exact-source CI, reproducible production artifacts, signed
publication, anonymous public readback and Server packaging have not yet been
completed for this version. No VM lifecycle PASS is claimed.

## Root cause and bounded repair

The shared image form supplied `ubuntu:26.04` as a fresh VM default and offered
`ubuntu:26.04` and `alpine:3.22` as VM quick picks. These ordinary-container
choices do not establish a compatible VM boot image. The VM launch contract
passes `-m` and `-smp` and uses the `/image` boot-disk interface; merely choosing
an operating-system container image does not supply that VM runtime contract.

Remove the fresh VM default and both quick picks, without substituting another
unverified image or restricting custom image references. Preserve an existing
image and the last-used VM image. Ordinary Linux and Windows container defaults
remain unchanged. Blank image input still reaches the existing required
validation and cancels the native save lifecycle before resource persistence.

English and Traditional Chinese explain the boot-image contract and ask the
operator to verify the target host meets the image's virtualization needs,
for example KVM (`/dev/kvm`). This guidance does not add a new KVM gate or claim
that every custom VM image forbids software emulation. VM and ordinary-container
required errors are localized and react to locale changes. New messages use
the existing English fallback outside the two reviewed locales.

No VM engine, host configuration, hardware flags, API schema, payload,
authorization, MFA or resource lifecycle is changed. Dependency versions,
overrides and the installed dependency graph are unchanged; the two lock files
change only their root package version metadata to `1.6.174`.

## Verification and release boundaries

Five new real-component Ember regressions cover existing and last-used images,
blank-image save cancellation, custom VM input without misleading quick picks,
VM guidance/required-error locale changes and ordinary-container required-error
locale changes on the same form. They have not yet been executed. The expected
full test count is 817 (the previous 812 plus these five), not a verified result.
Offline method controls and syntax checks do not replace Chrome rendering,
exact-source CI or packaged native VM acceptance.

No source commit, formal run, archive checksum or publication coordinate is
asserted for this candidate. Reuse only the eventual reviewed exact-source CI
artifact for publication; do not rebuild or overwrite an older release.
The existing vendor-pending advisory and audit policy remain unchanged; this
candidate is not a zero-CVE claim.

The [published Web173 record](web-console-1.6.173.md) and its scoped results
remain historical evidence, not proof of this candidate's VM lifecycle.
Original HOLD receipts remain HOLD. The complete permission/resource/locale
matrix remains INCOMPLETE. Preserve rollback artifacts, configuration and
volumes; no company deployment is authorized by this source change.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pasturestack/web-console",
"version": "1.6.173",
"version": "1.6.174",
"private": true,
"description": "PastureStack browser console for the compatible control platform.",
"repository": {
Expand Down
4 changes: 2 additions & 2 deletions scripts/check-modernization-blockers
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ with open('package.json', encoding='utf-8') as f:
print(json.load(f).get('version', ''))
PY
)
if [[ "$version" != "1.6.173" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.173"
if [[ "$version" != "1.6.174" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.174"
failures=$((failures + 1))
fi

Expand Down
2 changes: 1 addition & 1 deletion scripts/check-ui-console-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,4 @@ if [[ -n ${PASTURESTACK_PRIVATE_MARKER:-} ]] && grep -RInF -- "$PASTURESTACK_PRI
fi

printf 'UI_CONSOLE_WORKSPACE_OK version=%s persistence=%s cross_tab=%s\n' \
1.6.173 browser-session broker-broadcast
1.6.174 browser-session broker-broadcast
2 changes: 1 addition & 1 deletion scripts/check-ui-critical-high-dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ if lock_bytes != baseline_bytes:
lock = json.loads(lock_bytes)
packages = lock.get("packages", {})
root = packages.get("", {})
if package.get("version") != "1.6.173":
if package.get("version") != "1.6.174":
fail(f"unexpected Web Console version: {package.get('version')}")
if root.get("version") != package.get("version"):
fail(f"lock root version differs: {root.get('version')}")
Expand Down
3 changes: 2 additions & 1 deletion scripts/check-ui-ember-api-store-fetch-upgrade
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,8 @@ for marker in [
if marker not in page_header_sources:
fail(f"transient service collection regression marker missing: {marker}")

if template_action_count != 563:
# The two ordinary-container VM quick-pick actions were removed in Web174.
if template_action_count != 561:
fail(f"unexpected template action count: {template_action_count}")
if power_select_count != 15:
fail(f"unexpected modern PowerSelect count: {power_select_count}")
Expand Down
107 changes: 107 additions & 0 deletions tests/integration/form-image-vm-boot-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { module, test } from 'qunit';
import { render, find, findAll, fillIn, settled, setupContext, setupRenderingContext, teardownContext } from '@ember/test-helpers';
import { precompileTemplate } from '@ember/template-compilation';
import EmberObject from '@ember/object';
import Service from '@ember/service';
import { run } from '@ember/runloop';
import resolver from '../helpers/resolver';
import { initialize as initializePodLayouts } from 'ui/initializers/pod-component-layouts';
import { initialize as initializeIntl } from 'ui/instance-initializers/intl';

module('Integration | Component | VM boot image', function(hooks) {
hooks.beforeEach(async function() {
this.testRoot = document.createElement('div');
this.testRoot.id = 'ember-testing';
document.body.appendChild(this.testRoot);
await setupContext(this, {resolver});
initializePodLayouts();
initializeIntl(this.owner);
this.owner.register('service:projects', Service.extend({
current: EmberObject.create({isWindows: false}),
}));
this.owner.register('service:settings', Service.extend({appName: 'PastureStack'}));
this.intl = this.owner.lookup('service:intl');

for (let locale of ['en-us', 'zh-tw', 'fr-fr']) {
let response = await fetch('/translations/' + locale + '.json');

if (!response.ok) {
throw new Error('Local translation fixture failed: ' + locale + ' ' + response.status);
}
this.intl.addTranslations(locale, await response.json());
}
this.intl.setLocale(['en-us']);
this.changed = (value) => { this.set('selectedImage', value); };
this.setLabels = () => {};
await setupRenderingContext(this);
});

hooks.afterEach(async function() {
await teardownContext(this);
this.testRoot.remove();
});

async function renderVm() {
await render(precompileTemplate('{{form-image isVm=true initialValue="docker:example.test/boot:qa" errors=this.imageErrors changed=this.changed setLabels=this.setLabels}}{{top-errors errors=this.imageErrors}}'));
}

test('VM accepts custom boot-image text with no ordinary-container quick picks', async function(assert) {
await renderVm();
assert.strictEqual(find('input[type="text"]').value, 'example.test/boot:qa', 'existing image is preserved');
assert.strictEqual(find('input[type="text"]').placeholder, 'VM boot image');
assert.strictEqual(findAll('.dropdown-toggle, .dropdown-menu').length, 0, 'no VM quick picks imply that ordinary container images can boot a VM');
assert.ok(find('.vm-boot-image-help').textContent.includes('/dev/kvm'));

await fillIn('input[type="text"]', 'registry.example/custom-boot:qa');
assert.strictEqual(this.selectedImage, 'docker:registry.example/custom-boot:qa', 'arbitrary custom VM images remain accepted');
await fillIn('input[type="text"]', '');
assert.deepEqual(this.imageErrors, ['Enter a compatible VM boot image.']);
assert.ok(this.testRoot.textContent.includes('Enter a compatible VM boot image.'), 'the actual top-errors component displays the required error');
});

test('blank VM required copy and guidance react to locale, with English fallback', async function(assert) {
await renderVm();
await fillIn('input[type="text"]', '');
run(() => this.intl.setLocale(['zh-tw', 'en-us']));
await settled();
assert.deepEqual(this.imageErrors, ['請填入相容的 VM 專用開機映像。']);
assert.strictEqual(find('input[type="text"]').placeholder, 'VM 專用開機映像');
assert.ok(this.testRoot.textContent.includes('確認目標主機符合映像的虛擬化需求'));

run(() => this.intl.setLocale(['fr-fr', 'en-us']));
await settled();
assert.deepEqual(this.imageErrors, ['Enter a compatible VM boot image.'], 'missing non-primary locale keys use loaded English translations');
assert.strictEqual(find('input[type="text"]').placeholder, 'VM boot image');
let help = find('.vm-boot-image-help').textContent;

for (let capability of ['-m', '-smp', '/image', '/dev/kvm']) {
assert.ok(help.includes(capability), capability);
}
assert.notOk(this.testRoot.textContent.includes('Missing translation'), 'fallback has no missing-key marker');
});

test('blank container required copy rerenders on the same form with zh-tw and English fallback', async function(assert) {
await render(precompileTemplate('{{form-image isVm=false initialValue="docker:example.test/container:qa" errors=this.imageErrors changed=this.changed setLabels=this.setLabels}}{{top-errors errors=this.imageErrors}}'));
let input = find('input[type="text"]');

await fillIn('input[type="text"]', '');
assert.strictEqual(this.selectedImage, null);
assert.deepEqual(this.imageErrors, ['Image is required']);
assert.ok(this.testRoot.textContent.includes('Image is required'));
assert.strictEqual(findAll('.vm-boot-image-help').length, 0, 'no VM guidance is added to the container form');

run(() => this.intl.setLocale(['zh-tw', 'en-us']));
await settled();
assert.strictEqual(find('input[type="text"]'), input, 'the same input remains mounted');
assert.deepEqual(this.imageErrors, ['請填入容器映像。']);
assert.ok(this.testRoot.textContent.includes('請填入容器映像。'));

run(() => this.intl.setLocale(['fr-fr', 'en-us']));
await settled();
assert.strictEqual(find('input[type="text"]'), input, 'fallback does not replace the form');
assert.deepEqual(this.imageErrors, ['Image is required']);
assert.ok(this.testRoot.textContent.includes('Image is required'));
assert.strictEqual(this.selectedImage, null, 'locale changes do not substitute an image');
assert.notOk(this.testRoot.textContent.includes('Missing translation'));
});
});
Loading
Loading