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
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,29 @@ PastureStack is an independent community effort to preserve, audit, and moderniz

## Project status

The current source compatibility target is `1.6.154`. It retains the existing Node 24, Ember, Sass,
The current source compatibility target is `1.6.155`. 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.

Release `1.6.155` positions global notices below the navigation bar using the
existing navbar height and keeps them inside narrow viewports. This corrects
the mobile layout overlap found during `1.6.154` acceptance without changing
authorization or API behavior. See the
[release note](docs/releases/web-console-1.6.155.md) for focused visual tests;
packaged Server/browser acceptance must be checked against a new image.

Release `1.6.154` shows a localized, persistent permission error when a direct
Stack or Service creation URL is denied, then returns to the Stacks list.
Service upgrade URLs continue to use update permission and receive an update
error only when that permission is absent. The shared message covers all
resource types using the route guard. See the
[release note](docs/releases/web-console-1.6.154.md) for source test evidence;
packaged browser and Server acceptance remain pending.
[release note](docs/releases/web-console-1.6.154.md) for source test evidence.
It was packaged in Server `v1.6.488`; its 8080 browser acceptance identified
the notification/navbar overlap addressed by `1.6.155`.

Release `1.6.153` makes Stack, Service, and Container write controls check
their current project/resource capability when the user acts; delayed project
Expand Down
2 changes: 1 addition & 1 deletion app/styles/app-dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ $k8sHeader: theme.$k8sHeader;
//
// Component-specific files.
//******************************************
@include meta.load-css("app/styles/components/growl", $with: ("brand-danger": $brand-danger));
@include meta.load-css("app/styles/components/growl", $with: ("brand-danger": $brand-danger, "navbar-height": $navbar-height));
@include meta.load-css("app/styles/components/resource-actions", $with: ("text-color": $text-color));
@include meta.load-css("app/styles/components/login", $with: ("primary": $primary));
@include meta.load-css("app/styles/components/mfa");
Expand Down
2 changes: 1 addition & 1 deletion app/styles/app-light.scss
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ $k8sHeader: theme.$k8sHeader;
//
// Component-specific files.
//******************************************
@include meta.load-css("app/styles/components/growl", $with: ("brand-danger": $brand-danger));
@include meta.load-css("app/styles/components/growl", $with: ("brand-danger": $brand-danger, "navbar-height": $navbar-height));
@include meta.load-css("app/styles/components/resource-actions", $with: ("text-color": $text-color));
@include meta.load-css("app/styles/components/login", $with: ("primary": $primary));
@include meta.load-css("app/styles/components/mfa");
Expand Down
5 changes: 5 additions & 0 deletions app/styles/components/_growl.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
$brand-danger: null !default;
$navbar-height: null !default;

.jGrowl.top-right {
// jGrowl's notification margin supplies the gap below the top navigation.
top: $navbar-height;
z-index: 10;
}

Expand All @@ -14,6 +17,8 @@ $brand-danger: null !default;
.jGrowl-notification,
.jGrowl-closer {
width: 300px;
// Leave room for jGrowl's 10px margin on each side of a narrow viewport.
max-width: calc(100vw - 20px);
opacity: 1;
color: white;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "@pasturestack/web-console",
"version": "1.6.154",
"version": "1.6.155",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pasturestack/web-console",
"version": "1.6.154",
"version": "1.6.155",
"license": "Apache-2.0",
"dependencies": {
"sass": "1.103.1"
Expand Down
19 changes: 19 additions & 0 deletions docs/releases/web-console-1.6.155.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Web Console 1.6.155

Server v1.6.488 packaged Web Console 1.6.154 and correctly denied direct
Stack/Service creation without permission. Browser screenshots at 375×812 and
1440×900 exposed a separate layout defect: the global error notice started at
10px while the 45px top navigation was still underneath it. The message was
technically inside the viewport but visually obscured the navigation.

The shared growl stylesheet now starts top-right notices below the existing
`$navbar-height` token, retaining the vendor notification margin as the gap.
The notification width is capped to the viewport minus its two 10px margins.
This applies to light/dark themes and LTR/RTL without changing route guards,
API permissions, or notification text.

The focused Chrome QUnit layout test covers both themes, both directions and
1440px, 375px, and 280px widths. It checks notice/nav separation, viewport
fit, dismiss-control placement and opacity. Local production/development
build and the focused test passed. Official release CI and packaged 8080
browser acceptance are separate gates and must be recorded after publication.
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.154",
"version": "1.6.155",
"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.154" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.154"
if [[ "$version" != "1.6.155" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.155"
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.154 browser-session broker-broadcast
1.6.155 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.154":
if package.get("version") != "1.6.155":
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
2 changes: 1 addition & 1 deletion scripts/check-ui-sass-import-boundary
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ configured_load_css_expected = {
"app/styles/layout/footer": ['"footer": $footer', '"midGrayDark": $midGrayDark'],
"app/styles/layout/layout": ['"accent-two": $accent-two', '"body-bg": $body-bg', '"bold-weight": $bold-weight', '"brand-primary": $brand-primary', '"font-size-small": $font-size-small', '"icon-check": $icon-check', '"lightGrayDark": $lightGrayDark', '"lightGrayTwo": $lightGrayTwo', '"lightestGray": $lightestGray', '"line-height-base": $line-height-base', '"line-height-computed": $line-height-computed', '"main-padding": $main-padding', '"midGray": $midGray', '"normal-weight": $normal-weight', '"orange": $orange', '"page-header-border-color": $page-header-border-color', '"primary": $primary', '"tab-bg": $tab-bg', '"tab-color": $tab-color', '"tab-hover": $tab-hover', '"table-bg-accent": $table-bg-accent', '"table-bg-hover": $table-bg-hover', '"table-border-color": $table-border-color', '"table-cell-padding": $table-cell-padding', '"tealTwo": $tealTwo', '"well-bg": $well-bg', '"well-border": $well-border'],
"app/styles/layout/flex-box": ['"table-border-color": $table-border-color'],
"app/styles/components/growl": ['"brand-danger": $brand-danger'],
"app/styles/components/growl": ['"brand-danger": $brand-danger', '"navbar-height": $navbar-height'],
"app/styles/components/resource-actions": ['"text-color": $text-color'],
"app/styles/components/login": ['"primary": $primary'],
"app/styles/components/progress-bar": ['"lightestGray": $lightestGray'],
Expand Down
61 changes: 61 additions & 0 deletions tests/unit/vendor/growl-viewport-layout-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { module, test } from 'qunit';

module('Unit | Vendor | Growl viewport layout');

function growlFrame(asset, direction) {
return new Promise((resolve, reject) => {
let frame = document.createElement('iframe');
let timer = setTimeout(() => reject(new Error(`Timed out loading ${asset}`)), 10000);

frame.style.cssText = 'position:absolute;top:0;left:0;width:1440px;height:600px;opacity:0;pointer-events:none;border:0';
frame.onload = () => {
clearTimeout(timer);
resolve(frame);
};
frame.srcdoc = `<!doctype html><html dir="${direction}"><head>
<link rel="stylesheet" href="/assets/vendor.css">
<link rel="stylesheet" href="/assets/${asset}">
</head><body style="margin:0">
<header><nav class="navbar"></nav></header>
<div id="jGrowl" class="jGrowl top-right">
<div class="jGrowl-notification error" style="display:block">
<div class="jGrowl-close">×</div>
<div class="jGrowl-header">沒有權限建立服務</div>
<div class="jGrowl-message">您沒有權限在此專案建立服務。</div>
</div>
</div></body></html>`;
document.getElementById('qunit-fixture').appendChild(frame);
});
}

test('global notices clear the navbar and fit desktop and mobile viewports', async function(assert) {
for (let theme of ['light', 'dark']) {
for (let direction of ['ltr', 'rtl']) {
let asset = `ui-${theme}${direction === 'rtl' ? '.rtl' : ''}.css`;
let frame = await growlFrame(asset, direction);
let page = frame.contentDocument;
let notice = page.querySelector('.jGrowl-notification');

for (let width of [1440, 375, 280]) {
frame.style.width = `${width}px`;
await new Promise((resolve) => frame.contentWindow.requestAnimationFrame(resolve));

let navbar = page.querySelector('nav.navbar').getBoundingClientRect();
let bounds = notice.getBoundingClientRect();
let close = page.querySelector('.jGrowl-close').getBoundingClientRect();
let context = `${asset} ${width}px`;

assert.ok(bounds.top >= navbar.bottom, `${context}: notice starts below the navbar`);
assert.ok(bounds.left >= -1 && bounds.right <= width + 1,
`${context}: notice stays within the viewport`);
assert.ok(bounds.width > 0 && bounds.height > 0, `${context}: notice remains visible`);
assert.ok(close.left >= bounds.left && close.right <= bounds.right,
`${context}: dismiss control remains inside the notice`);
assert.strictEqual(frame.contentWindow.getComputedStyle(notice).opacity, '1',
`${context}: notice remains opaque and readable`);
}

frame.remove();
}
}
});
Loading