mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat: CCW Dashboard 增强 - 停止命令、浏览器修复和MCP多源配置
- 新增 ccw stop 命令支持优雅停止和强制终止 (--force) - 修复 ccw view 服务器检测时浏览器无法打开的问题 - MCP 配置现在从多个源读取: - ~/.claude.json (项目级) - ~/.claude/settings.json 和 settings.local.json (全局) - 各工作空间的 .claude/settings.json (工作空间级) - 新增全局 MCP 服务器显示区域 - 修复路径选择模态框样式问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
// ========== MCP State ==========
|
||||
let mcpConfig = null;
|
||||
let mcpAllProjects = {};
|
||||
let mcpGlobalServers = {};
|
||||
let mcpCurrentProjectServers = {};
|
||||
let mcpCreateMode = 'form'; // 'form' or 'json'
|
||||
|
||||
@@ -31,6 +32,7 @@ async function loadMcpConfig() {
|
||||
const data = await response.json();
|
||||
mcpConfig = data;
|
||||
mcpAllProjects = data.projects || {};
|
||||
mcpGlobalServers = data.globalServers || {};
|
||||
|
||||
// Get current project servers
|
||||
const currentPath = projectPath.replace(/\//g, '\\');
|
||||
@@ -150,6 +152,15 @@ function updateMcpBadge() {
|
||||
function getAllAvailableMcpServers() {
|
||||
const allServers = {};
|
||||
|
||||
// Collect global servers first
|
||||
for (const [name, serverConfig] of Object.entries(mcpGlobalServers)) {
|
||||
allServers[name] = {
|
||||
config: serverConfig,
|
||||
usedIn: [],
|
||||
isGlobal: true
|
||||
};
|
||||
}
|
||||
|
||||
// Collect servers from all projects
|
||||
for (const [path, config] of Object.entries(mcpAllProjects)) {
|
||||
const servers = config.mcpServers || {};
|
||||
@@ -157,7 +168,8 @@ function getAllAvailableMcpServers() {
|
||||
if (!allServers[name]) {
|
||||
allServers[name] = {
|
||||
config: serverConfig,
|
||||
usedIn: []
|
||||
usedIn: [],
|
||||
isGlobal: false
|
||||
};
|
||||
}
|
||||
allServers[name].usedIn.push(path);
|
||||
|
||||
@@ -26,8 +26,12 @@ async function renderMcpManager() {
|
||||
|
||||
// Separate current project servers and available servers
|
||||
const currentProjectServerNames = Object.keys(projectServers);
|
||||
const otherAvailableServers = Object.entries(allAvailableServers)
|
||||
|
||||
// Separate global servers and project servers that are not in current project
|
||||
const globalServerEntries = Object.entries(mcpGlobalServers)
|
||||
.filter(([name]) => !currentProjectServerNames.includes(name));
|
||||
const otherProjectServers = Object.entries(allAvailableServers)
|
||||
.filter(([name, info]) => !currentProjectServerNames.includes(name) && !info.isGlobal);
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="mcp-manager">
|
||||
@@ -61,20 +65,39 @@ async function renderMcpManager() {
|
||||
`}
|
||||
</div>
|
||||
|
||||
<!-- Global MCP Servers -->
|
||||
${globalServerEntries.length > 0 ? `
|
||||
<div class="mcp-section mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">🌐</span>
|
||||
<h3 class="text-lg font-semibold text-foreground">Global MCP Servers</h3>
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">${globalServerEntries.length} servers from ~/.claude/settings</span>
|
||||
</div>
|
||||
|
||||
<div class="mcp-server-grid grid gap-3">
|
||||
${globalServerEntries.map(([serverName, serverConfig]) => {
|
||||
return renderGlobalServerCard(serverName, serverConfig);
|
||||
}).join('')}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Available MCP Servers from Other Projects -->
|
||||
<div class="mcp-section">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-foreground">Available from Other Projects</h3>
|
||||
<span class="text-sm text-muted-foreground">${otherAvailableServers.length} servers available</span>
|
||||
<span class="text-sm text-muted-foreground">${otherProjectServers.length} servers available</span>
|
||||
</div>
|
||||
|
||||
${otherAvailableServers.length === 0 ? `
|
||||
${otherProjectServers.length === 0 ? `
|
||||
<div class="mcp-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
||||
<p class="text-muted-foreground">No additional MCP servers found in other projects</p>
|
||||
</div>
|
||||
` : `
|
||||
<div class="mcp-server-grid grid gap-3">
|
||||
${otherAvailableServers.map(([serverName, serverInfo]) => {
|
||||
${otherProjectServers.map(([serverName, serverInfo]) => {
|
||||
return renderAvailableServerCard(serverName, serverInfo);
|
||||
}).join('')}
|
||||
</div>
|
||||
@@ -240,6 +263,52 @@ function renderAvailableServerCard(serverName, serverInfo) {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderGlobalServerCard(serverName, serverConfig) {
|
||||
const command = serverConfig.command || 'N/A';
|
||||
const args = serverConfig.args || [];
|
||||
const hasEnv = serverConfig.env && Object.keys(serverConfig.env).length > 0;
|
||||
|
||||
return `
|
||||
<div class="mcp-server-card mcp-server-global bg-card border border-primary/30 rounded-lg p-4 hover:shadow-md transition-all">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xl">🌐</span>
|
||||
<h4 class="font-semibold text-foreground">${escapeHtml(serverName)}</h4>
|
||||
<span class="text-xs px-2 py-0.5 bg-primary/10 text-primary rounded-full">Global</span>
|
||||
</div>
|
||||
<button class="px-3 py-1 text-xs bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
|
||||
data-server-name="${escapeHtml(serverName)}"
|
||||
data-server-config='${JSON.stringify(serverConfig).replace(/'/g, "'")}'
|
||||
data-action="add">
|
||||
Add to Project
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mcp-server-details text-sm space-y-1">
|
||||
<div class="flex items-center gap-2 text-muted-foreground">
|
||||
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">cmd</span>
|
||||
<span class="truncate" title="${escapeHtml(command)}">${escapeHtml(command)}</span>
|
||||
</div>
|
||||
${args.length > 0 ? `
|
||||
<div class="flex items-start gap-2 text-muted-foreground">
|
||||
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">args</span>
|
||||
<span class="text-xs font-mono truncate" title="${escapeHtml(args.join(' '))}">${escapeHtml(args.slice(0, 3).join(' '))}${args.length > 3 ? '...' : ''}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
${hasEnv ? `
|
||||
<div class="flex items-center gap-2 text-muted-foreground">
|
||||
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">env</span>
|
||||
<span class="text-xs">${Object.keys(serverConfig.env).length} variables</span>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="flex items-center gap-2 text-muted-foreground mt-1">
|
||||
<span class="text-xs italic">Available to all projects from ~/.claude/settings</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function attachMcpEventListeners() {
|
||||
// Toggle switches
|
||||
document.querySelectorAll('.mcp-server-card input[data-action="toggle"]').forEach(input => {
|
||||
|
||||
@@ -8112,3 +8112,76 @@ code.ctx-meta-chip-value {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Path Input Group */
|
||||
.path-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.path-input-group label {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.path-input-group input {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 0.625rem 0.875rem;
|
||||
background: hsl(var(--background));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-family: var(--font-mono);
|
||||
color: hsl(var(--foreground));
|
||||
outline: none;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.path-input-group input:focus {
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.1);
|
||||
}
|
||||
|
||||
.path-input-group input::placeholder {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.path-go-btn {
|
||||
padding: 0.625rem 1.25rem;
|
||||
background: hsl(var(--primary));
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.path-go-btn:hover {
|
||||
background: hsl(var(--primary) / 0.9);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.path-go-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Selected Folder Display */
|
||||
.selected-folder {
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.selected-folder strong {
|
||||
font-size: 1rem;
|
||||
color: hsl(var(--foreground));
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user