refactor(ui): move MCP settings to dedicated admin tab and increase prod db timeout
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-12-15 16:28:47 +01:00
parent e8d9d0c686
commit 71d5c825c5
4 changed files with 89 additions and 6 deletions
@@ -6,8 +6,9 @@ import { Button } from "@/components/ui/button";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { useState, useEffect } from "react";
import { Loader2, Server, Save } from "lucide-react";
import { Loader2, Server, Save, Copy } from "lucide-react";
import { useToast } from "@/hooks/use-toast";
import { apiRequest } from "@/lib/queryClient";
export function McpSettingsCard() {
const { t } = useTranslation();
@@ -18,6 +19,38 @@ export function McpSettingsCard() {
mcp_port: "5001" // Default to main app port unless specific override logic is added
});
// Feature 1: Get User for API Key
const { data: user } = useQuery<any>({
queryKey: ['/api/user']
});
// Feature 2: API Key Mutations
const generateApiKeyMutation = useMutation({
mutationFn: async () => {
const res = await apiRequest("POST", "/api/user/apikey", {});
return res.json();
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["/api/user"] });
toast({ title: t('settings.mcp.generated') });
},
});
const revokeApiKeyMutation = useMutation({
mutationFn: async () => {
await apiRequest("DELETE", "/api/user/apikey");
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["/api/user"] });
toast({ title: t('settings.mcp.revoked') });
},
});
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
toast({ title: "Copied!" });
}
const { data: settings, isLoading } = useQuery<any>({
queryKey: ['/api/admin/settings'],
});
@@ -86,6 +119,55 @@ export function McpSettingsCard() {
/>
</div>
{/* Status Indicator */}
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<p className="font-medium">{t('settings.mcp.status')}</p>
</div>
<div className="flex items-center gap-2 text-green-500">
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
<span className="font-medium">{t('settings.mcp.running')}</span>
</div>
</div>
{/* Endpoint URL */}
<div className="space-y-2">
<p className="text-sm font-medium">{t('settings.mcp.url')}</p>
<div className="flex gap-2">
<Input readOnly value={`${window.location.protocol}//${window.location.host}/api/mcp/sse`} />
<Button variant="outline" size="icon" onClick={() => copyToClipboard(`${window.location.protocol}//${window.location.host}/api/mcp/sse`)}>
<Copy className="w-4 h-4" />
</Button>
</div>
</div>
{/* API Key Management */}
<div className="space-y-2">
<p className="text-sm font-medium">{t('settings.mcp.apiKey')}</p>
{user?.apiKey ? (
<div className="flex gap-2">
<Input type="password" readOnly value={user.apiKey} />
<Button variant="outline" size="icon" onClick={() => copyToClipboard(user.apiKey!)}>
<Copy className="w-4 h-4" />
</Button>
<Button variant="destructive" onClick={() => revokeApiKeyMutation.mutate()} disabled={revokeApiKeyMutation.isPending}>
{t('settings.mcp.revoke')}
</Button>
</div>
) : (
<div className="space-y-2">
<p className="text-sm text-muted-foreground">{t('settings.mcp.noKey')}</p>
<Button onClick={() => generateApiKeyMutation.mutate()} disabled={generateApiKeyMutation.isPending}>
{t('settings.mcp.generate')}
</Button>
</div>
)}
</div>
<div className="bg-muted/50 p-3 rounded-lg text-sm text-muted-foreground">
{t('settings.mcp.instructions')}
</div>
<div className="grid gap-2">
<Label htmlFor="mcp_port">Port (Informational)</Label>
<Input