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
7 changes: 7 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ 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.

Web Console `1.6.160` recognizes the established post-authorization Certificate
in-use response (`405`, `InvalidAction`, and the known API message prefix),
showing reviewed English, Traditional Chinese or Japanese copy that explains
how to release the reference. It never renders the response's load-balancer
names. `403`, `404`, and unrecognized `405` responses remain neutral. This is
display-only; API authorization, DELETE/remove and authentication are unchanged.

The published package identity is `@pasturestack/web-console`, while the Ember 2 runtime keeps the neutral internal `ui/` module prefix used by existing imports. The static server artifact must contain a fingerprinted `/assets/ui*.js` entry and matching `index.html` reference; changing either side requires a coordinated Server packaging test.

Before release, validate login and logout, environment selection, hosts, stacks, services, containers, shell, logs, console, catalog, storage, networking, access control, settings, API errors, browser navigation, `en-US`, and `zh-TW` against an isolated compatible server.
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ PastureStack is an independent community effort to preserve, audit, and moderniz

## Project status

The current source compatibility target is `1.6.159`. It retains the existing Node 24, Ember, Sass,
The current source compatibility target is `1.6.160`. It retains the existing Node 24, Ember, Sass,
dependency, browser-smoke, terminal, console, and test-harness modernization.
It adds a provider-neutral OpenID Connect administration and sign-in flow with
PKCE S256, staged configuration validation, a real test login before
activation, and local-authentication recovery. Product-owned names, logos,
icons, package metadata, and visible text use PastureStack branding. API
models and protocol fields remain compatible.

The `1.6.160` source adds a clear, localized explanation when the API rejects
deleting a certificate still referenced by a load balancer. It tells the user
to remove those references first, without exposing service names. Denied or
missing resources and unrelated action failures retain neutral messages;
authorization, delete behavior, session and MFA contracts do not change. See
the [release note](docs/releases/web-console-1.6.160.md). Publication and live
acceptance evidence are recorded separately from this source target.

Release `1.6.159` preserves an existing Registry credential's password when
the editor changes only its username or leaves the password input blank. The
editor submits a password only when a new nonempty value is entered, preserving
Expand Down
2 changes: 1 addition & 1 deletion app/services/growl.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default Service.extend({
// Growls also report deletes and resource actions, not just saves.
// Keep denied/missing-resource details private without calling them saves.
var body = status === 403 || status === 404 || status === 405 ?
this.get('intl').t('resourceSaveError.actionUnavailable') :
this.get('intl').t(Errors.actionMessageKey(err) || 'resourceSaveError.actionUnavailable') :
Errors.stringify(err, this.get('intl'));
this.error(title,body);
},
Expand Down
19 changes: 18 additions & 1 deletion app/utils/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ function nestedStatus(value, seen, depth) {
return null;
}

function actionMessageKey(err) {
// Only this established, post-authorization lifecycle rejection is specific.
// Do not expose the server's load balancer names, or specialize 403/404.
if ( nestedStatus(err, [], 0) !== 405 ||
nestedStringField(err, 'code', [], 0) !== 'InvalidAction' ) {
return null;
}

let message = nestedStringField(err, 'message', [], 0);
let prefix = 'Certificate is in use by load balancer services: ';
return message && message.startsWith(prefix) && message.length > prefix.length ?
'resourceSaveError.certificateInUse' : null;
}

export default {
stringify(err, intl) {
if ( intl && typeof intl.t === 'function' ) {
Expand All @@ -154,7 +168,8 @@ export default {
if ( status === 403 || status === 404 || status === 405 ) {
// A denied resource and a missing resource must have the same visible
// explanation. Client-created errors can supply a more specific key.
let key = nonEmptyString(fieldValue(err, 'messageKey')) || 'resourceSaveError.unavailable';
let key = actionMessageKey(err) || nonEmptyString(fieldValue(err, 'messageKey')) ||
'resourceSaveError.unavailable';
return intl.t(key);
}

Expand Down Expand Up @@ -283,4 +298,6 @@ export default {
status(err) {
return nestedStatus(err, [], 0);
},

actionMessageKey,
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "@pasturestack/web-console",
"version": "1.6.159",
"version": "1.6.160",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pasturestack/web-console",
"version": "1.6.159",
"version": "1.6.160",
"license": "Apache-2.0",
"dependencies": {
"sass": "1.103.1"
Expand Down
26 changes: 26 additions & 0 deletions docs/releases/web-console-1.6.160.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Web Console 1.6.160

Certificate deletion could correctly fail at the Engine's reference guard
while the console displayed only a generic unavailable-or-denied notification.
This release gives the known `405 / InvalidAction` certificate-in-use response
a reviewed explanation: remove the load-balancer service's certificate
references before deleting the certificate.

The shared error formatter and growl service use the same classification.
English, Traditional Chinese and Japanese are covered. The server response's
service names and IDs are not displayed. A `403` or `404`, including one with a
nested certificate message, still receives the same neutral explanation. Other
`405` responses are not guessed to be certificate-in-use errors.

No API status, authorization, Certificate mutation, authentication, session,
OIDC, MFA or proxy behavior is changed. Unit coverage includes direct API error
models, nested response envelopes, missing or unrelated codes/messages,
non-disclosure, all three locales, and the existing validation formatting.

Focused native headless Chrome 153 QUnit validation passed 32/32 tests with the
`/errors|growl/` filter. This includes the formatter/growl tests and adjacent
existing error-display tests, not the full suite. Both localization-quality
and Traditional Chinese completeness checks passed with no missing keys.

Release publication and native browser acceptance are separate gates. Full
resource/role-matrix completion is not implied by these formatter tests.
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.159",
"version": "1.6.160",
"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.159" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.159"
if [[ "$version" != "1.6.160" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.160"
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.159 browser-session broker-broadcast
1.6.160 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 @@ -66,7 +66,7 @@ if lock_bytes != baseline_bytes:
lock = json.loads(lock_bytes)
packages = lock.get("packages", {})
root = packages.get("", {})
if package.get("version") != "1.6.159":
if package.get("version") != "1.6.160":
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
12 changes: 12 additions & 0 deletions tests/unit/services/growl-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ test('denied deletes and actions use neutral, private-safe copy in all supported
}, `${locale} ${status} does not call a delete failure a save or expose its resource ID`);
}

let inUse = {status: 405, code: 'InvalidAction',
message: 'Certificate is in use by load balancer services: private-balancer 1s-secret'};
growl.fromError('Delete failed', inUse);
assert.ok(messages['resourceSaveError.certificateInUse'], `${locale} has certificate lifecycle copy`);
assert.strictEqual(notifications.pop().body, messages['resourceSaveError.certificateInUse'],
`${locale} explains the blocked delete without disclosing service names`);
for (let status of [403, 404]) {
growl.fromError('Delete failed', {status, body: inUse});
assert.strictEqual(notifications.pop().body, messages['resourceSaveError.actionUnavailable'],
`${locale} denied/missing resources retain the same neutral growl`);
}

growl.fromError('Validation failed', {status: 422, fieldName: 'name', detail: 'already used'});
assert.ok(notifications.pop().body.startsWith(messages['resourceSaveError.validation']),
`${locale} validation still uses the existing localized formatter`);
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/utils/errors-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@ test('finds authentication status codes in nested request failures', function(as
assert.strictEqual(Errors.status({xhr: {status: 0}}), null, 'a network failure is not authentication failure');
});

test('only recognized authorized certificate lifecycle errors have specific action copy', function(assert) {
let message = 'Certificate is in use by load balancer services: private-balancer';
let body = {status: 405, code: 'InvalidAction', message};
let key = 'resourceSaveError.certificateInUse';
assert.strictEqual(Errors.actionMessageKey(body), key, 'the existing API contract is recognized');
assert.strictEqual(Errors.actionMessageKey(ApiError.create(body)), key, 'API error models are supported');
assert.strictEqual(Errors.actionMessageKey({xhr: {status: 405, responseJSON: body}}), key,
'nested response envelopes use the same classification');
for (let status of [403, 404]) {
assert.strictEqual(Errors.actionMessageKey({status, body}), null,
`${status} remains neutral even with a nested lifecycle message`);
}
for (let error of [
{status: 405, message},
{status: 405, code: 'ActionNotAvailable', message},
{status: 405, code: 'InvalidAction', message: 'Another action is unavailable'},
{status: 405, code: 'InvalidAction', message: 'Certificate is in use by load balancer services:'},
]) {
assert.strictEqual(Errors.actionMessageKey(error), null, 'other lifecycle failures are not guessed');
}
});

test('certificate-in-use copy is localized and never reveals referenced service names', async function(assert) {
for (let locale of ['en-us', 'zh-tw', 'ja-jp']) {
let response = await fetch(`/translations/${locale}.json`);
let messages = await response.json();
let key = 'resourceSaveError.certificateInUse';
assert.ok(messages[key], `${locale} has a human-readable explanation`);
let error = ApiError.create({status: 405, code: 'InvalidAction',
message: 'Certificate is in use by load balancer services: <private-balancer> 1s-secret'});
assert.strictEqual(Errors.stringify(error, {t: k => messages[k]}), messages[key],
`${locale} shows only reviewed copy, not service names or raw API text`);
}
});

test('save errors have useful reviewed copy in English, Traditional Chinese, and Japanese', async function(assert) {
for (let locale of ['en-us', 'zh-tw', 'ja-jp']) {
let response = await fetch(`/translations/${locale}.json`);
Expand Down
1 change: 1 addition & 0 deletions translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ resourceSaveError:
scaleFailed: The service scale could not be updated.
unavailable: The save could not be completed. The resource may be unavailable, or you may not have permission. Refresh to check what was saved before retrying.
actionUnavailable: The action could not be completed. The resource may be unavailable, or you may not have permission. Refresh to check its current state before trying again.
certificateInUse: This certificate is still used by a load balancer service. Remove its certificate references before deleting it.
validation: The server rejected the changes. Check the fields and try again.
failed: The save could not be completed. Refresh to check what was saved before retrying.

Expand Down
1 change: 1 addition & 0 deletions translations/ja-jp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ resourceSaveError:
scaleFailed: サービスの数を更新できませんでした。
unavailable: 保存を完了できませんでした。リソースを利用できないか、権限がない可能性があります。再試行する前にページを再読み込みして、保存済みの内容を確認してください。
actionUnavailable: 操作を完了できませんでした。リソースを利用できないか、権限がない可能性があります。再試行する前にページを再読み込みして、現在の状態を確認してください。
certificateInUse: この証明書はロードバランサーサービスで使用中です。サービスの証明書参照を解除してから、この証明書を削除してください。
validation: サーバーが変更を受け付けませんでした。入力項目を確認して、もう一度お試しください。
failed: 保存を完了できませんでした。再試行する前にページを再読み込みして、保存済みの内容を確認してください。
haPage:
Expand Down
1 change: 1 addition & 0 deletions translations/zh-tw.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,7 @@ resourceSaveError:
scaleFailed: 無法更新服務數量。
unavailable: 無法完成儲存。資源可能無法使用,或您沒有權限。請先重新整理並確認已儲存的內容,再重試。
actionUnavailable: 無法完成此操作。資源可能無法使用,或您沒有權限。請先重新整理並確認目前狀態,再重試。
certificateInUse: 此憑證仍被負載平衡服務使用。請先解除服務的憑證參照,再刪除此憑證。
validation: 伺服器未接受變更。請檢查欄位後重試。
failed: 無法完成儲存。請先重新整理並確認已儲存的內容,再重試。
haPage:
Expand Down
Loading