mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-20 04:17:37 +00:00
Merge pull request #1324 from kaitranntt/kai/fix/dashboard-cliproxy-restart-button
fix: ensure dashboard cliproxy restart waits for recovery
This commit is contained in:
@@ -5,10 +5,6 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
isRunningUnderSupervisord,
|
||||
restartCliproxyViaSupervisord,
|
||||
} from '../../docker/supervisord-lifecycle';
|
||||
import {
|
||||
fetchCliproxyStats,
|
||||
fetchCliproxyModels,
|
||||
@@ -59,9 +55,11 @@ import {
|
||||
getDeniedModelIdReasonForProvider,
|
||||
} from '../../cliproxy/ai-providers/model-id-normalizer';
|
||||
import { installDashboardCliproxyVersion } from '../services/cliproxy-dashboard-install-service';
|
||||
import { restartDashboardCliproxy } from '../services/cliproxy-dashboard-restart-service';
|
||||
import { requireLocalAccessWhenAuthDisabled } from '../middleware/auth-middleware';
|
||||
|
||||
const router = Router();
|
||||
type RestartDashboardCliproxyHandler = typeof restartDashboardCliproxy;
|
||||
|
||||
const QUOTA_RATE_LIMIT_WINDOW_MS = 60_000;
|
||||
const QUOTA_RATE_LIMIT_MAX_REQUESTS = 120;
|
||||
@@ -1085,34 +1083,21 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
||||
* POST /api/cliproxy/restart - Restart CLIProxy without version change
|
||||
* Returns: { success, port?, error? }
|
||||
*/
|
||||
router.post('/restart', async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const port = resolveLifecyclePort();
|
||||
if (isRunningUnderSupervisord()) {
|
||||
// Docker mode: delegate to supervisord which owns the process lifecycle
|
||||
const result = restartCliproxyViaSupervisord();
|
||||
export function registerCliproxyRestartRoute(
|
||||
targetRouter: Router,
|
||||
restartHandler: RestartDashboardCliproxyHandler = restartDashboardCliproxy
|
||||
): void {
|
||||
targetRouter.post('/restart', async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const result = await restartHandler();
|
||||
res.json(result);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Local mode: direct process management
|
||||
await stopProxy(port);
|
||||
|
||||
// Small delay to ensure port is released
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
// Start proxy
|
||||
const startResult = await ensureCliproxyService(port);
|
||||
|
||||
if (startResult.started || startResult.alreadyRunning) {
|
||||
res.json({ success: true, port: startResult.port });
|
||||
} else {
|
||||
res.json({ success: false, error: startResult.error || 'Failed to start proxy' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
registerCliproxyRestartRoute(router);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
|
||||
import { ensureCliproxyService, type ServiceStartResult } from '../../cliproxy/service-manager';
|
||||
import { waitForProxyHealthy } from '../../cliproxy/proxy/proxy-detector';
|
||||
import { stopProxy } from '../../cliproxy/session-tracker';
|
||||
import {
|
||||
isRunningUnderSupervisord,
|
||||
restartCliproxyViaSupervisord,
|
||||
} from '../../docker/supervisord-lifecycle';
|
||||
|
||||
export interface CliproxyDashboardRestartResult {
|
||||
success: boolean;
|
||||
port?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface CliproxyDashboardRestartDeps {
|
||||
resolveLifecyclePortFn?: typeof resolveLifecyclePort;
|
||||
isRunningUnderSupervisordFn?: typeof isRunningUnderSupervisord;
|
||||
restartCliproxyViaSupervisordFn?: typeof restartCliproxyViaSupervisord;
|
||||
waitForProxyHealthyFn?: typeof waitForProxyHealthy;
|
||||
stopProxyFn?: typeof stopProxy;
|
||||
ensureCliproxyServiceFn?: typeof ensureCliproxyService;
|
||||
delayFn?: (ms: number) => Promise<void>;
|
||||
}
|
||||
|
||||
const RESTART_SETTLE_MS = 500;
|
||||
const SUPERVISOR_HEALTH_TIMEOUT_MS = 10_000;
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function startResultToRestartResult(
|
||||
startResult: ServiceStartResult
|
||||
): CliproxyDashboardRestartResult {
|
||||
if (startResult.started || startResult.alreadyRunning) {
|
||||
return { success: true, port: startResult.port };
|
||||
}
|
||||
|
||||
return { success: false, error: startResult.error || 'Failed to start proxy' };
|
||||
}
|
||||
|
||||
export async function restartDashboardCliproxy(
|
||||
deps: CliproxyDashboardRestartDeps = {}
|
||||
): Promise<CliproxyDashboardRestartResult> {
|
||||
const resolveLifecyclePortFn = deps.resolveLifecyclePortFn ?? resolveLifecyclePort;
|
||||
const isRunningUnderSupervisordFn = deps.isRunningUnderSupervisordFn ?? isRunningUnderSupervisord;
|
||||
const restartCliproxyViaSupervisordFn =
|
||||
deps.restartCliproxyViaSupervisordFn ?? restartCliproxyViaSupervisord;
|
||||
const waitForProxyHealthyFn = deps.waitForProxyHealthyFn ?? waitForProxyHealthy;
|
||||
const stopProxyFn = deps.stopProxyFn ?? stopProxy;
|
||||
const ensureCliproxyServiceFn = deps.ensureCliproxyServiceFn ?? ensureCliproxyService;
|
||||
const delayFn = deps.delayFn ?? delay;
|
||||
|
||||
const port = resolveLifecyclePortFn();
|
||||
|
||||
if (isRunningUnderSupervisordFn()) {
|
||||
const supervisorResult = restartCliproxyViaSupervisordFn();
|
||||
if (!supervisorResult.success) {
|
||||
return supervisorResult;
|
||||
}
|
||||
|
||||
const healthy = await waitForProxyHealthyFn(port, SUPERVISOR_HEALTH_TIMEOUT_MS);
|
||||
if (!healthy) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'CLIProxy did not become healthy after supervisor restart',
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, port: supervisorResult.port ?? port };
|
||||
}
|
||||
|
||||
await stopProxyFn(port);
|
||||
await delayFn(RESTART_SETTLE_MS);
|
||||
|
||||
const startResult = await ensureCliproxyServiceFn(port);
|
||||
return startResultToRestartResult(startResult);
|
||||
}
|
||||
Reference in New Issue
Block a user