mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
Add coverage prettification and sorting functionality
- Introduced `prettify.css` for syntax highlighting in coverage reports. - Added `prettify.js` to handle code formatting and highlighting. - Included `sort-arrow-sprite.png` for sort indicators in the coverage table. - Implemented `sorter.js` to enable sorting and filtering of coverage summary tables. - Added a search box for filtering table rows based on user input.
This commit is contained in:
@@ -71,8 +71,7 @@ describe('EndpointsPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// confirm() used for delete
|
||||
// @ts-expect-error - test override
|
||||
global.confirm = vi.fn(() => true);
|
||||
vi.stubGlobal('confirm', vi.fn(() => true));
|
||||
});
|
||||
|
||||
it('should render page title', () => {
|
||||
@@ -128,4 +127,3 @@ describe('EndpointsPage', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// ========================================
|
||||
// Application settings and configuration with CLI tools management
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Settings,
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
Calendar,
|
||||
File,
|
||||
ArrowUpCircle,
|
||||
Save,
|
||||
} from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
@@ -53,6 +54,21 @@ import {
|
||||
useUpgradeCcwInstallation,
|
||||
} from '@/hooks/useSystemSettings';
|
||||
|
||||
// ========== Tool Config File Helpers ==========
|
||||
|
||||
/** Tools that use .env file for environment variables */
|
||||
const ENV_FILE_TOOLS = new Set(['gemini', 'qwen', 'opencode']);
|
||||
/** Tools that use --settings for Claude CLI settings file */
|
||||
const SETTINGS_FILE_TOOLS = new Set(['claude']);
|
||||
/** Tools that don't need any config file */
|
||||
const NO_CONFIG_FILE_TOOLS = new Set(['codex']);
|
||||
|
||||
function getConfigFileType(toolId: string): 'envFile' | 'settingsFile' | 'none' {
|
||||
if (ENV_FILE_TOOLS.has(toolId)) return 'envFile';
|
||||
if (SETTINGS_FILE_TOOLS.has(toolId)) return 'settingsFile';
|
||||
return 'none';
|
||||
}
|
||||
|
||||
// ========== CLI Tool Card Component ==========
|
||||
|
||||
interface CliToolCardProps {
|
||||
@@ -61,13 +77,16 @@ interface CliToolCardProps {
|
||||
isDefault: boolean;
|
||||
isExpanded: boolean;
|
||||
toolAvailable?: boolean;
|
||||
isSaving?: boolean;
|
||||
onToggleExpand: () => void;
|
||||
onToggleEnabled: () => void;
|
||||
onSetDefault: () => void;
|
||||
onUpdateModel: (field: 'primaryModel' | 'secondaryModel', value: string) => void;
|
||||
onUpdateTags: (tags: string[]) => void;
|
||||
onUpdateAvailableModels: (models: string[]) => void;
|
||||
onUpdateEnvFile: (envFile: string | undefined) => void;
|
||||
onUpdateSettingsFile: (settingsFile: string | undefined) => void;
|
||||
onSaveToBackend: () => void;
|
||||
}
|
||||
|
||||
function CliToolCard({
|
||||
@@ -76,13 +95,16 @@ function CliToolCard({
|
||||
isDefault,
|
||||
isExpanded,
|
||||
toolAvailable,
|
||||
isSaving,
|
||||
onToggleExpand,
|
||||
onToggleEnabled,
|
||||
onSetDefault,
|
||||
onUpdateModel,
|
||||
onUpdateTags,
|
||||
onUpdateAvailableModels,
|
||||
onUpdateEnvFile,
|
||||
onUpdateSettingsFile,
|
||||
onSaveToBackend,
|
||||
}: CliToolCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
@@ -123,6 +145,8 @@ function CliToolCard({
|
||||
// Predefined tags
|
||||
const predefinedTags = ['分析', 'Debug', 'implementation', 'refactoring', 'testing'];
|
||||
|
||||
const configFileType = getConfigFileType(toolId);
|
||||
|
||||
return (
|
||||
<Card className={cn('overflow-hidden', !config.enabled && 'opacity-60')}>
|
||||
{/* Header */}
|
||||
@@ -350,26 +374,59 @@ function CliToolCard({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Settings File */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'apiSettings.cliSettings.settingsFile' })}
|
||||
</label>
|
||||
<Input
|
||||
value={config.settingsFile || ''}
|
||||
onChange={(e) => onUpdateSettingsFile(e.target.value || undefined)}
|
||||
placeholder={formatMessage({ id: 'apiSettings.cliSettings.settingsFilePlaceholder' })}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'apiSettings.cliSettings.settingsFileHint' })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!isDefault && config.enabled && (
|
||||
<Button variant="outline" size="sm" onClick={onSetDefault}>
|
||||
{formatMessage({ id: 'settings.cliTools.setDefault' })}
|
||||
</Button>
|
||||
{/* Env File - for gemini/qwen/opencode */}
|
||||
{configFileType === 'envFile' && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'settings.cliTools.envFile' })}
|
||||
</label>
|
||||
<Input
|
||||
value={config.envFile || ''}
|
||||
onChange={(e) => onUpdateEnvFile(e.target.value || undefined)}
|
||||
placeholder={formatMessage({ id: 'settings.cliTools.envFilePlaceholder' })}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'settings.cliTools.envFileHint' })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Settings File - for claude only */}
|
||||
{configFileType === 'settingsFile' && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'apiSettings.cliSettings.settingsFile' })}
|
||||
</label>
|
||||
<Input
|
||||
value={config.settingsFile || ''}
|
||||
onChange={(e) => onUpdateSettingsFile(e.target.value || undefined)}
|
||||
placeholder={formatMessage({ id: 'apiSettings.cliSettings.settingsFilePlaceholder' })}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'apiSettings.cliSettings.settingsFileHint' })}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-2">
|
||||
{!isDefault && config.enabled && (
|
||||
<Button variant="outline" size="sm" onClick={onSetDefault}>
|
||||
{formatMessage({ id: 'settings.cliTools.setDefault' })}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
onClick={onSaveToBackend}
|
||||
disabled={isSaving}
|
||||
>
|
||||
<Save className="w-4 h-4 mr-1" />
|
||||
{isSaving
|
||||
? formatMessage({ id: 'settings.cliTools.saving' })
|
||||
: formatMessage({ id: 'settings.cliTools.saveToConfig' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
@@ -666,13 +723,16 @@ interface CliToolsWithStatusProps {
|
||||
cliTools: Record<string, CliToolConfig>;
|
||||
defaultCliTool: string;
|
||||
expandedTools: Set<string>;
|
||||
savingTools: Set<string>;
|
||||
onToggleExpand: (toolId: string) => void;
|
||||
onToggleEnabled: (toolId: string) => void;
|
||||
onSetDefault: (toolId: string) => void;
|
||||
onUpdateModel: (toolId: string, field: 'primaryModel' | 'secondaryModel', value: string) => void;
|
||||
onUpdateTags: (toolId: string, tags: string[]) => void;
|
||||
onUpdateAvailableModels: (toolId: string, models: string[]) => void;
|
||||
onUpdateEnvFile: (toolId: string, envFile: string | undefined) => void;
|
||||
onUpdateSettingsFile: (toolId: string, settingsFile: string | undefined) => void;
|
||||
onSaveToBackend: (toolId: string) => void;
|
||||
formatMessage: ReturnType<typeof useIntl>['formatMessage'];
|
||||
}
|
||||
|
||||
@@ -680,13 +740,16 @@ function CliToolsWithStatus({
|
||||
cliTools,
|
||||
defaultCliTool,
|
||||
expandedTools,
|
||||
savingTools,
|
||||
onToggleExpand,
|
||||
onToggleEnabled,
|
||||
onSetDefault,
|
||||
onUpdateModel,
|
||||
onUpdateTags,
|
||||
onUpdateAvailableModels,
|
||||
onUpdateEnvFile,
|
||||
onUpdateSettingsFile,
|
||||
onSaveToBackend,
|
||||
formatMessage,
|
||||
}: CliToolsWithStatusProps) {
|
||||
const { data: toolStatus } = useCliToolStatus();
|
||||
@@ -707,13 +770,16 @@ function CliToolsWithStatus({
|
||||
isDefault={toolId === defaultCliTool}
|
||||
isExpanded={expandedTools.has(toolId)}
|
||||
toolAvailable={status?.available}
|
||||
isSaving={savingTools.has(toolId)}
|
||||
onToggleExpand={() => onToggleExpand(toolId)}
|
||||
onToggleEnabled={() => onToggleEnabled(toolId)}
|
||||
onSetDefault={() => onSetDefault(toolId)}
|
||||
onUpdateModel={(field, value) => onUpdateModel(toolId, field, value)}
|
||||
onUpdateTags={(tags) => onUpdateTags(toolId, tags)}
|
||||
onUpdateAvailableModels={(models) => onUpdateAvailableModels(toolId, models)}
|
||||
onUpdateEnvFile={(envFile) => onUpdateEnvFile(toolId, envFile)}
|
||||
onUpdateSettingsFile={(settingsFile) => onUpdateSettingsFile(toolId, settingsFile)}
|
||||
onSaveToBackend={() => onSaveToBackend(toolId)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -733,6 +799,7 @@ export function SettingsPage() {
|
||||
const { updateCliTool, setDefaultCliTool, setUserPreferences, resetUserPreferences } = useConfigStore();
|
||||
|
||||
const [expandedTools, setExpandedTools] = useState<Set<string>>(new Set());
|
||||
const [savingTools, setSavingTools] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleToolExpand = (toolId: string) => {
|
||||
setExpandedTools((prev) => {
|
||||
@@ -766,10 +833,68 @@ export function SettingsPage() {
|
||||
updateCliTool(toolId, { availableModels });
|
||||
};
|
||||
|
||||
const handleUpdateEnvFile = (toolId: string, envFile: string | undefined) => {
|
||||
updateCliTool(toolId, { envFile });
|
||||
};
|
||||
|
||||
const handleUpdateSettingsFile = (toolId: string, settingsFile: string | undefined) => {
|
||||
updateCliTool(toolId, { settingsFile });
|
||||
};
|
||||
|
||||
// Save tool config to backend (~/.claude/cli-tools.json)
|
||||
const handleSaveToBackend = useCallback(async (toolId: string) => {
|
||||
const config = cliTools[toolId];
|
||||
if (!config) return;
|
||||
|
||||
setSavingTools((prev) => new Set(prev).add(toolId));
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
enabled: config.enabled,
|
||||
primaryModel: config.primaryModel,
|
||||
secondaryModel: config.secondaryModel,
|
||||
tags: config.tags,
|
||||
availableModels: config.availableModels,
|
||||
};
|
||||
|
||||
// Only include the relevant config file field
|
||||
const configFileType = getConfigFileType(toolId);
|
||||
if (configFileType === 'envFile') {
|
||||
body.envFile = config.envFile || null;
|
||||
} else if (configFileType === 'settingsFile') {
|
||||
body.settingsFile = config.settingsFile || null;
|
||||
}
|
||||
|
||||
const res = await fetch(`/api/cli/config/${toolId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
|
||||
// Show success notification via a brief visual indicator
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'fixed bottom-4 right-4 z-50 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2';
|
||||
toast.textContent = formatMessage({ id: 'settings.cliTools.configSaved' });
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 3000);
|
||||
} catch {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'fixed bottom-4 right-4 z-50 bg-red-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2';
|
||||
toast.textContent = formatMessage({ id: 'settings.cliTools.configSaveError' });
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 4000);
|
||||
} finally {
|
||||
setSavingTools((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(toolId);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
}, [cliTools, formatMessage]);
|
||||
|
||||
const handlePreferenceChange = (key: keyof UserPreferences, value: unknown) => {
|
||||
setUserPreferences({ [key]: value });
|
||||
};
|
||||
@@ -859,13 +984,16 @@ export function SettingsPage() {
|
||||
cliTools={cliTools}
|
||||
defaultCliTool={defaultCliTool}
|
||||
expandedTools={expandedTools}
|
||||
savingTools={savingTools}
|
||||
onToggleExpand={toggleToolExpand}
|
||||
onToggleEnabled={handleToggleToolEnabled}
|
||||
onSetDefault={handleSetDefaultTool}
|
||||
onUpdateModel={handleUpdateModel}
|
||||
onUpdateTags={handleUpdateTags}
|
||||
onUpdateAvailableModels={handleUpdateAvailableModels}
|
||||
onUpdateEnvFile={handleUpdateEnvFile}
|
||||
onUpdateSettingsFile={handleUpdateSettingsFile}
|
||||
onSaveToBackend={handleSaveToBackend}
|
||||
formatMessage={formatMessage}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user