mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 12:09:03 +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'],
|
['start', 'Start CLIProxy instance in background'],
|
||||||
['restart', 'Restart CLIProxy instance'],
|
['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'],
|
['stop', 'Stop running CLIProxy instance'],
|
||||||
['doctor | diag', 'Quota diagnostics and shared project detection'],
|
['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 { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services';
|
||||||
import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector';
|
import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector';
|
||||||
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
|
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> {
|
export async function handleStart(verbose = false): Promise<void> {
|
||||||
await initUI();
|
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(ok(`CLIProxy started on port ${result.port}`));
|
||||||
}
|
}
|
||||||
console.log(dim('To stop: ccs cliproxy stop'));
|
console.log(dim('To stop: ccs cliproxy stop'));
|
||||||
|
if (result.port) {
|
||||||
|
printControlPanelAccess(result.port);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(warn(result.error || 'Failed to start CLIProxy'));
|
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('');
|
||||||
console.log(dim('To stop: ccs cliproxy stop'));
|
console.log(dim('To stop: ccs cliproxy stop'));
|
||||||
|
printControlPanelAccess(status.port ?? port);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: detect untracked/orphaned proxy process (e.g. detached session without lock file).
|
// Fallback: detect untracked/orphaned proxy process (e.g. detached session without lock file).
|
||||||
const detected = await detectRunningProxy(port);
|
const detected = await detectRunningProxy(port);
|
||||||
@@ -109,6 +130,7 @@ export async function handleProxyStatus(verbose = false): Promise<void> {
|
|||||||
}
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(dim('To stop: ccs cliproxy stop'));
|
console.log(dim('To stop: ccs cliproxy stop'));
|
||||||
|
printControlPanelAccess(port);
|
||||||
} else {
|
} else {
|
||||||
console.log(` Status: ${color('Not running', 'warning')}`);
|
console.log(` Status: ${color('Not running', 'warning')}`);
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|||||||
Reference in new issue
Block a user