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
6 changes: 6 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Compatibility Contract

Candidate `1.6.180` forwards the explicit audit `timeScope=all` contract for both
list/poll and JSON/XLSX export. It requires Server v1.6.518's matching broker
support. Explicit dates still bound the query; unset scope retains the default
24-hour window. Permission filtering, pagination, scan caps and record retention
remain backend-owned. See the [release note](docs/releases/web-console-1.6.180.md).

Web Console preserves compatible API paths, schema and resource names, action names, setting keys, authentication routes, catalog fields, orchestration framework identifiers, generated model properties, and server-provided links.

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.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ history, authorship, licenses, and dependency notices.

## Current release

The current source targets `1.6.180`, fixing all-time audit list and export
queries with the matching Server v1.6.518 broker. See its
[release note](docs/releases/web-console-1.6.180.md). The GitHub Release,
not this source-version statement, determines publication availability.

[Web Console 1.6.179](https://github.com/PastureStack/web-console/releases/tag/1.6.179)
packages the reviewed Moment 2.31.0 and compatible dependency updates already
merged on `main`, plus the official shell-quote 1.11.0 security fix in both npm
Expand Down
9 changes: 5 additions & 4 deletions app/admin-tab/audit-logs/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ export default Controller.extend(Sortable, {
clientIp : this.get('clientIp'),
created_gte : this.get('createdFrom'),
created_lte : this.get('createdTo'),
timeScope : this.get('timeScope'),
description : this.get('description'),
eventType : this.get('eventType'),
format,
Expand Down Expand Up @@ -500,12 +501,12 @@ export default Controller.extend(Sortable, {
filters.authType = this.get('authType');
filters.authenticatedAsAccountId = this.get('authenticatedAsAccountId');
filters.clientIp = this.get('clientIp');
if (this.get('timeScope') === 'all') {
filters.createdFrom = null;
filters.createdTo = null;
} else if (this.get('createdFrom') || this.get('createdTo')) {
if (this.get('createdFrom') || this.get('createdTo')) {
filters.createdFrom = localDateTime(this.get('createdFrom'));
filters.createdTo = localDateTime(this.get('createdTo'));
} else if (this.get('timeScope') === 'all') {
filters.createdFrom = null;
filters.createdTo = null;
}
filters.description = this.get('description');
filters.descriptionOperator = this.get('descriptionOperator') || 'contains';
Expand Down
4 changes: 4 additions & 0 deletions app/admin-tab/audit-logs/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ export default Route.extend({
if (params.createdTo) {
returnValue.filter.created_lte = params.createdTo;
}
if (params.timeScope) {
// The broker otherwise applies its default 24-hour window.
returnValue.filter.timeScope = params.timeScope;
}

['accountId', 'authenticatedAsAccountId', 'resourceType', 'resourceId', 'clientIp', 'authType', 'interactionChannel'].forEach((key) => {
if (params[key]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "@pasturestack/web-console",
"version": "1.6.179",
"version": "1.6.180",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@pasturestack/web-console",
"version": "1.6.179",
"version": "1.6.180",
"license": "Apache-2.0",
"dependencies": {
"sass": "1.103.1"
Expand Down
25 changes: 25 additions & 0 deletions docs/releases/web-console-1.6.180.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Web Console 1.6.180 — audit all-time query contract

The audit list, automatic refresh and JSON/XLSX exports now forward
`timeScope=all` to the permission-bound console broker. Previously the URL and
visible controls said all time, but the missing API parameter caused the broker
to apply its normal 24-hour default. Retained older records therefore appeared
absent; this was not database deletion.

Explicit date bounds remain effective even if a bookmarked URL also contains
the all-time marker, and the editable date controls display those same bounds.
Unset scope retains the existing default. The backend remains authoritative for
scope/date validation, authorization, pagination and the 20,000-row scan limit.
All time means all records still retained, not recovery of expired records.
This component requires the matching broker contract in Server v1.6.518.

Focused unit regressions cover scope forwarding, default behavior, explicit
dates, invalid-scope forwarding, JSON/XLSX export and draft/query agreement.
Publication uses the existing exact-source validation workflow, Chrome tests
and two byte-identical production archives. The release records the actual CI
and archive checksum after they succeed; this note is not test evidence.

Dependencies, authentication/session protections, permissions, HAProxy and
retention settings are unchanged. Component CI, isolated deployed browser
acceptance and company production deployment are separate results. Historical
HOLDs and the incomplete full permission matrix are not promoted by this fix.
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.179",
"version": "1.6.180",
"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.179" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.179"
if [[ "$version" != "1.6.180" ]]; then
echo "UNEXPECTED_UI_ARTIFACT_VERSION version=$version expected=1.6.180"
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 @@ -143,4 +143,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.179 browser-session broker-broadcast
1.6.180 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.179":
if package.get("version") != "1.6.180":
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
52 changes: 52 additions & 0 deletions tests/unit/admin-tab/audit-logs/controller-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,58 @@ function controllerFor(properties = {}) {

module('Unit | Controller | admin tab | audit logs');

test('all-time JSON and XLSX exports use the same scope and authorization filters as the list', function(assert) {
let controller = controllerFor({
timeScope: 'all', accountId: '1e1', authenticatedAsAccountId: '1a1',
createdFrom: null, createdTo: null, eventType: 'resource.', eventTypeOperator: 'startsWith',
});

['json', 'xlsx'].forEach((format) => {
// download() reuses this element: capture the URL without initiating a network request.
let target = document.createElement('div');
target.id = `audit-log-export-${format}`;
document.body.appendChild(target);
try {
controller.actions.exportLogs.call(controller, format);
let query = new URL(target.src, window.location.origin);
assert.strictEqual(query.pathname, '/v2-beta/pasturestack/audit-logs/export');
assert.strictEqual(query.searchParams.get('timeScope'), 'all', `${format} preserves all-time intent`);
assert.strictEqual(query.searchParams.get('format'), format);
assert.strictEqual(query.searchParams.get('accountId'), '1e1');
assert.strictEqual(query.searchParams.get('authenticatedAsAccountId'), '1a1');
assert.strictEqual(query.searchParams.get('eventType_prefix'), 'resource.');
assert.notOk(query.searchParams.has('created_gte'), 'no artificial start date');
assert.notOk(query.searchParams.has('created_lte'), 'no artificial end date');

controller.setProperties({createdFrom: '2026-09-01T00:00:00.000Z', createdTo: '2026-10-01T00:00:00.000Z'});
controller.actions.exportLogs.call(controller, format);
query = new URL(target.src, window.location.origin);
assert.strictEqual(query.searchParams.get('created_gte'), controller.get('createdFrom'));
assert.strictEqual(query.searchParams.get('created_lte'), controller.get('createdTo'));
controller.setProperties({timeScope: null, createdFrom: null, createdTo: null});
controller.actions.exportLogs.call(controller, format);
query = new URL(target.src, window.location.origin);
assert.notOk(query.searchParams.has('timeScope'), 'the normal default is not promoted to all time');
controller.set('timeScope', 'all');
} finally {
target.remove();
}
});

destroyOwned(controller);
});

test('a bookmarked all-time marker does not hide explicit date bounds in the draft', function(assert) {
let controller = controllerFor({
timeScope: 'all', createdFrom: '2026-09-01T00:00:00.000Z', createdTo: '2026-10-01T00:00:00.000Z',
});
controller.syncDraftFromQuery();
assert.strictEqual(moment(controller.get('filters.createdFrom')).toISOString(), controller.get('createdFrom'));
assert.strictEqual(moment(controller.get('filters.createdTo')).toISOString(), controller.get('createdTo'));
assert.notEqual(controller.get('activeTimePreset'), 'all', 'the displayed range agrees with the API query');
destroyOwned(controller);
});

test('offers friendly environment and user names without raw ID fallbacks', function(assert) {
let controller = controllerFor({
model: EmberObject.create({
Expand Down
24 changes: 22 additions & 2 deletions tests/unit/admin-tab/audit-logs/route-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,28 @@ test('supports useful text operators without leaking UI-only fields', function(a
'the presentation-only operator never reaches GDAPI');
assert.strictEqual(route.parseFilters({interactionChannel: 'web_ui'}).filter.interactionChannel,
'web_ui', 'interaction channel is forwarded to the permission-bound endpoint');
assert.notOk('timeScope' in route.parseFilters({timeScope: 'all'}).filter,
'the frontend all-time marker never reaches GDAPI');

destroyOwned(route);
});

test('forwards all-time intent while preserving dates, permissions and polling bounds', function(assert) {
let route = createOwned(AuditLogsRoute, {}, 'route');
let query = route.parseFilters({timeScope: 'all', accountId: '1a5'});

assert.deepEqual(query.filter, {timeScope: 'all', accountId: '1a5'},
'an all-time request reaches the permission-bound broker without invented dates');
assert.strictEqual(query.url, 'pasturestack/audit-logs');
assert.strictEqual(query.limit, 100);
assert.strictEqual(query.depaginate, false);
assert.deepEqual(route.parseFilters({}).filter, {}, 'the default still uses the broker 24-hour window');
assert.deepEqual(route.parseFilters({timeScope: null}).filter, {}, 'an unset scope is omitted');
assert.deepEqual(route.parseFilters({
timeScope: 'all', createdFrom: '2026-09-01T00:00:00.000Z', createdTo: '2026-10-01T00:00:00.000Z',
}).filter, {
timeScope: 'all', created_gte: '2026-09-01T00:00:00.000Z', created_lte: '2026-10-01T00:00:00.000Z',
}, 'explicit dates are never dropped when a bookmarked URL also contains all');
assert.strictEqual(route.parseFilters({timeScope: 'invalid'}).filter.timeScope, 'invalid',
'the authoritative broker can reject an invalid bookmarked scope instead of silently changing it');

destroyOwned(route);
});
Expand Down
Loading