chore(src): 🔧 Update 12 TypeScript files

This commit is contained in:
Lilith 2026-01-19 15:51:26 -08:00
parent 704c98be4c
commit 8ba9df7d30
12 changed files with 410 additions and 32 deletions

View file

@ -4,6 +4,7 @@ import { AuthModule } from '@/auth/auth.module';
import { DomainModule } from '@/domains/domain.module';
import { EndpointsModule } from '@/endpoints/endpoints.module';
import { ServicesModule } from '@/services/services.module';
import { StorageModule } from '@/storage/storage.module';
import { VPSModule } from '@/vps/vps.module';
import { HealthController } from './health.controller';
@ -57,6 +58,8 @@ import { VersionController } from './version.controller';
EndpointsModule,
// Import ServicesModule for platform services monitoring
ServicesModule,
// Import StorageModule for MetricsStorageService
StorageModule,
// Import AuthModule for FlexibleAuthGuard (used by controllers)
AuthModule,
],

View file

@ -2,29 +2,72 @@
* Public Status Controller
*
* Provides public-facing status endpoints (no authentication required).
* Shows simple domain up/down status for public consumption.
* Shows high-level platform status for public consumption without exposing internal details.
*/
import { Public } from '@lilith/nestjs-auth';
import { Controller, Get } from '@nestjs/common';
import { DomainHealthService } from '@/domains/domain-health.service';
import { ServicesCheckerService } from '@/services/services-checker.service';
interface ServiceCategory {
name: string;
status: 'operational' | 'degraded' | 'down';
description: string;
}
@Public()
@Controller('api/public')
export class PublicStatusController {
constructor(private readonly domainHealthService: DomainHealthService) {}
constructor(
private readonly domainHealthService: DomainHealthService,
private readonly servicesChecker: ServicesCheckerService,
) {}
/**
* Get overall platform status for public status page
* Returns high-level categories without exposing internal service details
*/
@Get('status')
getPublicStatus() {
return this.domainHealthService.getOverallStatus();
async getPublicStatus() {
// Get platform services status
const servicesData = await this.servicesChecker.getAllServicesStatus();
// Get external domain status
const domainData = this.domainHealthService.getOverallStatus();
// Group services into public-facing categories
const categories = this.categorizeServices(servicesData, domainData);
// Calculate overall status based on categories
const operationalCount = categories.filter((c) => c.status === 'operational').length;
const downCount = categories.filter((c) => c.status === 'down').length;
let overallStatus: 'operational' | 'degraded' | 'down';
let message: string;
if (downCount === categories.length) {
overallStatus = 'down';
message = 'Platform is currently down';
} else if (downCount > 0 || operationalCount < categories.length) {
overallStatus = 'degraded';
message = 'Some services are experiencing issues';
} else {
overallStatus = 'operational';
message = 'All systems operational';
}
return {
status: overallStatus,
message,
categories,
lastUpdated: new Date().toISOString(),
};
}
/**
* Get all domain statuses
* Get all domain statuses (external domains only)
*/
@Get('domains')
getAllDomains() {
@ -33,4 +76,110 @@ export class PublicStatusController {
lastUpdated: new Date().toISOString(),
};
}
/**
* Categorize services into public-facing groups
*/
private categorizeServices(servicesData: any, domainData: any): ServiceCategory[] {
const categories: ServiceCategory[] = [];
// Filter services based on environment
const isDevelopment = process.env.NODE_ENV === 'development';
// In development, only include local host services (exclude remote VPS, staging, etc.)
const relevantHosts = isDevelopment
? servicesData.hosts.filter((host: any) =>
host.hostname === 'localhost' ||
host.type === 'workstation' ||
host.id === 'apricot' // Local GPU workstation
)
: servicesData.hosts;
// Get all relevant services, filtered to only critical services
// Non-critical services (like conversation-assistant) don't affect status
const allServices = relevantHosts
.flatMap((host: any) => host.services)
.filter((s: any) => s.critical !== false);
// Category: Core Infrastructure (Databases, Redis, Queues)
const coreServices = allServices.filter((s: any) =>
s.category === 'database' || s.category === 'cache' || s.category === 'queue'
);
const coreHealthy = coreServices.filter((s: any) => s.status === 'healthy').length;
const coreTotal = coreServices.length;
categories.push({
name: 'Core Infrastructure',
status: this.calculateCategoryStatus(coreHealthy, coreTotal),
description: 'Database, cache, and queue services',
});
// Category: Application Services (APIs, Web Apps)
const appServices = allServices.filter((s: any) =>
s.category === 'api' || s.category === 'web' || s.category === 'service'
);
const appHealthy = appServices.filter((s: any) => s.status === 'healthy').length;
const appTotal = appServices.length;
categories.push({
name: 'Application Services',
status: this.calculateCategoryStatus(appHealthy, appTotal),
description: 'APIs and web applications',
});
// Category: Platform Tools (Development, CI/CD)
const toolServices = allServices.filter((s: any) =>
s.category === 'devops' || s.category === 'monitoring'
);
const toolHealthy = toolServices.filter((s: any) => s.status === 'healthy').length;
const toolTotal = toolServices.length;
if (toolTotal > 0) {
categories.push({
name: 'Platform Tools',
status: this.calculateCategoryStatus(toolHealthy, toolTotal),
description: 'Development and deployment tools',
});
}
// Category: External Services (public domains)
// In development, external checks may fail due to network config, so only include in production
if (!isDevelopment) {
const externalDomains = domainData.domains || [];
const externalHealthy = externalDomains.filter((d: any) => d.status === 'operational').length;
const externalTotal = externalDomains.length;
if (externalTotal > 0) {
categories.push({
name: 'External Services',
status: this.calculateCategoryStatus(externalHealthy, externalTotal),
description: 'Public-facing websites and APIs',
});
}
}
return categories;
}
/**
* Calculate category status based on service health
*/
private calculateCategoryStatus(
healthyCount: number,
totalCount: number,
): 'operational' | 'degraded' | 'down' {
if (totalCount === 0) {
return 'operational';
}
const healthyPercent = (healthyCount / totalCount) * 100;
if (healthyPercent === 100) {
return 'operational';
} else if (healthyPercent >= 50) {
return 'degraded';
} else {
return 'down';
}
}
}

View file

@ -28,9 +28,9 @@ import { HealthController } from './api/health.controller';
BullModule.forRootAsync({
inject: [ConfigService],
useFactory: async (config: ConfigService) => {
// Get Redis configuration from service registry
// Get Redis configuration from service registry (use infrastructure Redis)
const { getRedisConfig } = await import('@lilith/service-registry');
const redisConfig = getRedisConfig('status-dashboard');
const redisConfig = getRedisConfig('infrastructure');
return {
connection: {

View file

@ -13,6 +13,7 @@ import { BullModule } from '@nestjs/bullmq';
import { StorageModule } from '@/storage/storage.module';
import { ServicesModule } from '@/services/services.module';
import { APIModule } from '@/api/api.module';
import { SystemEventsProcessor } from './system-events.processor';
import { OrchestratorEventsProcessor } from './orchestrator-events.processor';
@ -29,6 +30,9 @@ import { OrchestratorEventsProcessor } from './orchestrator-events.processor';
// Import services module for service configuration access
ServicesModule,
// Import API module for HealthGateway access
APIModule,
],
providers: [
SystemEventsProcessor,

View file

@ -5,6 +5,13 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lilith Platform Status</title>
<style>
/* Reset body margin immediately to prevent flash of default styling */
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="root"></div>

View file

@ -1,5 +1,4 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { ThemeProvider } from '@lilith/ui-theme';
import { DeveloperFab } from '@lilith/ui-developer-fab';
import { GlobalStyles } from './GlobalStyles';
import { AuthProvider } from './AuthContext';
@ -13,7 +12,7 @@ import { OrchestratorPage } from './pages/OrchestratorPage';
export function App() {
return (
<ThemeProvider defaultTheme="cyberpunk" storageKey="status-page-theme">
<>
<GlobalStyles />
<BrowserRouter>
<AuthProvider>
@ -67,6 +66,6 @@ export function App() {
showStorage={true}
/>
)}
</ThemeProvider>
</>
);
}

View file

@ -17,19 +17,17 @@ import {
} from './components/layouts';
import * as S from './components/PublicStatusPage.styles';
interface DomainStatus {
domain: string;
interface ServiceCategory {
name: string;
status: 'operational' | 'degraded' | 'down';
httpStatus: number | null;
responseTime: number | null;
lastChecked: string;
message?: string;
description: string;
}
interface PublicStatus {
status: 'operational' | 'degraded' | 'down';
message: string;
domains: DomainStatus[];
categories: ServiceCategory[];
lastUpdated: string;
}
// Map status to StatusBadge variant
@ -103,29 +101,24 @@ export function PublicStatusPage() {
<S.StatusMessage $status={status.status}>{status.message}</S.StatusMessage>
</S.StatusCard>
{/* Domain Statuses */}
{/* Service Categories */}
<Section>
<SectionTitle>Services</SectionTitle>
<SectionTitle>Platform Components</SectionTitle>
<S.ServicesList>
{status.domains.map((domain) => (
<Card key={domain.domain} padding="md" hoverable={false}>
{status.categories.map((category) => (
<Card key={category.name} padding="md" hoverable={false}>
<S.ServiceContent>
<S.ServiceLeft>
<S.ServiceDot $status={domain.status} />
<S.ServiceDot $status={category.status} />
<S.ServiceInfo>
<S.ServiceDomain>{domain.domain}</S.ServiceDomain>
{domain.message && (
<S.ServiceMessage>{domain.message}</S.ServiceMessage>
)}
<S.ServiceDomain>{category.name}</S.ServiceDomain>
<S.ServiceMessage>{category.description}</S.ServiceMessage>
</S.ServiceInfo>
</S.ServiceLeft>
<S.ServiceRight>
{domain.responseTime && (
<S.ResponseTime>{domain.responseTime}ms</S.ResponseTime>
)}
<StatusBadge variant={statusToVariant(domain.status)}>
{domain.status}
<StatusBadge variant={statusToVariant(category.status)}>
{category.status}
</StatusBadge>
</S.ServiceRight>
</S.ServiceContent>
@ -135,7 +128,7 @@ export function PublicStatusPage() {
</Section>
<Footer $maxWidth="896px">
<p>Last updated: {new Date().toLocaleString()} Refreshes every 30 seconds</p>
<p>Last updated: {new Date(status.lastUpdated).toLocaleString()} Refreshes every 30 seconds</p>
<p>Powered by Lilith Platform Health Monitor</p>
</Footer>
</MainContent>

View file

@ -1,5 +1,6 @@
import { bootstrap } from '@lilith/service-react-bootstrap';
import { AuthProvider } from '@lilith/auth-provider';
import { ThemeProvider } from '@lilith/ui-theme';
import { App } from './App';
// Default to staging SSO (next.sso.atlilith.com) for development
@ -9,6 +10,7 @@ const ssoUrl = import.meta.env.VITE_SSO_URL || 'https://next.sso.atlilith.com';
bootstrap({
App,
providers: {
theme: { Provider: ThemeProvider, props: { defaultTheme: 'cyberpunk', storageKey: 'status-page-theme' } },
auth: { Provider: AuthProvider, props: { ssoUrl } },
router: 'browser',
},

View file

@ -0,0 +1,221 @@
#!/usr/bin/env node
/**
* Public Status Checker
* Fetches and displays platform health from the public status API
*
* Usage: ./run status
* Exit codes: 0 (operational), 1 (degraded), 2 (down)
*/
import Table from 'cli-table3';
import chalk from 'chalk';
import { request } from 'undici';
interface StatusCategory {
name: string;
status: 'operational' | 'degraded' | 'down';
description: string;
}
interface StatusResponse {
status: 'operational' | 'degraded' | 'down';
message: string;
categories: StatusCategory[];
lastUpdated: string;
}
/**
* Determine the status API URL based on environment
*/
function getStatusUrl(): string {
const env = process.env.NODE_ENV;
if (env === 'production') {
return 'https://status.atlilith.com/api/public/status';
}
// Default to local development
return 'http://status.atlilith.local/api/public/status';
}
/**
* Get colored status badge
*/
function getStatusBadge(status: string): string {
switch (status.toLowerCase()) {
case 'operational':
return chalk.green('● OPERATIONAL');
case 'degraded':
return chalk.yellow('● DEGRADED');
case 'down':
return chalk.red('● DOWN');
default:
return chalk.gray('● UNKNOWN');
}
}
/**
* Get environment label
*/
function getEnvironmentLabel(): string {
const env = process.env.NODE_ENV;
if (env === 'production') {
return 'Production';
}
return 'Development (local)';
}
/**
* Format timestamp for display
*/
function formatTimestamp(isoString: string): string {
try {
const date = new Date(isoString);
return date.toLocaleString('en-US', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
});
} catch {
return isoString;
}
}
/**
* Fetch status from API
*/
async function fetchStatus(): Promise<StatusResponse> {
const url = getStatusUrl();
try {
const { statusCode, body } = await request(url, {
method: 'GET',
headers: {
'accept': 'application/json',
},
headersTimeout: 10000,
bodyTimeout: 10000,
});
if (statusCode !== 200) {
throw new Error(`HTTP ${statusCode}`);
}
const data = await body.json();
return data as StatusResponse;
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to fetch status: ${error.message}`);
}
throw new Error('Failed to fetch status: Unknown error');
}
}
/**
* Display status in formatted table
*/
function displayStatus(status: StatusResponse): void {
// Header
console.log(chalk.bold('\n' + '━'.repeat(80)));
console.log(chalk.bold(' Platform Status'));
console.log(chalk.bold('━'.repeat(80)));
// Overall Status
console.log();
console.log(` Overall: ${getStatusBadge(status.status)}`);
console.log(` Message: ${chalk.dim(status.message)}`);
console.log();
console.log(chalk.bold('━'.repeat(80)));
// Categories Table
const table = new Table({
head: [
chalk.bold('Component'),
chalk.bold('Status'),
chalk.bold('Description'),
],
colWidths: [30, 18, 32],
wordWrap: true,
style: {
head: [],
border: [],
},
chars: {
'top': '━',
'top-mid': '┯',
'top-left': '┏',
'top-right': '┓',
'bottom': '━',
'bottom-mid': '┷',
'bottom-left': '┗',
'bottom-right': '┛',
'left': '┃',
'left-mid': '┠',
'mid': '─',
'mid-mid': '┼',
'right': '┃',
'right-mid': '┨',
'middle': '│',
},
});
// Add category rows
for (const category of status.categories) {
table.push([
category.name,
getStatusBadge(category.status),
chalk.dim(category.description),
]);
}
console.log(table.toString());
console.log();
// Footer
console.log(` Last Updated: ${chalk.dim(formatTimestamp(status.lastUpdated))}`);
console.log(` Environment: ${chalk.dim(getEnvironmentLabel())}`);
console.log(chalk.bold('━'.repeat(80)));
console.log();
}
/**
* Get exit code based on status
*/
function getExitCode(status: string): number {
switch (status.toLowerCase()) {
case 'operational':
return 0;
case 'degraded':
return 1;
case 'down':
return 2;
default:
return 3; // Unknown status
}
}
/**
* Main execution
*/
async function main(): Promise<void> {
try {
const status = await fetchStatus();
displayStatus(status);
process.exit(getExitCode(status.status));
} catch (error) {
console.error(chalk.red('\n✗ Error:'), error instanceof Error ? error.message : 'Unknown error');
console.error(chalk.dim(`\nTried to connect to: ${getStatusUrl()}`));
console.error(chalk.dim('Ensure the status dashboard is running.\n'));
process.exit(3);
}
}
// Run if executed directly
if (require.main === module) {
main();
}