feat: opt web ux
This commit is contained in:
@@ -1,20 +1,19 @@
|
||||
import { useState } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { apiFetch } from '../../lib/api';
|
||||
import ConfirmDialog from '../../components/ConfirmDialog';
|
||||
import { useLayoutContext } from '../Layout';
|
||||
|
||||
type Project = { id: string; name: string };
|
||||
|
||||
export default function McpIntegration({ project }: { project: Project }) {
|
||||
const [apiKey, setApiKey] = useState<string | null>(null);
|
||||
const [showRotateConfirm, setShowRotateConfirm] = useState(false);
|
||||
const [copied, setCopied] = useState<string | null>(null);
|
||||
const { onOpenSettings } = useLayoutContext();
|
||||
const mcpHost = window.location.hostname;
|
||||
const mcpUrl = `http://${mcpHost}:3001/mcp/${project.id}`;
|
||||
|
||||
const rotateMutation = useMutation({
|
||||
mutationFn: () => apiFetch<{ apiKey: string }>(`/projects/${project.id}/api-key/rotate`, { method: 'POST' }),
|
||||
onSuccess: (data) => { setApiKey(data.apiKey); setShowRotateConfirm(false); },
|
||||
const { data: keyStatus } = useQuery({
|
||||
queryKey: ['api-key-status'],
|
||||
queryFn: () => apiFetch<{ hasKey: boolean; prefix: string | null }>('/auth/api-key/status'),
|
||||
});
|
||||
|
||||
const serverName = project.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
||||
@@ -23,7 +22,7 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
[serverName]: {
|
||||
type: 'http',
|
||||
url: mcpUrl,
|
||||
headers: { Authorization: `Bearer ${apiKey || '<your-api-key>'}` },
|
||||
headers: { Authorization: 'Bearer <your-api-key>' },
|
||||
},
|
||||
},
|
||||
}, null, 2);
|
||||
@@ -52,37 +51,6 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* API Key */}
|
||||
<section>
|
||||
<p className="section-title">API Key</p>
|
||||
<p className="section-desc mb-3">Used to authenticate MCP requests. Each project has its own key.</p>
|
||||
{apiKey ? (
|
||||
<div className="p-4 rounded-lg bg-warning-muted border border-warning/20 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-4 h-4 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" /></svg>
|
||||
<p className="text-xs font-medium text-warning">Save this key — it won't be shown again.</p>
|
||||
</div>
|
||||
<button onClick={() => copyText(apiKey, 'key')} className="text-xs font-medium text-warning hover:underline">
|
||||
{copied === 'key' ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
<code className="block text-xs break-all text-text-primary font-mono bg-bg-primary/50 rounded p-2">{apiKey}</code>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-3 px-3.5 py-2.5 rounded-lg bg-bg-tertiary border border-border-muted">
|
||||
<svg className="w-4 h-4 text-text-muted shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>
|
||||
<p className="text-[13px] text-text-muted">API key is hidden. Rotate to generate a new one.</p>
|
||||
</div>
|
||||
)}
|
||||
<button onClick={() => setShowRotateConfirm(true)} className="btn-outline mt-3">
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
Rotate API Key
|
||||
</button>
|
||||
</section>
|
||||
|
||||
{/* Config snippet */}
|
||||
<section>
|
||||
<p className="section-title">Configuration for Claude Code / Cursor</p>
|
||||
@@ -93,6 +61,30 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
{copied === 'config' ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* API Key guidance */}
|
||||
{keyStatus && (
|
||||
<div className="mt-3">
|
||||
{keyStatus.hasKey ? (
|
||||
<div className="flex items-center gap-2 px-3.5 py-2.5 rounded-lg bg-bg-tertiary border border-border-muted">
|
||||
<svg className="w-4 h-4 text-success shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M5 13l4 4L19 7" /></svg>
|
||||
<p className="text-[13px] text-text-secondary">
|
||||
API key generated. Copy it from{' '}
|
||||
<button onClick={onOpenSettings} className="text-accent hover:underline font-medium">Settings</button>
|
||||
{' '}and replace <code className="text-xs font-mono bg-bg-inset px-1 py-0.5 rounded"><your-api-key></code> above.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-3 p-3.5 rounded-lg bg-warning-muted border border-warning/20">
|
||||
<svg className="w-4 h-4 text-warning shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}><path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" /></svg>
|
||||
<p className="text-[13px] text-text-secondary flex-1">You need to generate an API key before using MCP.</p>
|
||||
<button onClick={onOpenSettings} className="btn-primary shrink-0 text-[13px] py-1.5 px-3">
|
||||
Open Settings
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Available tools */}
|
||||
@@ -117,16 +109,6 @@ export default function McpIntegration({ project }: { project: Project }) {
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ConfirmDialog
|
||||
open={showRotateConfirm}
|
||||
onCancel={() => setShowRotateConfirm(false)}
|
||||
onConfirm={() => rotateMutation.mutate()}
|
||||
title="Rotate API Key"
|
||||
description="This will invalidate the current API key immediately. Any MCP clients using the old key will stop working."
|
||||
confirmText="Rotate Key"
|
||||
variant="warning"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user