mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 04:17:11 +00:00
fix(dashboard): support unified config across health checks and settings
- health-service.ts: respect isUnifiedMode() in fixHealthIssue() - config-checks.ts: check config.yaml in unified mode - profile-checks.ts: read profiles from unified config - settings-routes.ts: use atomic writes (temp+rename) for presets - profile-registry.ts: add DRY helpers getAllProfilesMerged(), getDefaultResolved() Closes #203
This commit is contained in:
@@ -332,6 +332,42 @@ export class ProfileRegistry {
|
||||
config.accounts[name].last_used = new Date().toISOString();
|
||||
saveUnifiedConfig(config);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DRY Helper Methods (consolidated logic)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get all profiles merged from both legacy and unified config.
|
||||
* Unified config takes precedence for duplicate names.
|
||||
* DRY helper to consolidate merge logic used in multiple places.
|
||||
*/
|
||||
getAllProfilesMerged(): Record<string, ProfileMetadata> {
|
||||
const legacyProfiles = this.getAllProfiles();
|
||||
const unifiedAccounts = this.getAllAccountsUnified();
|
||||
|
||||
// Start with legacy profiles
|
||||
const merged: Record<string, ProfileMetadata> = { ...legacyProfiles };
|
||||
|
||||
// Override with unified config accounts (takes precedence)
|
||||
for (const [name, account] of Object.entries(unifiedAccounts)) {
|
||||
merged[name] = {
|
||||
type: 'account',
|
||||
created: account.created,
|
||||
last_used: account.last_used,
|
||||
};
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved default profile from unified config first, fallback to legacy.
|
||||
* DRY helper to consolidate default resolution logic.
|
||||
*/
|
||||
getDefaultResolved(): string | null {
|
||||
return this.getDefaultUnified() ?? this.getDefaultProfile();
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfileRegistry;
|
||||
|
||||
@@ -142,6 +142,17 @@ export function fixHealthIssue(checkId: string): { success: boolean; message: st
|
||||
return { success: true, message: 'Created ~/.ccs directory' };
|
||||
|
||||
case 'config-file': {
|
||||
// Use appropriate config based on unified mode
|
||||
const { isUnifiedMode } = require('../config/unified-config-loader');
|
||||
if (isUnifiedMode()) {
|
||||
const {
|
||||
loadOrCreateUnifiedConfig,
|
||||
saveUnifiedConfig,
|
||||
} = require('../config/unified-config-loader');
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
saveUnifiedConfig(config);
|
||||
return { success: true, message: 'Created/updated config.yaml' };
|
||||
}
|
||||
const configPath = getConfigPath();
|
||||
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
||||
fs.writeFileSync(configPath, JSON.stringify({ profiles: {} }, null, 2) + '\n');
|
||||
@@ -149,6 +160,12 @@ export function fixHealthIssue(checkId: string): { success: boolean; message: st
|
||||
}
|
||||
|
||||
case 'profiles-file': {
|
||||
// Use appropriate storage based on unified mode
|
||||
const { isUnifiedMode: isUnified } = require('../config/unified-config-loader');
|
||||
if (isUnified()) {
|
||||
// In unified mode, accounts are stored in config.yaml
|
||||
return { success: true, message: 'Accounts stored in config.yaml (unified mode)' };
|
||||
}
|
||||
const profilesPath = path.join(ccsDir, 'profiles.json');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
fs.writeFileSync(profilesPath, JSON.stringify({ profiles: {} }, null, 2) + '\n');
|
||||
|
||||
@@ -1,18 +1,57 @@
|
||||
/**
|
||||
* Configuration Health Checks
|
||||
*
|
||||
* Check config.json, settings files, and Claude settings.
|
||||
* Check config.json, config.yaml, settings files, and Claude settings.
|
||||
* Supports both legacy (config.json) and unified (config.yaml) modes.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { getConfigPath } from '../../utils/config-manager';
|
||||
import { isUnifiedMode, hasUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import type { HealthCheck } from './types';
|
||||
|
||||
/**
|
||||
* Check config.json file
|
||||
* Check config file (config.json or config.yaml based on mode)
|
||||
*/
|
||||
export function checkConfigFile(): HealthCheck {
|
||||
// In unified mode, check config.yaml
|
||||
if (isUnifiedMode() || hasUnifiedConfig()) {
|
||||
const ccsDir = path.join(process.env.HOME || '', '.ccs');
|
||||
const yamlPath = path.join(ccsDir, 'config.yaml');
|
||||
|
||||
if (!fs.existsSync(yamlPath)) {
|
||||
return {
|
||||
id: 'config-file',
|
||||
name: 'config.yaml',
|
||||
status: 'warning',
|
||||
message: 'Not found (unified mode)',
|
||||
details: yamlPath,
|
||||
fixable: true,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
fs.readFileSync(yamlPath, 'utf8');
|
||||
return {
|
||||
id: 'config-file',
|
||||
name: 'config.yaml',
|
||||
status: 'ok',
|
||||
message: 'Valid (unified mode)',
|
||||
details: yamlPath,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
id: 'config-file',
|
||||
name: 'config.yaml',
|
||||
status: 'error',
|
||||
message: 'Cannot read file',
|
||||
details: yamlPath,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy mode: check config.json
|
||||
const configPath = getConfigPath();
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
|
||||
@@ -2,16 +2,52 @@
|
||||
* Profile Health Checks
|
||||
*
|
||||
* Check profiles, instances, and delegation.
|
||||
* Supports both legacy (config.json) and unified (config.yaml) modes.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { isUnifiedMode, loadUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import type { HealthCheck } from './types';
|
||||
|
||||
/**
|
||||
* Check profiles configuration
|
||||
* Check profiles configuration (API profiles from config.json or config.yaml)
|
||||
*/
|
||||
export function checkProfiles(ccsDir: string): HealthCheck {
|
||||
// Check unified config first
|
||||
if (isUnifiedMode()) {
|
||||
const config = loadUnifiedConfig();
|
||||
if (!config) {
|
||||
return {
|
||||
id: 'profiles',
|
||||
name: 'Profiles',
|
||||
status: 'info',
|
||||
message: 'config.yaml not loaded',
|
||||
};
|
||||
}
|
||||
|
||||
const profileCount = Object.keys(config.profiles || {}).length;
|
||||
const profileNames = Object.keys(config.profiles || {}).join(', ');
|
||||
|
||||
if (profileCount === 0) {
|
||||
return {
|
||||
id: 'profiles',
|
||||
name: 'Profiles',
|
||||
status: 'ok',
|
||||
message: 'No API profiles (unified mode)',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'profiles',
|
||||
name: 'Profiles',
|
||||
status: 'ok',
|
||||
message: `${profileCount} configured (unified)`,
|
||||
details: profileNames.length > 40 ? profileNames.substring(0, 37) + '...' : profileNames,
|
||||
};
|
||||
}
|
||||
|
||||
// Legacy mode: check config.json
|
||||
const configPath = path.join(ccsDir, 'config.json');
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
|
||||
@@ -222,7 +222,11 @@ router.post('/:profile/presets', (req: Request, res: Response): void => {
|
||||
};
|
||||
|
||||
settings.presets.push(preset);
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
||||
|
||||
// Atomic write: temp file + rename
|
||||
const tempPath = settingsPath + '.tmp';
|
||||
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n');
|
||||
fs.renameSync(tempPath, settingsPath);
|
||||
|
||||
res.status(201).json({ preset });
|
||||
} catch (error) {
|
||||
@@ -250,7 +254,11 @@ router.delete('/:profile/presets/:name', (req: Request, res: Response): void =>
|
||||
}
|
||||
|
||||
settings.presets = settings.presets.filter((p) => p.name !== name);
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
||||
|
||||
// Atomic write: temp file + rename
|
||||
const tempPath = settingsPath + '.tmp';
|
||||
fs.writeFileSync(tempPath, JSON.stringify(settings, null, 2) + '\n');
|
||||
fs.renameSync(tempPath, settingsPath);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user