Skip to content

Commit 7235fbe

Browse files
Redesign About page with 3-col layout and VCS links
Three-column card: logo | frontend+backend versions | signed in. Semantic theme colors, formatted timestamps, native hover tooltips on all truncated fields, GitHub links for commit and branch. Backend branch displayed alongside frontend branch. Admin row full-width with orange tint. Responsive: 2-col tablet, 1-col phone. Unit and E2E tests added.
1 parent d980b97 commit 7235fbe

4 files changed

Lines changed: 321 additions & 61 deletions

File tree

e2e/tests/core/about-page.spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { test, expect } from '../../fixtures/test-base';
2+
3+
/**
4+
* About Page E2E Tests
5+
*
6+
* Verifies version information is displayed correctly on the About page.
7+
* Frontend build info comes from BUILD_INFO (generated by `make fe-version`).
8+
* Backend version info comes from the session API (/api/v1/auth/session/verify).
9+
*
10+
* Use this test to confirm which build is deployed — frontend and backend
11+
* versions should match the intentional build numbers.
12+
*/
13+
test.describe('About Page', () => {
14+
15+
test('should display the about page', async ({ loggedInAdminPage }) => {
16+
const page = loggedInAdminPage.page;
17+
await page.goto('/about');
18+
await page.waitForLoadState('networkidle');
19+
20+
await expect(page.locator('app-page-header')).toBeVisible();
21+
});
22+
23+
test('should display frontend version information', async ({ loggedInAdminPage }) => {
24+
const page = loggedInAdminPage.page;
25+
await page.goto('/about');
26+
await page.waitForLoadState('networkidle');
27+
28+
// Frontend section heading
29+
await expect(page.locator('text=Frontend')).toBeVisible({ timeout: 10000 });
30+
31+
// Version field should be visible and non-empty
32+
const versionItem = page.locator('app-metadata-item').filter({ hasText: /^Version/ }).first();
33+
await expect(versionItem).toBeVisible();
34+
const versionText = await versionItem.textContent();
35+
expect(versionText).toMatch(/v?\d+\.\d+\.\d+/);
36+
});
37+
38+
test('should display frontend commit and branch', async ({ loggedInAdminPage }) => {
39+
const page = loggedInAdminPage.page;
40+
await page.goto('/about');
41+
await page.waitForLoadState('networkidle');
42+
43+
// Commit should be a short SHA
44+
const commitItem = page.locator('app-metadata-item').filter({ hasText: /^Commit/ }).first();
45+
await expect(commitItem).toBeVisible({ timeout: 10000 });
46+
const commitText = await commitItem.textContent();
47+
expect(commitText?.replace('Commit', '').trim().length).toBeGreaterThan(0);
48+
49+
// Branch should be visible
50+
const branchItem = page.locator('app-metadata-item').filter({ hasText: /^Branch/ });
51+
await expect(branchItem).toBeVisible();
52+
const branchText = await branchItem.textContent();
53+
expect(branchText?.replace('Branch', '').trim().length).toBeGreaterThan(0);
54+
});
55+
56+
test('should display backend version information', async ({ loggedInAdminPage }) => {
57+
const page = loggedInAdminPage.page;
58+
await page.goto('/about');
59+
await page.waitForLoadState('networkidle');
60+
61+
// Backend section heading
62+
await expect(page.locator('text=Backend')).toBeVisible({ timeout: 10000 });
63+
64+
// Backend version should be visible
65+
const backendVersionItems = page.locator('app-metadata-item').filter({ hasText: /^Version/ });
66+
// There are two Version fields (frontend + backend)
67+
await expect(backendVersionItems).toHaveCount(2);
68+
});
69+
70+
test('should display database version', async ({ loggedInAdminPage }) => {
71+
const page = loggedInAdminPage.page;
72+
await page.goto('/about');
73+
await page.waitForLoadState('networkidle');
74+
75+
const dbItem = page.locator('app-metadata-item').filter({ hasText: /^Database/ });
76+
await expect(dbItem).toBeVisible({ timeout: 10000 });
77+
const dbText = await dbItem.textContent();
78+
expect(dbText?.replace('Database', '').trim().length).toBeGreaterThan(0);
79+
});
80+
81+
test('should show admin diagnostics button for admin users', async ({ loggedInAdminPage }) => {
82+
const page = loggedInAdminPage.page;
83+
await page.goto('/about');
84+
await page.waitForLoadState('networkidle');
85+
86+
await expect(page.locator('text=You have administrative privileges')).toBeVisible({ timeout: 10000 });
87+
await expect(page.locator('button:has-text("View Diagnostics")')).toBeVisible();
88+
});
89+
90+
test('frontend and backend versions should both be present', async ({ loggedInAdminPage }) => {
91+
const page = loggedInAdminPage.page;
92+
await page.goto('/about');
93+
await page.waitForLoadState('networkidle');
94+
95+
// Get all version text content for manual verification
96+
const allVersionItems = page.locator('app-metadata-item').filter({ hasText: /^Version/ });
97+
const count = await allVersionItems.count();
98+
expect(count).toBe(2);
99+
100+
const versions = await allVersionItems.allTextContents();
101+
// Log for manual verification during testing
102+
console.log('Frontend version:', versions[0]);
103+
console.log('Backend version:', versions[1]);
104+
105+
// Both should have actual version strings
106+
for (const v of versions) {
107+
expect(v.trim().length).toBeGreaterThan('Version'.length);
108+
}
109+
});
110+
111+
});
Lines changed: 131 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,143 @@
11
<app-page-header [showUnderFlow]="true">About</app-page-header>
2-
@if (!customizations.aboutInfoComponent) {
3-
<div class="card mb-6">
4-
<div class="card-body">
5-
<app-stratos-title></app-stratos-title>
6-
<div class="space-y-4">
7-
<div class="text-lg font-semibold text-gray-600">{{ (versionNumber$ | async) }}</div>
8-
<div class="text-base text-gray-700 leading-relaxed">
9-
<app-product-name></app-product-name> provides an easy-to-use web-based UI that allows developers and administrators to manage their applications and Cloud Foundry deployments
10-
</div>
11-
</div>
12-
</div>
13-
</div>
14-
}
15-
<template #aboutInfoContainer></template>
16-
17-
@if (customizations.hasEula) {
18-
<div class="card mb-6">
19-
<div class="card-body">
20-
<div class="mb-4 text-gray-700">Use of this software is subject to the End-User License Agreement</div>
21-
<button [routerLink]="['eula']" class="btn btn-primary">View EULA</button>
22-
</div>
23-
</div>
24-
}
252

3+
<template #aboutInfoContainer></template>
264
<template #supportInfoContainer></template>
275

286
@if ((sessionData$ | async); as session) {
29-
<div class="card mb-6">
30-
<div class="card-body space-y-4">
31-
<app-metadata-item icon="person" label="User">{{ session.user.name }}</app-metadata-item>
32-
</div>
33-
</div>
34-
<div class="card mb-6">
35-
<div class="card-body space-y-4">
36-
<div class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">Frontend</div>
37-
<app-metadata-item icon="web_asset" label="Version">{{ buildInfo.version }}</app-metadata-item>
38-
<app-metadata-item icon="commit" label="Commit">{{ buildInfo.gitCommit }}</app-metadata-item>
39-
<app-metadata-item icon="account_tree" label="Branch">{{ buildInfo.gitBranch }}</app-metadata-item>
40-
<app-metadata-item icon="schedule" label="Built">{{ buildInfo.buildDate }}</app-metadata-item>
41-
</div>
42-
</div>
43-
<div class="card mb-6">
44-
<div class="card-body space-y-4">
45-
<div class="text-sm font-semibold text-gray-500 uppercase tracking-wide mb-2">Backend</div>
46-
<app-metadata-item icon="dns" label="Version">{{ session.version.proxy_version }}</app-metadata-item>
47-
@if (session.version.git_commit) {
48-
<app-metadata-item icon="commit" label="Commit">{{ session.version.git_commit }}</app-metadata-item>
49-
}
50-
@if (session.version.build_date) {
51-
<app-metadata-item icon="schedule" label="Built">{{ session.version.build_date }}</app-metadata-item>
7+
8+
<div class="card overflow-hidden">
9+
10+
<!-- Main layout: logo | frontend+backend (2-col) | user/login -->
11+
<!-- Desktop: 3 cols | Tablet: 2 cols | Phone: 1 col -->
12+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-[1fr_3fr_auto] divide-y sm:divide-y-0 sm:divide-x divide-gray-200 dark:divide-gray-700">
13+
14+
<!-- Col 1: Logo / Theme (hidden on phone) -->
15+
@if (!customizations.aboutInfoComponent) {
16+
<div class="hidden sm:flex p-6 flex-col items-center justify-start text-center gap-2 bg-content-bg/30">
17+
<app-stratos-title></app-stratos-title>
18+
<div class="text-sm text-content-muted">{{ (versionNumber$ | async) }}</div>
19+
</div>
5220
}
53-
<app-metadata-item icon="storage" label="Database">{{ session.version.database_version }}</app-metadata-item>
54-
</div>
55-
</div>
56-
}
5721

58-
@if ((this.userIsAdmin$ | async)) {
59-
<div class="card mb-6 border-orange-200 bg-orange-50">
60-
<div class="card-body">
61-
<div class="flex items-center space-x-4">
62-
<span class="material-icons text-orange-600 text-2xl">security</span>
63-
<div class="flex-1">
64-
<div class="text-gray-800 font-medium mb-2">You have administrative privileges</div>
65-
<button [routerLink]="['diagnostics']" class="btn btn-primary">View Diagnostics</button>
22+
<!-- Col 2: Frontend + Backend nested 2-col grid -->
23+
<div class="lg:col-span-1">
24+
<div class="grid grid-cols-2 divide-x divide-gray-200 dark:divide-gray-700 h-full">
25+
26+
<!-- Frontend -->
27+
<div class="p-5">
28+
<div class="text-xs font-semibold text-content-muted uppercase tracking-wider mb-3">Frontend</div>
29+
<dl class="text-sm space-y-2">
30+
<div class="flex gap-2">
31+
<dt class="text-content-muted w-20 shrink-0">Version</dt>
32+
<dd class="text-content-text font-medium min-w-0 truncate" [title]="buildInfo.version">{{ buildInfo.version }}</dd>
33+
</div>
34+
<div class="flex gap-2">
35+
<dt class="text-content-muted w-20 shrink-0">Commit</dt>
36+
<dd class="text-content-text font-mono min-w-0 truncate" [title]="buildInfo.gitCommit">
37+
@if (gitCommitLink) {
38+
<a [href]="gitCommitLink" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">{{ buildInfo.gitCommit }}</a>
39+
} @else {
40+
{{ buildInfo.gitCommit }}
41+
}
42+
</dd>
43+
</div>
44+
<div class="flex gap-2">
45+
<dt class="text-content-muted w-20 shrink-0">Built</dt>
46+
<dd class="text-content-text min-w-0 truncate" [title]="buildInfo.buildDate">{{ buildInfo.buildDate | date:"yyyy-MM-dd HH:mm 'UTC'":"UTC" }}</dd>
47+
</div>
48+
<div class="flex gap-2">
49+
<dt class="text-content-muted w-20 shrink-0">Branch</dt>
50+
<dd class="text-content-text min-w-0 truncate" [title]="buildInfo.gitBranch">
51+
@if (gitBranchLink) {
52+
<a [href]="gitBranchLink" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">{{ buildInfo.gitBranch }}</a>
53+
} @else {
54+
{{ buildInfo.gitBranch }}
55+
}
56+
</dd>
57+
</div>
58+
</dl>
59+
</div>
60+
61+
<!-- Backend -->
62+
<div class="p-5">
63+
<div class="text-xs font-semibold text-content-muted uppercase tracking-wider mb-3">Backend</div>
64+
<dl class="text-sm space-y-2">
65+
<div class="flex gap-2">
66+
<dt class="text-content-muted w-20 shrink-0">Version</dt>
67+
<dd class="text-content-text font-medium min-w-0 truncate" [title]="session.version.proxy_version">{{ session.version.proxy_version }}</dd>
68+
</div>
69+
<div class="flex gap-2">
70+
<dt class="text-content-muted w-20 shrink-0">Commit</dt>
71+
<dd class="text-content-text font-mono min-w-0 truncate" [title]="session.version.git_commit">
72+
@if (backendCommitLink(session.version.git_commit)) {
73+
<a [href]="backendCommitLink(session.version.git_commit)" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">{{ session.version.git_commit }}</a>
74+
} @else {
75+
{{ session.version.git_commit || '—' }}
76+
}
77+
</dd>
78+
</div>
79+
<div class="flex gap-2">
80+
<dt class="text-content-muted w-20 shrink-0">Built</dt>
81+
<dd class="text-content-text min-w-0 truncate" [title]="session.version.build_date">{{ session.version.build_date ? (session.version.build_date | date:"yyyy-MM-dd HH:mm 'UTC'":"UTC") : '—' }}</dd>
82+
</div>
83+
<div class="flex gap-2">
84+
<dt class="text-content-muted w-20 shrink-0">Branch</dt>
85+
<dd class="text-content-text min-w-0 truncate" [title]="session.version.git_branch">
86+
@if (backendBranchLink(session.version.git_branch)) {
87+
<a [href]="backendBranchLink(session.version.git_branch)" target="_blank" rel="noopener noreferrer" class="text-primary hover:underline">{{ session.version.git_branch }}</a>
88+
} @else {
89+
{{ session.version.git_branch || '—' }}
90+
}
91+
</dd>
92+
</div>
93+
@if (session.diagnostics?.databaseBackend) {
94+
<div class="flex gap-2">
95+
<dt class="text-content-muted w-20 shrink-0">Database</dt>
96+
<dd class="text-content-text truncate" [title]="session.diagnostics.databaseBackend">{{ session.diagnostics.databaseBackend }}</dd>
97+
</div>
98+
}
99+
@if (session.diagnostics?.deploymentType) {
100+
<div class="flex gap-2">
101+
<dt class="text-content-muted w-20 shrink-0">Deployed</dt>
102+
<dd class="text-content-text truncate" [title]="session.diagnostics.deploymentType">{{ session.diagnostics.deploymentType }}</dd>
103+
</div>
104+
}
105+
</dl>
106+
</div>
107+
66108
</div>
67109
</div>
110+
111+
<!-- Col 3: User / Login info — top aligned, centered -->
112+
<div class="p-6 flex flex-col items-center justify-start text-center gap-2 min-w-[120px]">
113+
<div class="text-xs font-semibold text-content-muted uppercase tracking-wider">Signed in as</div>
114+
<div class="font-medium text-content-text text-base">{{ session.user.name }}</div>
115+
@if ((this.userIsAdmin$ | async)) {
116+
<span class="text-xs bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300 px-2 py-0.5 rounded-full">Admin</span>
117+
}
118+
</div>
119+
68120
</div>
121+
122+
<!-- EULA row (conditional, full width) -->
123+
@if (customizations.hasEula) {
124+
<div class="border-t border-gray-200 dark:border-gray-700 px-6 py-4 flex items-center justify-between">
125+
<div class="text-content-muted">Use of this software is subject to the End-User License Agreement</div>
126+
<button [routerLink]="['eula']" class="btn btn-primary">View EULA</button>
127+
</div>
128+
}
129+
130+
<!-- Admin row: full width, subtle orange tint -->
131+
@if ((this.userIsAdmin$ | async)) {
132+
<div class="border-t border-gray-200 dark:border-gray-700 bg-orange-50 dark:bg-orange-900/20 px-6 py-3 flex items-center justify-end gap-4">
133+
<div class="flex items-center gap-2 text-sm text-orange-700 dark:text-orange-300">
134+
<span class="material-icons text-base">security</span>
135+
<span>You have administrative privileges</span>
136+
</div>
137+
<button [routerLink]="['diagnostics']" class="btn btn-primary btn-sm">View Diagnostics</button>
138+
</div>
139+
}
140+
69141
</div>
142+
70143
}

src/frontend/packages/core/src/features/about/about-page/about-page.component.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
22
import { provideZonelessChangeDetection } from '@angular/core';
3-
import { describe, it, expect, beforeEach } from 'vitest';
3+
import { describe, it, expect, beforeEach, vi } from 'vitest';
44
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
55
import { RouterTestingModule } from '@angular/router/testing';
66
import { createBasicStoreModule, STORE_TEST_PROVIDERS, CoreTestingModule } from '@test-framework';
7+
import { of } from 'rxjs';
78

89
import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service';
910
import { TabNavService } from '../../../tab-nav.service';
@@ -40,4 +41,31 @@ describe('AboutPageComponent', () => {
4041
it('should create', () => {
4142
expect(component).toBeTruthy();
4243
});
44+
45+
it('should expose buildInfo from BUILD_INFO token', () => {
46+
expect(component.buildInfo).toBeDefined();
47+
expect(component.buildInfo.version).toBeDefined();
48+
expect(component.buildInfo.gitCommit).toBeDefined();
49+
expect(component.buildInfo.gitBranch).toBeDefined();
50+
expect(component.buildInfo.buildDate).toBeDefined();
51+
});
52+
53+
it('should have non-empty build info fields', () => {
54+
expect(component.buildInfo.version.length).toBeGreaterThan(0);
55+
expect(component.buildInfo.gitCommit.length).toBeGreaterThan(0);
56+
expect(component.buildInfo.gitBranch.length).toBeGreaterThan(0);
57+
expect(component.buildInfo.buildDate.length).toBeGreaterThan(0);
58+
});
59+
60+
it('should expose sessionData$ observable', () => {
61+
expect(component.sessionData$).toBeDefined();
62+
});
63+
64+
it('should expose userIsAdmin$ observable', () => {
65+
expect(component.userIsAdmin$).toBeDefined();
66+
});
67+
68+
it('should expose versionNumber$ observable', () => {
69+
expect(component.versionNumber$).toBeDefined();
70+
});
4371
});

0 commit comments

Comments
 (0)