mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: enhance dialog and drawer components with new styles and functionality
- Updated Dialog component to support fullscreen mode and added a back button. - Introduced Drawer component for side navigation with customizable size and position. - Added DialogStyleContext for managing dialog style preferences including smart mode and drawer settings. - Implemented pending question service for managing persistent storage of pending questions. - Enhanced WebSocket handling to request pending questions upon frontend readiness. - Created dashboard launcher utility to manage the Dashboard server lifecycle.
This commit is contained in:
201
ccw/src/utils/dashboard-launcher.ts
Normal file
201
ccw/src/utils/dashboard-launcher.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
// ========================================
|
||||
// Dashboard Launcher Utility
|
||||
// ========================================
|
||||
// Detects Dashboard server status and auto-starts if needed
|
||||
|
||||
import { spawn, type ChildProcess } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import http from 'http';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Constants
|
||||
const DASHBOARD_PORT = Number(process.env.CCW_PORT || 3456);
|
||||
const DASHBOARD_HOST = process.env.CCW_HOST || '127.0.0.1';
|
||||
const DASHBOARD_CHECK_TIMEOUT_MS = 500;
|
||||
const DASHBOARD_STARTUP_TIMEOUT_MS = 30000;
|
||||
const DASHBOARD_STARTUP_POLL_INTERVAL_MS = 500;
|
||||
|
||||
// Path to CCW CLI (adjust based on build output location)
|
||||
const CCW_CLI_PATH = join(__dirname, '../../bin/ccw.js');
|
||||
|
||||
// Track spawned dashboard process
|
||||
let dashboardProcess: ChildProcess | null = null;
|
||||
|
||||
/**
|
||||
* Check if the Dashboard server is running by attempting to connect to its health endpoint.
|
||||
* @returns Promise that resolves to true if server is reachable
|
||||
*/
|
||||
export async function isDashboardServerRunning(
|
||||
port: number = DASHBOARD_PORT,
|
||||
host: string = DASHBOARD_HOST
|
||||
): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.get(
|
||||
{
|
||||
hostname: host,
|
||||
port,
|
||||
path: '/api/health',
|
||||
timeout: DASHBOARD_CHECK_TIMEOUT_MS,
|
||||
},
|
||||
(res) => {
|
||||
res.resume(); // Consume response data
|
||||
res.on('end', () => {
|
||||
resolve(res.statusCode === 200);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
req.on('error', () => {
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for Dashboard server to become available.
|
||||
* Polls the health endpoint until it responds or timeout is reached.
|
||||
* @param port - Port to check
|
||||
* @param host - Host to check
|
||||
* @param timeoutMs - Maximum time to wait
|
||||
* @returns Promise that resolves to true if server became available
|
||||
*/
|
||||
export async function waitForDashboardReady(
|
||||
port: number = DASHBOARD_PORT,
|
||||
host: string = DASHBOARD_HOST,
|
||||
timeoutMs: number = DASHBOARD_STARTUP_TIMEOUT_MS
|
||||
): Promise<boolean> {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
const isRunning = await isDashboardServerRunning(port, host);
|
||||
if (isRunning) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, DASHBOARD_STARTUP_POLL_INTERVAL_MS));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to start the CCW Dashboard server in a detached child process.
|
||||
* @param port - Port to start the server on
|
||||
* @param host - Host to bind the server to
|
||||
* @param openBrowser - Whether to open browser (default: false)
|
||||
* @returns Promise that resolves to true if process was successfully spawned and became ready
|
||||
*/
|
||||
export async function startCcwServeProcess(
|
||||
port: number = DASHBOARD_PORT,
|
||||
host: string = DASHBOARD_HOST,
|
||||
openBrowser: boolean = false
|
||||
): Promise<boolean> {
|
||||
// Don't start if already running
|
||||
const alreadyRunning = await isDashboardServerRunning(port, host);
|
||||
if (alreadyRunning) {
|
||||
console.log(`[DashboardLauncher] Dashboard already running at ${host}:${port}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't spawn duplicate process
|
||||
if (dashboardProcess && !dashboardProcess.killed) {
|
||||
console.log(`[DashboardLauncher] Dashboard process already spawned, waiting for ready...`);
|
||||
return waitForDashboardReady(port, host);
|
||||
}
|
||||
|
||||
console.log(`[DashboardLauncher] Starting Dashboard server at ${host}:${port}...`);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const args = ['serve', '--port', port.toString(), '--host', host];
|
||||
if (!openBrowser) {
|
||||
args.push('--no-browser');
|
||||
}
|
||||
|
||||
dashboardProcess = spawn('node', [CCW_CLI_PATH, ...args], {
|
||||
detached: true,
|
||||
stdio: 'ignore', // Detach stdio from parent
|
||||
shell: process.platform === 'win32', // Use shell on Windows for better compatibility
|
||||
env: {
|
||||
...process.env,
|
||||
CCW_PORT: port.toString(),
|
||||
CCW_HOST: host,
|
||||
},
|
||||
});
|
||||
|
||||
dashboardProcess.unref(); // Allow parent to exit independently
|
||||
|
||||
dashboardProcess.on('error', (err) => {
|
||||
console.error(`[DashboardLauncher] Failed to start Dashboard: ${err.message}`);
|
||||
dashboardProcess = null;
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
// Wait for server to become ready
|
||||
waitForDashboardReady(port, host)
|
||||
.then((ready) => {
|
||||
if (ready) {
|
||||
console.log(`[DashboardLauncher] Dashboard started successfully (PID: ${dashboardProcess?.pid})`);
|
||||
} else {
|
||||
console.error(`[DashboardLauncher] Dashboard failed to start within timeout`);
|
||||
dashboardProcess = null;
|
||||
}
|
||||
resolve(ready);
|
||||
})
|
||||
.catch(() => {
|
||||
resolve(false);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`[DashboardLauncher] Exception while starting Dashboard: ${(err as Error).message}`);
|
||||
dashboardProcess = null;
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current dashboard process info.
|
||||
* @returns Object with process status and PID
|
||||
*/
|
||||
export function getDashboardProcessStatus(): { running: boolean; pid: number | null } {
|
||||
return {
|
||||
running: dashboardProcess !== null && !dashboardProcess.killed,
|
||||
pid: dashboardProcess?.pid ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the spawned dashboard process (if any).
|
||||
* Note: This only stops the process we spawned, not externally started servers.
|
||||
*/
|
||||
export async function stopSpawnedDashboard(): Promise<void> {
|
||||
if (dashboardProcess && !dashboardProcess.killed) {
|
||||
console.log(`[DashboardLauncher] Stopping spawned Dashboard process (PID: ${dashboardProcess.pid})...`);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (dashboardProcess && !dashboardProcess.killed) {
|
||||
dashboardProcess.kill('SIGKILL');
|
||||
}
|
||||
dashboardProcess = null;
|
||||
resolve();
|
||||
}, 5000);
|
||||
|
||||
dashboardProcess!.once('exit', () => {
|
||||
clearTimeout(timeout);
|
||||
dashboardProcess = null;
|
||||
console.log(`[DashboardLauncher] Dashboard process stopped`);
|
||||
resolve();
|
||||
});
|
||||
|
||||
dashboardProcess!.kill('SIGTERM');
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user