Environment UI Indicator¶
Executive Summary¶
This feature provides clear visual indication of the current environment (Production, Staging, Preview) and displays comprehensive version information for all deployed services. It prevents confusion between test and production data while providing developers and users with essential debugging information.
Key Objectives:
- Make environment and database clearly visible to prevent data confusion
- Display all service versions (K8s and Lambda) with pre-release indicators
- Provide non-disruptive UI that maintains accurate production preview
- Show clear warnings when users dismiss environment indicators
Implementation Status¶
| Phase | Status | Description |
|---|---|---|
| Phase 1: Backend Endpoint | ✅ Complete | API service /api/application/info endpoint implemented |
| Phase 2: Frontend Core | ✅ Complete | Config interfaces, env-mapping.yaml, Helm chart values updated |
| Phase 3: Components | ✅ Complete | Environment banner, indicator, and warning dialog created |
| Phase 4: Dialog Enhancement | ✅ Complete | VersionInfoDialog enhanced with environment info and PM service versions |
| Phase 5: Integration | ✅ Complete | Components integrated in app.component |
| Health Endpoint Enhancement | ✅ Complete | /health/live endpoints now include version info in JSON response |
Recent Changes (2025-12-28):
- Added
VersionHealthCheckclass toSyRF.WebHostConfig.Commonfor dual-purpose health endpoints - Updated
SyrfHealthCheckExtensionsto register version health check and output JSON with version data - Modified
AddSyrfInstrumentationAndHealthChecksto include version health check automatically - All .NET services (API, PM, Quartz) now expose version info via
/health/live - PM service added
/api/application/infoendpoint viaApplicationController.cs - Frontend
VersionInfoDialogfetches version info from both API and PM services
Pending:
- Deploy to staging for validation
- Manual testing of all environments
Problem Statement¶
Current Issues¶
- No environment visibility: Users cannot easily tell if they're on staging, preview, or production
- Database confusion: No indication of which database is being used (especially important for PR previews with isolated databases)
- Version information incomplete: Current Version Info Dialog shows Web and API versions but:
- PM Service shows as "unavailable" (no pmOrigin configured)
- Quartz Service not shown
- S3 Notifier Lambda not shown
- Database name not shown
- No warning on non-production: Users may enter data thinking it's production
User Stories¶
- As a developer, I need to know which database my PR preview is using so I don't confuse test data with production data
- As a tester, I need to see all service versions to verify the correct build is deployed
- As a support engineer, I need to quickly identify the environment and versions when debugging user issues
- As a user, I need clear warning that I'm not on production to avoid entering real data in test environments
Solution Design¶
UX Flow¶
┌─────────────────────────────────────────────────────────────────────────────┐
│ UX STATE MACHINE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ [App Load - Non-Production] │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ ⚠️ STAGING │ Database: syrf_staging │ Click for details [×] ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ │ │ │
│ │ (click banner text) │ (click × dismiss) │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────────────┐ │
│ │ VERSION INFO DIALOG │ │ ENVIRONMENT WARNING DIALOG │ │
│ │ (full service info) │ │ │ │
│ │ │ │ ⚠️ NON-PRODUCTION │ │
│ │ See "Dialog Layout" │ │ │ │
│ │ section below │ │ You are using STAGING. │ │
│ │ │ │ │ │
│ └──────────────────────┘ │ • Data may be reset anytime │ │
│ │ • Changes won't affect prod │ │
│ │ • For testing purposes only │ │
│ │ │ │
│ │ Database: syrf_staging │ │
│ │ │ │
│ │ [Keep Banner] [Hide Banner] │ │
│ └──────────────────────────────┘ │
│ │ │ │
│ (Keep Banner) │ │ (Hide) │
│ │ │ │
│ ┌────────────────────────┘ │ │
│ │ ▼ │
│ │ ┌───────────────────────────────┐ │
│ │ │ [STG ●] │ │
│ │ │ │ │
│ │ │ Discrete corner indicator: │ │
│ │ │ - Does NOT disrupt layout │ │
│ │ │ - Accurate production preview│ │
│ │ │ - Click → Version Info Dialog│ │
│ │ └───────────────────────────────┘ │
│ │ │ │
│ │ │ (click indicator) │
│ ▼ ▼ │
│ [Banner restored] [Version Info Dialog] │
│ │
│ ───────────────────────────────────────────────────────────────────────── │
│ [App Load - Production] │
│ │ │
│ ▼ │
│ No banner shown. Footer version link opens Version Info Dialog. │
│ Dialog shows "Production" badge with database: syrftest │
└─────────────────────────────────────────────────────────────────────────────┘
Version Info Dialog Layout¶
┌──────────────────────────────────────────────────────────────────────────────┐
│ Version & Environment Info │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ ENVIRONMENT │
│ ──────────────────────────────────────────────────────────────────────── │
│ Environment [STAGING] (badge with environment-specific color) │
│ Database syrf_staging │
│ PR Number N/A (or "2234" for preview environments) │
│ │
│ SERVICE VERSIONS (Kubernetes) │
│ ──────────────────────────────────────────────────────────────────────── │
│ Web Application v5.0.1 ✓ Healthy │
│ API Service v8.21.0 ✓ Healthy │
│ PM Service v10.45.0-PullRequest2234.1 ✓ Pre-release│
│ Quartz Service v0.5.2 ✓ Healthy │
│ │
│ SERVICE VERSIONS (Lambda) │
│ ──────────────────────────────────────────────────────────────────────── │
│ S3 File Notifier v1.2.0 ✓ Healthy │
│ │
│ BUILD INFORMATION │
│ ──────────────────────────────────────────────────────────────────────── │
│ Monorepo SHA abc1234def (global across all services) │
│ Web Full Version 5.0.1-PullRequest2234.1+abc1234def │
│ │
│ [Copy All Info] [Close] │
└──────────────────────────────────────────────────────────────────────────────┘
Environment-Specific Styling¶
| Environment | Banner Color | Badge Color | Indicator Text |
|---|---|---|---|
| Production | (not shown) | Green #4caf50 | (not shown) |
| Staging | Orange #ff9800 | Orange #ff9800 | STG |
| Preview | Blue #2196f3 | Blue #2196f3 | PR-{number} |
| Development | Pink #e91e63 | Pink #e91e63 | DEV |
Discrete Indicator Specification¶
The indicator shown when banner is dismissed must be:
- Non-disruptive: Does not affect page layout or content positioning
- Fixed position: Bottom-right corner, overlaying content
- Small footprint: Approximately 48x24 pixels
- Semi-transparent: 85% opacity, increases to 100% on hover
- Accessible: Click opens Version Info Dialog
.environment-indicator {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 1000;
padding: 4px 12px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
opacity: 0.85;
cursor: pointer;
transition: opacity 0.2s;
}
.environment-indicator:hover {
opacity: 1;
}
Technical Architecture¶
Backend Changes¶
New Endpoint: GET /api/application/info¶
File: src/services/api/SyRF.API.Endpoint/Controllers/ApplicationController.cs
[AllowAnonymous]
[HttpGet("application/info")]
[ProducesResponseType(typeof(ApplicationInfoDto), 200)]
public ActionResult<ApplicationInfoDto> GetApplicationInfo()
{
return Ok(new ApplicationInfoDto
{
Environment = _configuration["runtimeEnvironment"] ?? "Unknown",
DatabaseName = _mongoSettings.DatabaseName,
PrNumber = _syrfSettings.PrNumber,
IsProduction = _configuration["runtimeEnvironment"]?.Equals("Production",
StringComparison.OrdinalIgnoreCase) ?? false,
ApiVersion = _gitVersion.SemVer,
ApiFullVersion = _gitVersion.InformationalVersion,
MonorepoSha = _gitVersion.Sha
});
}
New DTO: src/services/api/SyRF.API.Endpoint/DTOs/ApplicationInfoDto.cs
public class ApplicationInfoDto
{
public string Environment { get; set; } = string.Empty;
public string DatabaseName { get; set; } = string.Empty;
public string? PrNumber { get; set; }
public bool IsProduction { get; set; }
public string ApiVersion { get; set; } = string.Empty;
public string ApiFullVersion { get; set; } = string.Empty;
public string MonorepoSha { get; set; } = string.Empty;
}
Required Injections:
IConfiguration- for runtimeEnvironmentMongoConnectionSettings- for DatabaseNameSyrfSettings- for PrNumberGitVersion- for version info
Frontend Changes¶
Configuration Updates¶
File: src/services/web/src/assets/data/env.template.json
Add new configuration properties:
{
"pmOrigin": "${SYRF__PmOrigin}",
"quartzOrigin": "${SYRF__QuartzOrigin}",
"s3NotifierVersion": "${SYRF__S3NotifierVersion}"
}
File: src/services/web/src/app/core/services/config/app-settings.interface.ts
export interface AppSettings {
// ... existing properties
pmOrigin?: string;
quartzOrigin?: string;
s3NotifierVersion?: string;
}
New Service: EnvironmentInfoService¶
File: src/services/web/src/app/core/services/environment-info/environment-info.service.ts
@Injectable({ providedIn: 'root' })
export class EnvironmentInfoService {
private readonly http = inject(HttpClient);
private readonly config = inject(AppConfigService).config;
private readonly _environmentInfo = signal<EnvironmentInfo | null>(null);
private readonly _bannerDismissed = signal<boolean>(false);
readonly environmentInfo = this._environmentInfo.asReadonly();
readonly bannerDismissed = this._bannerDismissed.asReadonly();
readonly isNonProduction = computed(() =>
this._environmentInfo()?.isProduction === false
);
readonly showBanner = computed(() =>
this.isNonProduction() && !this._bannerDismissed()
);
readonly showIndicator = computed(() =>
this.isNonProduction() && this._bannerDismissed()
);
constructor() {
// Restore dismissed state from sessionStorage
const dismissed = sessionStorage.getItem('env-banner-dismissed');
if (dismissed === 'true') {
this._bannerDismissed.set(true);
}
}
fetchEnvironmentInfo(): Observable<EnvironmentInfo> {
return this.http.get<EnvironmentInfo>(`${this.config.apiOrigin}/api/application/info`);
}
dismissBanner(): void {
this._bannerDismissed.set(true);
sessionStorage.setItem('env-banner-dismissed', 'true');
}
restoreBanner(): void {
this._bannerDismissed.set(false);
sessionStorage.removeItem('env-banner-dismissed');
}
}
New Components¶
- EnvironmentBannerComponent - Top banner for non-production
- EnvironmentIndicatorComponent - Discrete corner indicator
- EnvironmentWarningDialogComponent - Warning dialog on dismiss
Enhanced Version Info Dialog¶
Modify existing VersionInfoDialogComponent to:
- Add Environment section at top
- Fetch PM and Quartz versions from their health endpoints
- Display S3 Notifier version from config
- Show pre-release indicator for versions containing
- - Group services by deployment type (Kubernetes vs Lambda)
Helm Chart Changes¶
File: src/services/web/.chart/templates/_env-blocks.tpl
Add environment variables for new service origins:
- name: SYRF__PmOrigin
value: {{ .Values.pmOrigin | default (printf "http://pm-service.%s.svc.cluster.local" .Release.Namespace) }}
- name: SYRF__QuartzOrigin
value: {{ .Values.quartzOrigin | default (printf "http://quartz-service.%s.svc.cluster.local" .Release.Namespace) }}
- name: SYRF__S3NotifierVersion
value: {{ .Values.s3NotifierVersion | default "unknown" }}
Health Endpoint Design¶
Dual-Purpose Health Endpoints¶
The /health/live endpoints serve two purposes:
- Kubernetes Health Probes - Liveness/readiness checks for pod lifecycle
- Version Information - Expose service version for UI display
This dual-purpose design avoids creating separate version endpoints while maintaining full compatibility with Kubernetes health checking.
Response Schema¶
// GET /health/live
{
"status": "Healthy",
"version": "8.21.0",
"gitVersion": "8.21.0",
"gitSha": "abc1234def5678",
"gitInformationalVersion": "8.21.0+abc1234def5678"
}
Kubernetes Compatibility¶
Health probes only check HTTP status codes (200 = healthy), not response body:
# Kubernetes probe configuration (existing)
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
Why this works:
- Kubernetes probes ignore response body - only status code matters
- Adding version fields to JSON response has zero impact on probe behavior
- Response remains lightweight (< 200 bytes)
Current Implementation¶
The existing health endpoints already include version info via ASP.NET Core health checks:
// In Program.cs or Startup.cs
builder.Services.AddHealthChecks()
.AddCheck<VersionHealthCheck>("version");
The VersionHealthCheck injects GitVersion and adds version metadata:
public class VersionHealthCheck : IHealthCheck
{
private readonly GitVersion _gitVersion;
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
var data = new Dictionary<string, object>
{
["version"] = _gitVersion.SemVer,
["gitSha"] = _gitVersion.Sha,
["gitInformationalVersion"] = _gitVersion.InformationalVersion
};
return Task.FromResult(HealthCheckResult.Healthy("OK", data));
}
}
Service Health Endpoint Matrix¶
| Service | Endpoint | Response Includes | Notes |
|---|---|---|---|
| API | /health/live |
version, gitSha | Already implemented |
| PM | /health/live |
version, gitSha | Already implemented |
| Quartz | /health/live |
version, gitSha | Already implemented |
| Web | N/A (static) | Via config | NGINX serves static files |
| S3 Notifier | N/A (Lambda) | Via config | Version injected at deploy time |
Frontend Fetching Strategy¶
// Fetch versions from health endpoints
async fetchServiceVersions(): Promise<ServiceVersions> {
const [apiHealth, pmHealth, quartzHealth] = await Promise.allSettled([
this.http.get<HealthResponse>(`${this.config.apiOrigin}/health/live`).toPromise(),
this.http.get<HealthResponse>(`${this.config.pmOrigin}/health/live`).toPromise(),
this.http.get<HealthResponse>(`${this.config.quartzOrigin}/health/live`).toPromise(),
]);
return {
api: this.extractVersion(apiHealth),
pm: this.extractVersion(pmHealth),
quartz: this.extractVersion(quartzHealth),
s3Notifier: this.config.s3NotifierVersion, // From config, not health check
web: this.config.gitVersion, // From config
};
}
Error Handling¶
If a health endpoint is unreachable:
- Show "Unavailable" status in Version Info Dialog
- Don't block other version fetches (use
Promise.allSettled) - Log warning but don't show error to user
Version Information Strategy¶
Service-Specific SemVer¶
Each service maintains its own semantic version:
| Service | Version Source | Example |
|---|---|---|
| Web | gitVersion in config |
v5.0.1 |
| API | /health/live endpoint |
v8.21.0 |
| PM | /health/live endpoint |
v10.45.0 |
| Quartz | /health/live endpoint |
v0.5.2 |
| S3 Notifier | Config (set at deploy time) | v1.2.0 |
Pre-Release Detection¶
Versions containing - are pre-release builds:
v10.45.0-PullRequest2234.1→ Pre-release (PR build)v10.45.0-alpha.1→ Pre-release (alpha)v10.45.0→ Stable release
Display pre-release versions with visual indicator in dialog.
Global Monorepo SHA¶
The Git SHA is shared across all services in a deployment:
- Stored in each service's
GitVersion.Shaproperty - Represents the monorepo commit that produced the build
- Useful for tracing exact code version across all services
Files Summary¶
Files to Create¶
| File | Purpose |
|---|---|
src/services/api/SyRF.API.Endpoint/DTOs/ApplicationInfoDto.cs |
Response DTO for /application/info |
src/services/web/src/app/core/services/environment-info/environment-info.service.ts |
Environment state management |
src/services/web/src/app/shared/components/environment-banner/ |
Top banner component |
src/services/web/src/app/shared/components/environment-indicator/ |
Corner indicator component |
src/services/web/src/app/shared/dialogs/environment-warning-dialog/ |
Dismiss warning dialog |
Files to Modify¶
| File | Change |
|---|---|
ApplicationController.cs |
Add /api/application/info endpoint |
version-info-dialog.component.* |
Add environment section, all service versions |
app.component.ts |
Add banner/indicator integration |
app.component.html |
Add banner/indicator templates |
app.component.scss |
Add banner and indicator styles |
env.template.json |
Add pmOrigin, quartzOrigin, s3NotifierVersion |
app-settings.interface.ts |
Add new config properties |
_env-blocks.tpl |
Add new environment variables |
Testing Strategy¶
Unit Tests¶
- EnvironmentInfoService
- Fetches environment info correctly
- Handles API errors gracefully
- Persists/restores banner dismissed state
-
Computes isNonProduction correctly
-
EnvironmentBannerComponent
- Shows only for non-production
- Opens Version Info Dialog on click
- Opens Warning Dialog on dismiss
-
Hides when dismissed
-
EnvironmentIndicatorComponent
- Shows only when banner dismissed
- Opens Version Info Dialog on click
-
Displays correct abbreviation (STG, PR-N, DEV)
-
EnvironmentWarningDialogComponent
- Displays correct environment name
- Keep Banner restores banner
-
Hide Banner dismisses and shows indicator
-
Backend Endpoint
- Returns correct environment info
- Returns database name without credentials
- Handles missing configuration gracefully
Integration Tests¶
- Banner → Dialog flow
- Banner → Warning → Indicator flow
- Indicator → Dialog flow
- Session persistence across page reload
Manual Testing Checklist¶
- Local development shows DEV banner
- Staging shows STAGING banner with orange color
- PR preview shows PR-{number} with blue color
- Production shows no banner
- Version Info Dialog shows all service versions
- Pre-release versions show indicator
- Copy Info copies all version details
- Dismissed state persists on page reload
- Indicator is discrete and doesn't disrupt layout
Rollout Plan¶
Phase 1: Backend Endpoint (0.5 day)¶
- Create ApplicationInfoDto
- Add /api/application/info endpoint
- Add unit test
- Deploy to staging
Phase 2: Frontend Core (1 day)¶
- Add EnvironmentInfoService
- Update config interfaces
- Update env.template.json
- Add Helm chart changes
Phase 3: Components (1 day)¶
- Create EnvironmentBannerComponent
- Create EnvironmentIndicatorComponent
- Create EnvironmentWarningDialogComponent
- Add component tests
Phase 4: Dialog Enhancement (0.5 day)¶
- Enhance VersionInfoDialogComponent
- Add PM, Quartz, S3 Notifier versions
- Add environment section
- Update styling
Phase 5: Integration (0.5 day)¶
- Integrate in app.component
- Wire up dismiss flow
- End-to-end testing
- Deploy to staging for validation
Related Documentation¶
- MongoDB Testing Strategy - Database isolation for environments
- Seed Data Quality Analysis - Original UI specs (Parts 10-11)
- MongoDB Infrastructure Future Work - Consolidated future work plan
Appendix: Environment Warning Messages¶
Staging Environment (Isolated Database)¶
You are using the STAGING environment.
- Data may be reset at any time during deployments
- Changes you make will NOT affect production data
- This environment is for testing purposes only
- The database may be switched - your data could be replaced with fresh test data
Database:
syrf_staging
Staging Environment (Production Database - Legacy)¶
⚠️ WARNING: You are using STAGING with the PRODUCTION database.
- Changes you make WILL affect real production data
- This configuration is temporary during migration
- The database may be switched to an isolated staging database
- When switched, any data entered here will no longer be visible
Database:
syrftest(Production)
Preview Environment (Isolated Database)¶
You are using a PR PREVIEW environment.
- This is an isolated environment for PR #{number}
- Data is seeded from test fixtures, not production
- This environment will be deleted when the PR closes
- The database may be reset via workflow dispatch or label triggers
Database:
syrf_pr_{number}
Preview Environment (Production Database - Legacy)¶
⚠️ WARNING: You are using a PR PREVIEW with the PRODUCTION database.
- Changes you make WILL affect real production data
- This configuration may occur before database isolation is complete
- The database may be switched to an isolated PR database
- When switched, you will see seeded test data instead of production data
Database:
syrftest(Production)
Development Environment¶
You are using LOCAL DEVELOPMENT.
- Data is stored in your local/development database
- Changes will not affect any deployed environment
- The database connection may change based on your local configuration
Database:
{configured database name}
Appendix: Database Configuration States¶
The warning dialog dynamically adjusts based on two factors:
| Environment | Database Type | Warning Level | Key Message |
|---|---|---|---|
| Staging | Isolated (syrf_staging) |
Medium | Data may be reset |
| Staging | Production (syrftest) |
High | Affects real data, may be switched |
| Preview | Isolated (syrf_pr_N) |
Medium | Data is seeded, will be deleted |
| Preview | Production (syrftest) |
High | Affects real data, may be switched |
| Development | Any | Low | Local only |
| Production | Production (syrftest) |
None | No warning shown |
Detection Logic¶
// Determine warning severity based on environment + database combination
function getWarningSeverity(env: string, dbName: string): 'high' | 'medium' | 'low' | 'none' {
const isProductionDb = dbName === 'syrftest';
if (env === 'Production') return 'none';
if (isProductionDb) return 'high'; // Non-prod env using prod DB
if (env === 'Development') return 'low';
return 'medium'; // Staging/Preview with isolated DB
}
Visual Differentiation¶
| Warning Level | Banner Color | Icon | Additional Styling |
|---|---|---|---|
| High | Red #f44336 |
warning |
Pulsing animation |
| Medium | Orange #ff9800 |
info |
Standard |
| Low | Pink #e91e63 |
code |
Standard |
| None | (not shown) | - | - |