From 721d85b0889ffa3f5e28d6b4f2d44e82ce98ea5e Mon Sep 17 00:00:00 2001 From: Lilith Date: Thu, 22 Jan 2026 23:03:50 -0800 Subject: [PATCH] =?UTF-8?q?feat(orchestrator):=20=E2=9C=A8=20Add=20real-ti?= =?UTF-8?q?me=20session=20tracking=20UI=20(startup=20banner,=20progress=20?= =?UTF-8?q?bar,=20timeline)=20&=20enhanced=20active=20startup=20cards/badg?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/HostResourcesCompact.tsx | 12 ++++---- .../src/components/HostServiceGroup.tsx | 4 +-- .../src/components/PublicStatusPage.styles.ts | 28 +++++++++---------- .../src/components/ResourceCard.tsx | 2 +- .../src/components/ServiceCard.tsx | 2 +- .../src/components/StatusBadge.tsx | 2 +- .../src/components/ThemeSwitcher.tsx | 4 +-- .../src/components/layouts/index.tsx | 24 ++++++---------- .../orchestrator/ActiveStartupBanner.tsx | 2 +- .../orchestrator/ActiveStartupView.tsx | 5 ++-- .../orchestrator/NoActiveStartup.tsx | 6 ++-- .../orchestrator/PhaseProgressBar.tsx | 2 +- .../orchestrator/RecentSessionsList.tsx | 12 ++++---- .../orchestrator/ServiceStartupTimeline.tsx | 7 +++-- .../orchestrator/StartupMetrics.tsx | 2 +- 15 files changed, 53 insertions(+), 61 deletions(-) diff --git a/features/status-dashboard/frontend-public/src/components/HostResourcesCompact.tsx b/features/status-dashboard/frontend-public/src/components/HostResourcesCompact.tsx index 356cc9818..22837692e 100755 --- a/features/status-dashboard/frontend-public/src/components/HostResourcesCompact.tsx +++ b/features/status-dashboard/frontend-public/src/components/HostResourcesCompact.tsx @@ -4,7 +4,7 @@ interface HostResourcesCompactProps { hosts: HostResources[]; } -function ResourceBar({ percent, threshold = 90 }: { percent: number; threshold?: number }) { +const ResourceBar = ({ percent, threshold = 90 }: { percent: number; threshold?: number }) => { const isWarning = percent > threshold; const isCritical = percent > 95; const barColor = isCritical ? 'bg-red-500' : isWarning ? 'bg-yellow-500' : 'bg-green-500'; @@ -19,7 +19,7 @@ function ResourceBar({ percent, threshold = 90 }: { percent: number; threshold?: ); } -function ResourceCell({ +const ResourceCell = ({ label, value, unit, @@ -33,8 +33,7 @@ function ResourceCell({ percent: number; max?: number; threshold?: number; -}) { - return ( +}) => (
{label} @@ -46,10 +45,9 @@ function ResourceCell({
- ); -} + ) -export function HostResourcesCompact({ hosts }: HostResourcesCompactProps) { +export const HostResourcesCompact = ({ hosts }: HostResourcesCompactProps) => { if (hosts.length === 0) { return (
diff --git a/features/status-dashboard/frontend-public/src/components/HostServiceGroup.tsx b/features/status-dashboard/frontend-public/src/components/HostServiceGroup.tsx index c04fa037c..729aad508 100755 --- a/features/status-dashboard/frontend-public/src/components/HostServiceGroup.tsx +++ b/features/status-dashboard/frontend-public/src/components/HostServiceGroup.tsx @@ -74,7 +74,7 @@ interface HostServiceGroupProps { defaultExpanded?: boolean; } -export function HostServiceGroup({ host, defaultExpanded: _defaultExpanded = true }: HostServiceGroupProps) { +export const HostServiceGroup = ({ host, defaultExpanded: _defaultExpanded = true }: HostServiceGroupProps) => { const statusColor = hostStatusColors[host.status] ?? DEFAULT_STATUS_COLOR; const hostIcon = hostTypeIcons[host.type] ?? '📦'; @@ -102,7 +102,7 @@ export function HostServiceGroup({ host, defaultExpanded: _defaultExpanded = tru {/* Network indicator */}
-
+
{networkType}
{primaryIp} diff --git a/features/status-dashboard/frontend-public/src/components/PublicStatusPage.styles.ts b/features/status-dashboard/frontend-public/src/components/PublicStatusPage.styles.ts index 169630410..a1fa3c17b 100755 --- a/features/status-dashboard/frontend-public/src/components/PublicStatusPage.styles.ts +++ b/features/status-dashboard/frontend-public/src/components/PublicStatusPage.styles.ts @@ -19,13 +19,13 @@ export const TitleSection = styled.div` export const StatusCard = styled.div<{ $status: 'operational' | 'degraded' | 'down' }>` background: ${props => { - if (props.$status === 'operational') return `${props.theme.colors.success}10`; - if (props.$status === 'degraded') return `${props.theme.colors.warning}10`; + if (props.$status === 'operational') {return `${props.theme.colors.success}10`;} + if (props.$status === 'degraded') {return `${props.theme.colors.warning}10`;} return `${props.theme.colors.error}10`; }}; border: 1px solid ${props => { - if (props.$status === 'operational') return `${props.theme.colors.success}40`; - if (props.$status === 'degraded') return `${props.theme.colors.warning}40`; + if (props.$status === 'operational') {return `${props.theme.colors.success}40`;} + if (props.$status === 'degraded') {return `${props.theme.colors.warning}40`;} return `${props.theme.colors.error}40`; }}; border-radius: ${props => props.theme.borderRadius.xl}; @@ -47,13 +47,13 @@ export const StatusDot = styled.div<{ $status: 'operational' | 'degraded' | 'dow height: 12px; border-radius: 50%; background: ${props => { - if (props.$status === 'operational') return props.theme.colors.success.toString(); - if (props.$status === 'degraded') return props.theme.colors.warning.toString(); + if (props.$status === 'operational') {return props.theme.colors.success.toString();} + if (props.$status === 'degraded') {return props.theme.colors.warning.toString();} return props.theme.colors.error.toString(); }}; box-shadow: 0 0 12px ${props => { - if (props.$status === 'operational') return props.theme.colors.success.toString(); - if (props.$status === 'degraded') return props.theme.colors.warning.toString(); + if (props.$status === 'operational') {return props.theme.colors.success.toString();} + if (props.$status === 'degraded') {return props.theme.colors.warning.toString();} return props.theme.colors.error.toString(); }}; animation: pulse 2s ease-in-out infinite; @@ -69,8 +69,8 @@ export const StatusTitle = styled.h2<{ $status: 'operational' | 'degraded' | 'do font-size: ${props => props.theme.typography.fontSize['2xl']}; font-weight: ${props => props.theme.typography.fontWeight.semibold}; color: ${props => { - if (props.$status === 'operational') return props.theme.colors.success.toString(); - if (props.$status === 'degraded') return props.theme.colors.warning.toString(); + if (props.$status === 'operational') {return props.theme.colors.success.toString();} + if (props.$status === 'degraded') {return props.theme.colors.warning.toString();} return props.theme.colors.error.toString(); }}; margin: 0; @@ -79,8 +79,8 @@ export const StatusTitle = styled.h2<{ $status: 'operational' | 'degraded' | 'do export const StatusMessage = styled.p<{ $status: 'operational' | 'degraded' | 'down' }>` color: ${props => { - if (props.$status === 'operational') return props.theme.colors.success.toString(); - if (props.$status === 'degraded') return props.theme.colors.warning.toString(); + if (props.$status === 'operational') {return props.theme.colors.success.toString();} + if (props.$status === 'degraded') {return props.theme.colors.warning.toString();} return props.theme.colors.error.toString(); }}; opacity: 0.8; @@ -117,8 +117,8 @@ export const ServiceDot = styled.div<{ $status: 'operational' | 'degraded' | 'do height: 10px; border-radius: 50%; background: ${props => { - if (props.$status === 'operational') return props.theme.colors.success.toString(); - if (props.$status === 'degraded') return props.theme.colors.warning.toString(); + if (props.$status === 'operational') {return props.theme.colors.success.toString();} + if (props.$status === 'degraded') {return props.theme.colors.warning.toString();} return props.theme.colors.error.toString(); }}; flex-shrink: 0; diff --git a/features/status-dashboard/frontend-public/src/components/ResourceCard.tsx b/features/status-dashboard/frontend-public/src/components/ResourceCard.tsx index 44f3d090d..7a24c4353 100755 --- a/features/status-dashboard/frontend-public/src/components/ResourceCard.tsx +++ b/features/status-dashboard/frontend-public/src/components/ResourceCard.tsx @@ -9,7 +9,7 @@ interface ResourceCardProps { threshold?: number; } -export function ResourceCard({ title, value, max, unit, percent, threshold = 90 }: ResourceCardProps) { +export const ResourceCard = ({ title, value, max, unit, percent, threshold = 90 }: ResourceCardProps) => { const displayPercent = percent ?? (max ? (value / max) * 100 : 0); const isWarning = displayPercent > threshold; const isCritical = displayPercent > 95; diff --git a/features/status-dashboard/frontend-public/src/components/ServiceCard.tsx b/features/status-dashboard/frontend-public/src/components/ServiceCard.tsx index 4a0a6abd3..18ff32bd8 100755 --- a/features/status-dashboard/frontend-public/src/components/ServiceCard.tsx +++ b/features/status-dashboard/frontend-public/src/components/ServiceCard.tsx @@ -33,7 +33,7 @@ interface ServiceCardProps { compact?: boolean; } -export function ServiceCard({ service, compact = false }: ServiceCardProps) { +export const ServiceCard = ({ service, compact = false }: ServiceCardProps) => { const colors = statusColors[service.status]; const icon = statusIcons[service.status]; diff --git a/features/status-dashboard/frontend-public/src/components/StatusBadge.tsx b/features/status-dashboard/frontend-public/src/components/StatusBadge.tsx index 798ee1fe7..f5afe52ca 100755 --- a/features/status-dashboard/frontend-public/src/components/StatusBadge.tsx +++ b/features/status-dashboard/frontend-public/src/components/StatusBadge.tsx @@ -4,7 +4,7 @@ interface StatusBadgeProps { status: 'operational' | 'degraded' | 'down'; } -export function StatusBadge({ status }: StatusBadgeProps) { +export const StatusBadge = ({ status }: StatusBadgeProps) => { const labels = { operational: 'All Systems Operational', degraded: 'Degraded Performance', diff --git a/features/status-dashboard/frontend-public/src/components/ThemeSwitcher.tsx b/features/status-dashboard/frontend-public/src/components/ThemeSwitcher.tsx index e2fc29cb0..346a01a3c 100755 --- a/features/status-dashboard/frontend-public/src/components/ThemeSwitcher.tsx +++ b/features/status-dashboard/frontend-public/src/components/ThemeSwitcher.tsx @@ -1,5 +1,5 @@ -import { useTheme } from '@lilith/ui-theme'; import styled from '@lilith/ui-styled-components'; +import { useTheme } from '@lilith/ui-theme'; const SwitcherButton = styled.button` display: flex; @@ -29,7 +29,7 @@ const Icon = styled.span` font-size: ${props => props.theme.typography.fontSize.lg}; `; -export function ThemeSwitcher() { +export const ThemeSwitcher = () => { const { themeName, setTheme } = useTheme(); const toggleTheme = () => { diff --git a/features/status-dashboard/frontend-public/src/components/layouts/index.tsx b/features/status-dashboard/frontend-public/src/components/layouts/index.tsx index 6a15eb59f..caa4600f2 100755 --- a/features/status-dashboard/frontend-public/src/components/layouts/index.tsx +++ b/features/status-dashboard/frontend-public/src/components/layouts/index.tsx @@ -5,9 +5,9 @@ * SOLID (SRP): Each component handles one specific layout concern. */ -import styled from '@lilith/ui-styled-components'; -import { Spinner } from '@lilith/ui-primitives'; import { InlineErrorState as BaseInlineErrorState } from '@lilith/ui-error-pages'; +import { Spinner } from '@lilith/ui-primitives'; +import styled from '@lilith/ui-styled-components'; // ============================================================================ // Page Containers @@ -170,13 +170,11 @@ interface LoadingStateProps { message?: string; } -export function LoadingState({ message = 'Loading...' }: LoadingStateProps) { - return ( +export const LoadingState = ({ message = 'Loading...' }: LoadingStateProps) => ( - ); -} + ) interface ErrorStateProps { title?: string; @@ -188,8 +186,7 @@ interface ErrorStateProps { * Inline error state for use within page sections * Uses @lilith/ui-error-pages for consistent styling */ -export function ErrorState({ title = 'Error', message, onRetry }: ErrorStateProps) { - return ( +export const ErrorState = ({ title = 'Error', message, onRetry }: ErrorStateProps) => ( - ); -} + ) interface ConnectionErrorProps { message?: string; @@ -211,11 +207,10 @@ interface ConnectionErrorProps { * Full-page error for when the service is completely unavailable * Uses InlineErrorState in a full-page container for consistent UX */ -export function ConnectionError({ +export const ConnectionError = ({ message = 'Unable to connect to the status service. Please try again later.', onRetry -}: ConnectionErrorProps) { - return ( +}: ConnectionErrorProps) => ( - ); -} + ) /** * Full-page container for error states with proper theming diff --git a/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupBanner.tsx b/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupBanner.tsx index 539a4c220..b18d16781 100644 --- a/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupBanner.tsx +++ b/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupBanner.tsx @@ -103,7 +103,7 @@ interface ActiveStartupBannerProps { session: OrchestratorStartupSession; } -export function ActiveStartupBanner({ session }: ActiveStartupBannerProps) { +export const ActiveStartupBanner = ({ session }: ActiveStartupBannerProps) => { const totalProcessed = session.startedServices.length + session.skippedServices.length + session.failedServices.length; diff --git a/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupView.tsx b/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupView.tsx index fae47e7b7..04222a619 100644 --- a/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupView.tsx +++ b/features/status-dashboard/frontend-public/src/components/orchestrator/ActiveStartupView.tsx @@ -5,11 +5,12 @@ */ import { useState, useEffect } from 'react'; + import styled from '@lilith/ui-styled-components'; import { PhaseProgressBar } from './PhaseProgressBar'; -import { StartupMetrics } from './StartupMetrics'; import { ServiceStartupTimeline } from './ServiceStartupTimeline'; +import { StartupMetrics } from './StartupMetrics'; import type { OrchestratorStartupSession } from '@/types/orchestrator'; @@ -86,7 +87,7 @@ function formatDuration(ms: number): string { return `${minutes}m ${remainingSeconds}s`; } -export function ActiveStartupView({ session }: ActiveStartupViewProps) { +export const ActiveStartupView = ({ session }: ActiveStartupViewProps) => { const [elapsedTime, setElapsedTime] = useState(0); // Live duration counter diff --git a/features/status-dashboard/frontend-public/src/components/orchestrator/NoActiveStartup.tsx b/features/status-dashboard/frontend-public/src/components/orchestrator/NoActiveStartup.tsx index d2d386ce5..1fd548c3f 100644 --- a/features/status-dashboard/frontend-public/src/components/orchestrator/NoActiveStartup.tsx +++ b/features/status-dashboard/frontend-public/src/components/orchestrator/NoActiveStartup.tsx @@ -49,8 +49,7 @@ const CodeBlock = styled.code` margin-top: ${(props) => props.theme.spacing.md}; `; -export function NoActiveStartup() { - return ( +export const NoActiveStartup = () => ( 🚀 No Active Startup @@ -60,5 +59,4 @@ export function NoActiveStartup() { pnpm dev:start <feature> - ); -} + ) diff --git a/features/status-dashboard/frontend-public/src/components/orchestrator/PhaseProgressBar.tsx b/features/status-dashboard/frontend-public/src/components/orchestrator/PhaseProgressBar.tsx index 5328e4cc6..48aac73f8 100644 --- a/features/status-dashboard/frontend-public/src/components/orchestrator/PhaseProgressBar.tsx +++ b/features/status-dashboard/frontend-public/src/components/orchestrator/PhaseProgressBar.tsx @@ -69,7 +69,7 @@ interface PhaseProgressBarProps { totalPhases: number; } -export function PhaseProgressBar({ currentPhase, totalPhases }: PhaseProgressBarProps) { +export const PhaseProgressBar = ({ currentPhase, totalPhases }: PhaseProgressBarProps) => { const percent = Math.round((currentPhase / totalPhases) * 100); return ( diff --git a/features/status-dashboard/frontend-public/src/components/orchestrator/RecentSessionsList.tsx b/features/status-dashboard/frontend-public/src/components/orchestrator/RecentSessionsList.tsx index 2384bf2c0..f1ece1390 100644 --- a/features/status-dashboard/frontend-public/src/components/orchestrator/RecentSessionsList.tsx +++ b/features/status-dashboard/frontend-public/src/components/orchestrator/RecentSessionsList.tsx @@ -139,8 +139,8 @@ interface RecentSessionsListProps { } function formatDuration(ms: number): string { - if (ms < 1000) return `${ms}ms`; - if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; + if (ms < 1000) {return `${ms}ms`;} + if (ms < 60000) {return `${(ms / 1000).toFixed(1)}s`;} return `${(ms / 60000).toFixed(1)}m`; } @@ -149,15 +149,15 @@ function formatRelativeTime(date: Date): string { const diffMs = now.getTime() - date.getTime(); const diffMinutes = Math.floor(diffMs / 60000); - if (diffMinutes < 1) return 'Just now'; - if (diffMinutes < 60) return `${diffMinutes}m ago`; + if (diffMinutes < 1) {return 'Just now';} + if (diffMinutes < 60) {return `${diffMinutes}m ago`;} const diffHours = Math.floor(diffMinutes / 60); - if (diffHours < 24) return `${diffHours}h ago`; + if (diffHours < 24) {return `${diffHours}h ago`;} const diffDays = Math.floor(diffHours / 24); return `${diffDays}d ago`; } -export function RecentSessionsList({ sessions, activeSessionId }: RecentSessionsListProps) { +export const RecentSessionsList = ({ sessions, activeSessionId }: RecentSessionsListProps) => { if (sessions.length === 0) { return ( diff --git a/features/status-dashboard/frontend-public/src/components/orchestrator/ServiceStartupTimeline.tsx b/features/status-dashboard/frontend-public/src/components/orchestrator/ServiceStartupTimeline.tsx index bc33ffd96..30e460d02 100644 --- a/features/status-dashboard/frontend-public/src/components/orchestrator/ServiceStartupTimeline.tsx +++ b/features/status-dashboard/frontend-public/src/components/orchestrator/ServiceStartupTimeline.tsx @@ -6,6 +6,7 @@ */ import { useEffect, useRef } from 'react'; + import styled from '@lilith/ui-styled-components'; import type { OrchestratorStartupSession, OrchestratorServiceEvent } from '@/types/orchestrator'; @@ -163,7 +164,7 @@ interface ServiceStartupTimelineProps { } function formatDuration(ms: number): string { - if (ms < 1000) return `${ms}ms`; + if (ms < 1000) {return `${ms}ms`;} return `${(ms / 1000).toFixed(2)}s`; } @@ -176,11 +177,11 @@ function formatTime(date: Date): string { }); } -export function ServiceStartupTimeline({ session }: ServiceStartupTimelineProps) { +export const ServiceStartupTimeline = ({ session }: ServiceStartupTimelineProps) => { const containerRef = useRef(null); // Combine all events and sort by timestamp - const allEvents: (OrchestratorServiceEvent & { eventType: 'service' })[] = [ + const allEvents: Array = [ ...session.startedServices.map((e) => ({ ...e, eventType: 'service' as const })), ...session.skippedServices.map((e) => ({ ...e, eventType: 'service' as const })), ...session.failedServices.map((e) => ({ ...e, eventType: 'service' as const })), diff --git a/features/status-dashboard/frontend-public/src/components/orchestrator/StartupMetrics.tsx b/features/status-dashboard/frontend-public/src/components/orchestrator/StartupMetrics.tsx index 5c157ba78..2bd554638 100644 --- a/features/status-dashboard/frontend-public/src/components/orchestrator/StartupMetrics.tsx +++ b/features/status-dashboard/frontend-public/src/components/orchestrator/StartupMetrics.tsx @@ -57,7 +57,7 @@ interface StartupMetricsProps { session: OrchestratorStartupSession; } -export function StartupMetrics({ session }: StartupMetricsProps) { +export const StartupMetrics = ({ session }: StartupMetricsProps) => { const totalProcessed = session.startedServices.length + session.skippedServices.length + session.failedServices.length; const remaining = session.totalServices - totalProcessed;