diff --git a/src/web-server/overview-routes.ts b/src/web-server/overview-routes.ts index 9b9cdaef..208b4175 100644 --- a/src/web-server/overview-routes.ts +++ b/src/web-server/overview-routes.ts @@ -9,6 +9,8 @@ import * as fs from 'fs'; import * as path from 'path'; import { getCcsDir, loadConfig } from '../utils/config-manager'; import { runHealthChecks } from './health-service'; +import { getAllAuthStatus, initializeAccounts } from '../cliproxy/auth-handler'; +import { getVersion } from '../utils/version'; export const overviewRoutes = Router(); @@ -20,14 +22,25 @@ overviewRoutes.get('/', (_req: Request, res: Response) => { const config = loadConfig(); const profileCount = Object.keys(config.profiles).length; - const cliproxyCount = Object.keys(config.cliproxy || {}).length; + const cliproxyVariantCount = Object.keys(config.cliproxy || {}).length; + + // Count authenticated built-in providers (gemini, codex, agy, qwen, iflow) + initializeAccounts(); + const authStatuses = getAllAuthStatus(); + const authenticatedProviderCount = authStatuses.filter((s) => s.authenticated).length; + + // Total CLIProxy = custom variants + authenticated providers + const totalCliproxyCount = cliproxyVariantCount + authenticatedProviderCount; // Get quick health summary const health = runHealthChecks(); res.json({ + version: getVersion(), profiles: profileCount, - cliproxy: cliproxyCount, + cliproxy: totalCliproxyCount, + cliproxyVariants: cliproxyVariantCount, + cliproxyProviders: authenticatedProviderCount, accounts: getAccountCount(), health: { status: @@ -38,8 +51,11 @@ overviewRoutes.get('/', (_req: Request, res: Response) => { }); } catch { res.json({ + version: getVersion(), profiles: 0, cliproxy: 0, + cliproxyVariants: 0, + cliproxyProviders: 0, accounts: 0, health: { status: 'error', passed: 0, total: 0 }, }); diff --git a/ui/src/components/connection-indicator.tsx b/ui/src/components/connection-indicator.tsx index f19ecbe6..eb00487d 100644 --- a/ui/src/components/connection-indicator.tsx +++ b/ui/src/components/connection-indicator.tsx @@ -11,7 +11,7 @@ export function ConnectionIndicator() { const { status } = useWebSocket(); const statusConfig = { - connected: { icon: Wifi, color: 'text-green-500', label: 'Connected' }, + connected: { icon: Wifi, color: 'text-green-600', label: 'Connected' }, connecting: { icon: Wifi, color: 'text-yellow-500', label: 'Connecting...' }, disconnected: { icon: WifiOff, color: 'text-red-500', label: 'Disconnected' }, }; diff --git a/ui/src/components/health-card.tsx b/ui/src/components/health-card.tsx index 520e2083..c3097596 100644 --- a/ui/src/components/health-card.tsx +++ b/ui/src/components/health-card.tsx @@ -15,7 +15,7 @@ interface HealthCheck { const statusConfig = { ok: { icon: CheckCircle, - color: 'text-green-500', + color: 'text-green-600', bg: 'bg-green-50 dark:bg-green-900/20', border: 'border-green-200 dark:border-green-800', }, diff --git a/ui/src/components/hero-section.tsx b/ui/src/components/hero-section.tsx new file mode 100644 index 00000000..17418826 --- /dev/null +++ b/ui/src/components/hero-section.tsx @@ -0,0 +1,86 @@ +import { Badge } from '@/components/ui/badge'; +import { CcsLogo } from '@/components/ccs-logo'; +import { CheckCircle2, AlertCircle, XCircle } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface HeroSectionProps { + version?: string; + healthStatus?: 'ok' | 'warning' | 'error'; + healthPassed?: number; + healthTotal?: number; +} + +const statusConfig = { + ok: { + icon: CheckCircle2, + label: 'All Systems Operational', + color: 'text-green-600', + badgeBg: 'bg-green-500/10 text-green-600 border-green-500/20', + }, + warning: { + icon: AlertCircle, + label: 'Some Issues Detected', + color: 'text-yellow-500', + badgeBg: 'bg-yellow-500/10 text-yellow-500 border-yellow-500/20', + }, + error: { + icon: XCircle, + label: 'Action Required', + color: 'text-red-500', + badgeBg: 'bg-red-500/10 text-red-500 border-red-500/20', + }, +}; + +export function HeroSection({ + version = '5.0.0', + healthStatus = 'ok', + healthPassed = 0, + healthTotal = 0, +}: HeroSectionProps) { + const status = statusConfig[healthStatus]; + const StatusIcon = status.icon; + + return ( +
+ {/* Subtle background pattern */} +
+
+
+ +
+ {/* Left: Logo and Welcome */} +
+ +
+
+

CCS Config

+ + v{version} + +
+

Claude Code Switch Dashboard

+
+
+ + {/* Right: Health Status */} +
+ +
+

{status.label}

+ {healthTotal > 0 && ( +

+ {healthPassed}/{healthTotal} checks passed +

+ )} +
+
+
+
+ ); +} diff --git a/ui/src/components/quick-commands.tsx b/ui/src/components/quick-commands.tsx new file mode 100644 index 00000000..6ab4d6f8 --- /dev/null +++ b/ui/src/components/quick-commands.tsx @@ -0,0 +1,92 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Copy, Check, Terminal } from 'lucide-react'; +import { useState } from 'react'; +import { cn } from '@/lib/utils'; + +interface CommandSnippet { + label: string; + command: string; + description: string; +} + +const defaultSnippets: CommandSnippet[] = [ + { + label: 'Start Default', + command: 'ccs', + description: 'Launch Claude with default profile', + }, + { + label: 'GLM Profile', + command: 'ccs glm', + description: 'Switch to GLM model', + }, + { + label: 'Health Check', + command: 'ccs doctor', + description: 'Run system diagnostics', + }, + { + label: 'Delegate Task', + command: 'ccs glm -p "your task"', + description: 'Delegate to GLM profile', + }, +]; + +interface QuickCommandsProps { + snippets?: CommandSnippet[]; +} + +export function QuickCommands({ snippets = defaultSnippets }: QuickCommandsProps) { + const [copiedIndex, setCopiedIndex] = useState(null); + + const copyToClipboard = async (text: string, index: number) => { + await navigator.clipboard.writeText(text); + setCopiedIndex(index); + setTimeout(() => setCopiedIndex(null), 2000); + }; + + return ( + + + + + Quick Commands + + + +
+ {snippets.map((snippet, index) => ( +
+
+

{snippet.label}

+ + {snippet.command} + +
+ +
+ ))} +
+
+
+ ); +} diff --git a/ui/src/components/stat-card.tsx b/ui/src/components/stat-card.tsx index b2f6f991..8ef7f37b 100644 --- a/ui/src/components/stat-card.tsx +++ b/ui/src/components/stat-card.tsx @@ -1,33 +1,86 @@ import { Card, CardContent } from '@/components/ui/card'; import type { LucideIcon } from 'lucide-react'; +import { cn } from '@/lib/utils'; interface StatCardProps { title: string; value: number | string; icon: LucideIcon; color?: string; + variant?: 'default' | 'success' | 'warning' | 'error' | 'accent'; + subtitle?: string; onClick?: () => void; } +const variantStyles = { + default: { + iconBg: 'bg-muted', + iconColor: 'text-muted-foreground', + borderHover: 'hover:border-primary', + }, + success: { + iconBg: 'bg-green-500/10', + iconColor: 'text-green-600', + borderHover: 'hover:border-green-500/50', + }, + warning: { + iconBg: 'bg-yellow-500/10', + iconColor: 'text-yellow-500', + borderHover: 'hover:border-yellow-500/50', + }, + error: { + iconBg: 'bg-red-500/10', + iconColor: 'text-red-500', + borderHover: 'hover:border-red-500/50', + }, + accent: { + iconBg: 'bg-accent/10', + iconColor: 'text-accent', + borderHover: 'hover:border-accent/50', + }, +}; + export function StatCard({ title, value, icon: Icon, - color = 'text-primary', + color, + variant = 'default', + subtitle, onClick, }: StatCardProps) { + const styles = variantStyles[variant]; + const iconColorClass = color || styles.iconColor; + return ( - -
-
-

{title}

-

{value}

+ +
+ {/* Icon Container with background */} +
+ +
+ + {/* Content */} +
+

{title}

+

{value}

+ {subtitle &&

{subtitle}

}
-
diff --git a/ui/src/hooks/use-overview.ts b/ui/src/hooks/use-overview.ts index e395d006..09bab967 100644 --- a/ui/src/hooks/use-overview.ts +++ b/ui/src/hooks/use-overview.ts @@ -1,8 +1,11 @@ import { useQuery } from '@tanstack/react-query'; interface Overview { + version: string; profiles: number; cliproxy: number; + cliproxyVariants: number; + cliproxyProviders: number; accounts: number; health: { status: 'ok' | 'warning' | 'error'; diff --git a/ui/src/pages/home.tsx b/ui/src/pages/home.tsx index 2c28ca53..0e2077dc 100644 --- a/ui/src/pages/home.tsx +++ b/ui/src/pages/home.tsx @@ -2,6 +2,8 @@ import { useNavigate } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { StatCard } from '@/components/stat-card'; +import { HeroSection } from '@/components/hero-section'; +import { QuickCommands } from '@/components/quick-commands'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Skeleton } from '@/components/ui/skeleton'; import { @@ -14,14 +16,15 @@ import { BookOpen, FolderOpen, AlertTriangle, + ArrowRight, } from 'lucide-react'; import { useOverview } from '@/hooks/use-overview'; import { useSharedSummary } from '@/hooks/use-shared'; -const HEALTH_COLORS = { - ok: 'text-green-500', - warning: 'text-yellow-500', - error: 'text-red-500', +const HEALTH_VARIANTS = { + ok: 'success', + warning: 'warning', + error: 'error', } as const; export function HomePage() { @@ -32,44 +35,48 @@ export function HomePage() { if (isOverviewLoading || isSharedLoading) { return (
-
- - + {/* Hero Skeleton */} +
+
+ +
+ + +
+
+ + {/* Stats Skeleton */}
{[1, 2, 3, 4].map((i) => ( -
-
- - -
-
- - +
+
+ +
+ + +
))}
-
- + + {/* Quick Actions Skeleton */} +
+
-
-
- - -
-
- {[1, 2, 3].map((i) => ( -
- - - -
+ + {/* Quick Commands Skeleton */} +
+ +
+ {[1, 2, 3, 4].map((i) => ( + ))}
@@ -77,13 +84,21 @@ export function HomePage() { ); } + const healthVariant = overview?.health + ? HEALTH_VARIANTS[overview.health.status as keyof typeof HEALTH_VARIANTS] + : undefined; + return (
-
-

Welcome to CCS Config

-

Manage your Claude Code Switch configuration

-
+ {/* Hero Section */} + + {/* Configuration Warning */} {shared?.symlinkStatus && !shared.symlinkStatus.valid && ( @@ -98,74 +113,87 @@ export function HomePage() { title="API Profiles" value={overview?.profiles ?? 0} icon={Key} + variant="accent" + subtitle="Settings-based" onClick={() => navigate('/api')} /> navigate('/cliproxy')} /> navigate('/accounts')} /> navigate('/health')} />
{/* Quick Actions */} - + Quick Actions - - - + {/* Quick Commands */} + + {/* Shared Data Summary */} - - - Shared Data -
-
- - {shared?.commands ?? 0} +
+ {shared?.commands ?? 0} Commands
-
- {shared?.skills ?? 0} +
+ {shared?.skills ?? 0} Skills
-
- {shared?.agents ?? 0} +
+ {shared?.agents ?? 0} Agents