From 96d9fc68e9cf2af8b0b0d237d1ef094269cebb38 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 9 Dec 2025 13:00:04 -0500 Subject: [PATCH] feat(dashboard): add Environment and OAuth Readiness groups to health page Bring OAuth diagnostics from ccs doctor to web dashboard: - Add Environment group showing platform, SSH, TTY, browser capability - Add OAuth Readiness group showing port availability (8085, 1455, 51121) - Reuse existing environment-diagnostics and oauth-port-diagnostics modules The dashboard health page now has 7 groups matching ccs doctor output. --- src/web-server/health-service.ts | 79 ++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/src/web-server/health-service.ts b/src/web-server/health-service.ts index 469d0539..5219f904 100644 --- a/src/web-server/health-service.ts +++ b/src/web-server/health-service.ts @@ -2,7 +2,7 @@ * Health Check Service (Phase 06) * * Runs comprehensive health checks for CCS dashboard matching `ccs doctor` output. - * Groups: System, Configuration, Profiles & Delegation, System Health, CLIProxy + * Groups: System, Environment, Configuration, Profiles & Delegation, System Health, CLIProxy, OAuth Readiness */ import * as fs from 'fs'; @@ -21,6 +21,8 @@ import { import { getClaudeCliInfo } from '../utils/claude-detector'; import { getPortProcess, isCLIProxyProcess } from '../utils/port-utils'; import packageJson from '../../package.json'; +import { getEnvironmentDiagnostics } from '../management/environment-diagnostics'; +import { checkAuthCodePorts } from '../management/oauth-port-diagnostics'; export interface HealthCheck { id: string; @@ -70,7 +72,12 @@ export async function runHealthChecks(): Promise { systemChecks.push(checkCcsDirectory(ccsDir)); groups.push({ id: 'system', name: 'System', icon: 'Monitor', checks: systemChecks }); - // Group 2: Configuration + // Group 2: Environment (OAuth readiness diagnostics) + const envChecks: HealthCheck[] = []; + envChecks.push(checkEnvironment()); + groups.push({ id: 'environment', name: 'Environment', icon: 'Laptop', checks: envChecks }); + + // Group 3: Configuration const configChecks: HealthCheck[] = []; configChecks.push(checkConfigFile()); configChecks.push(...checkSettingsFiles(ccsDir)); @@ -82,7 +89,7 @@ export async function runHealthChecks(): Promise { checks: configChecks, }); - // Group 3: Profiles & Delegation + // Group 4: Profiles & Delegation const profileChecks: HealthCheck[] = []; profileChecks.push(checkProfiles(ccsDir)); profileChecks.push(checkInstances(ccsDir)); @@ -94,7 +101,7 @@ export async function runHealthChecks(): Promise { checks: profileChecks, }); - // Group 4: System Health + // Group 5: System Health const healthChecks: HealthCheck[] = []; healthChecks.push(checkPermissions(ccsDir)); healthChecks.push(checkCcsSymlinks()); @@ -106,7 +113,7 @@ export async function runHealthChecks(): Promise { checks: healthChecks, }); - // Group 5: CLIProxy + // Group 6: CLIProxy const cliproxyChecks: HealthCheck[] = []; cliproxyChecks.push(checkCliproxyBinary()); cliproxyChecks.push(checkCliproxyConfig()); @@ -119,6 +126,16 @@ export async function runHealthChecks(): Promise { checks: cliproxyChecks, }); + // Group 7: OAuth Readiness (port availability) + const oauthReadinessChecks: HealthCheck[] = []; + oauthReadinessChecks.push(...(await checkOAuthPortsForDashboard())); + groups.push({ + id: 'oauth-readiness', + name: 'OAuth Readiness', + icon: 'Key', + checks: oauthReadinessChecks, + }); + // Flatten all checks for backward compatibility const allChecks = groups.flatMap((g) => g.checks); @@ -747,6 +764,58 @@ async function checkCliproxyPort(): Promise { }; } +// Check 16: Environment (platform, SSH, TTY, browser capability) +function checkEnvironment(): HealthCheck { + const diag = getEnvironmentDiagnostics(); + + let status: 'ok' | 'warning' | 'info' = 'ok'; + let message = 'Browser available'; + + if (diag.detectedHeadless) { + if (diag.platform === 'win32' && diag.ttyStatus === 'undefined') { + status = 'warning'; + message = 'Possible headless false positive (Windows)'; + } else if (diag.sshSession) { + status = 'info'; + message = 'SSH session (headless mode)'; + } else { + status = 'info'; + message = 'Headless environment'; + } + } + + return { + id: 'environment', + name: 'Environment', + status, + message, + details: `${diag.platformName} | SSH: ${diag.sshSession ? 'Yes' : 'No'} | Browser: ${diag.browserReason}`, + }; +} + +// Check 17: OAuth Ports (port availability for Gemini, Codex, Agy) +async function checkOAuthPortsForDashboard(): Promise { + const portDiagnostics = await checkAuthCodePorts(); + + return portDiagnostics.map((diag) => { + const providerName = diag.provider.charAt(0).toUpperCase() + diag.provider.slice(1); + const portStr = diag.port ? ` (${diag.port})` : ''; + + let status: 'ok' | 'warning' | 'info' = 'ok'; + if (diag.status === 'occupied') status = 'warning'; + if (diag.status === 'not_applicable') status = 'info'; + + return { + id: `oauth-port-${diag.provider}`, + name: `${providerName}${portStr}`, + status, + message: diag.message, + details: diag.process ? `PID ${diag.process.pid}` : undefined, + fix: diag.recommendation || undefined, + }; + }); +} + /** * Fix a health issue by its check ID */