mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 03:13:12 +00:00
fix(cliproxy): surface Control Panel URL and login key in status/start
This commit is contained in:
1 parent
8a0031e514
commit
cee9822008
3 files changed
+115
-1
No files matched your search
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Tests for printControlPanelAccess: the Control Panel (API Management Center)
|
||||
* URL + login-key surface shown by `ccs cliproxy status` / `start`.
|
||||
*
|
||||
* Why this matters:
|
||||
* - The Control Panel login screen only asks for a "Management Key" with no
|
||||
* hint. Users who don't know the default (`ccs`) cannot get in. This surface
|
||||
* is the fix, so it must print the panel URL on the active port and the
|
||||
* EFFECTIVE key (default `ccs`, or the user's custom management_secret).
|
||||
*/
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test';
|
||||
|
||||
import { printControlPanelAccess } from '../proxy-lifecycle-subcommand';
|
||||
import { invalidateConfigCache, mutateConfig } from '../../../config/config-loader-facade';
|
||||
|
||||
function createTestHome(): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-control-panel-test-'));
|
||||
const ccsDir = path.join(dir, '.ccs');
|
||||
fs.mkdirSync(path.join(ccsDir, 'cliproxy', 'auth'), { recursive: true });
|
||||
fs.writeFileSync(path.join(ccsDir, 'config.yaml'), 'version: 1\n', 'utf8');
|
||||
return dir;
|
||||
}
|
||||
|
||||
describe('printControlPanelAccess', () => {
|
||||
let tempHome: string;
|
||||
let originalCcsHome: string | undefined;
|
||||
let logSpy: ReturnType<typeof spyOn>;
|
||||
let lines: string[];
|
||||
|
||||
beforeEach(() => {
|
||||
tempHome = createTestHome();
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
process.env.CCS_HOME = tempHome;
|
||||
invalidateConfigCache();
|
||||
lines = [];
|
||||
logSpy = spyOn(console, 'log').mockImplementation((msg?: unknown) => {
|
||||
lines.push(String(msg ?? ''));
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
logSpy.mockRestore();
|
||||
if (originalCcsHome === undefined) {
|
||||
delete process.env.CCS_HOME;
|
||||
} else {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
}
|
||||
invalidateConfigCache();
|
||||
try {
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
} catch {
|
||||
/* best-effort cleanup */
|
||||
}
|
||||
});
|
||||
|
||||
it('prints the panel URL on the given port and the default key (ccs)', () => {
|
||||
printControlPanelAccess(8317);
|
||||
const out = lines.join('\n');
|
||||
expect(out).toContain('http://127.0.0.1:8317/management.html');
|
||||
expect(out).toContain('Panel login key:');
|
||||
expect(out).toContain('ccs');
|
||||
});
|
||||
|
||||
it('uses the active port in the URL', () => {
|
||||
printControlPanelAccess(9000);
|
||||
expect(lines.join('\n')).toContain('http://127.0.0.1:9000/management.html');
|
||||
});
|
||||
|
||||
it('prints a custom management_secret when configured', () => {
|
||||
mutateConfig((config) => {
|
||||
if (!config.cliproxy) {
|
||||
config.cliproxy = {};
|
||||
}
|
||||
if (!config.cliproxy.auth) {
|
||||
config.cliproxy.auth = {};
|
||||
}
|
||||
config.cliproxy.auth.management_secret = 'my-custom-key';
|
||||
});
|
||||
invalidateConfigCache();
|
||||
|
||||
printControlPanelAccess(8317);
|
||||
const out = lines.join('\n');
|
||||
expect(out).toContain('Panel login key:');
|
||||
expect(out).toContain('my-custom-key');
|
||||
});
|
||||
});
|
||||
@@ -89,7 +89,10 @@ export async function showHelp(): Promise<void> {
|
||||
[
|
||||
['start', 'Start CLIProxy instance in background'],
|
||||
['restart', 'Restart CLIProxy instance'],
|
||||
['status [--verbose]', 'Show running CLIProxy status (--verbose adds uptime)'],
|
||||
[
|
||||
'status [--verbose]',
|
||||
'Show CLIProxy status + Control Panel URL and login key (--verbose adds uptime)',
|
||||
],
|
||||
['stop', 'Stop running CLIProxy instance'],
|
||||
['doctor | diag', 'Quota diagnostics and shared project detection'],
|
||||
],
|
||||
|
||||
@@ -12,6 +12,23 @@ import { initUI, header, color, dim, ok, warn, info } from '../../utils/ui';
|
||||
import { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services';
|
||||
import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector';
|
||||
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
|
||||
import { getEffectiveManagementSecret } from '../../cliproxy/auth/auth-token-manager';
|
||||
|
||||
/**
|
||||
* Print how to reach the local CLIProxy Control Panel (a.k.a. API Management
|
||||
* Center) and the key needed to log in.
|
||||
*
|
||||
* The panel is served by CLIProxy at `/management.html` on the proxy port and
|
||||
* its login is gated by the management secret (default `ccs`). The login screen
|
||||
* only asks for a "Management Key" with no hint, so users frequently cannot get
|
||||
* in. Surfacing the URL + resolved key here removes that guesswork.
|
||||
*/
|
||||
export function printControlPanelAccess(port: number): void {
|
||||
const secret = getEffectiveManagementSecret();
|
||||
console.log('');
|
||||
console.log(` Control Panel: http://127.0.0.1:${port}/management.html`);
|
||||
console.log(` Panel login key: ${secret}`);
|
||||
}
|
||||
|
||||
export async function handleStart(verbose = false): Promise<void> {
|
||||
await initUI();
|
||||
@@ -30,6 +47,9 @@ export async function handleStart(verbose = false): Promise<void> {
|
||||
console.log(ok(`CLIProxy started on port ${result.port}`));
|
||||
}
|
||||
console.log(dim('To stop: ccs cliproxy stop'));
|
||||
if (result.port) {
|
||||
printControlPanelAccess(result.port);
|
||||
}
|
||||
} else {
|
||||
console.log(warn(result.error || 'Failed to start CLIProxy'));
|
||||
}
|
||||
@@ -93,6 +113,7 @@ export async function handleProxyStatus(verbose = false): Promise<void> {
|
||||
|
||||
console.log('');
|
||||
console.log(dim('To stop: ccs cliproxy stop'));
|
||||
printControlPanelAccess(status.port ?? port);
|
||||
} else {
|
||||
// Fallback: detect untracked/orphaned proxy process (e.g. detached session without lock file).
|
||||
const detected = await detectRunningProxy(port);
|
||||
@@ -109,6 +130,7 @@ export async function handleProxyStatus(verbose = false): Promise<void> {
|
||||
}
|
||||
console.log('');
|
||||
console.log(dim('To stop: ccs cliproxy stop'));
|
||||
printControlPanelAccess(port);
|
||||
} else {
|
||||
console.log(` Status: ${color('Not running', 'warning')}`);
|
||||
console.log('');
|
||||
|
||||
Reference in new issue
Block a user