chore: auto-commit (12 archivos)
- app.md - backend/handlers.go - backend/main.go - frontend/src/App.tsx - frontend/src/api.ts - frontend/vite.config.ts - backend/mcp_http.go - backend/mcp_tokens.go - backend/mcp_tokens_handlers.go - backend/migrations/016_mcp_tokens.sql - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Code,
|
||||
CopyButton,
|
||||
Divider,
|
||||
Group,
|
||||
Loader,
|
||||
Modal,
|
||||
Stack,
|
||||
Table,
|
||||
Text,
|
||||
TextInput,
|
||||
Tooltip,
|
||||
} from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { IconCopy, IconCheck, IconTrash } from "@tabler/icons-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import * as api from "../api";
|
||||
import type { MCPToken, MCPTokenCreated } from "../api";
|
||||
import { formatDateTimeShort } from "./format";
|
||||
|
||||
interface Props {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function MCPTokensModal({ opened, onClose }: Props) {
|
||||
const [tokens, setTokens] = useState<MCPToken[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [newName, setNewName] = useState("");
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [justCreated, setJustCreated] = useState<MCPTokenCreated | null>(null);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setTokens(await api.listMCPTokens());
|
||||
} catch (e) {
|
||||
notifications.show({ color: "red", message: (e as Error).message });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (opened) {
|
||||
reload();
|
||||
setJustCreated(null);
|
||||
setNewName("");
|
||||
}
|
||||
}, [opened, reload]);
|
||||
|
||||
const create = async () => {
|
||||
const name = newName.trim() || "default";
|
||||
setCreating(true);
|
||||
try {
|
||||
const t = await api.createMCPToken(name);
|
||||
setJustCreated(t);
|
||||
setNewName("");
|
||||
await reload();
|
||||
} catch (e) {
|
||||
notifications.show({ color: "red", message: (e as Error).message });
|
||||
} finally {
|
||||
setCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const revoke = async (id: string) => {
|
||||
if (!confirm("Revocar este token? Quien lo este usando dejara de tener acceso.")) return;
|
||||
try {
|
||||
await api.revokeMCPToken(id);
|
||||
await reload();
|
||||
} catch (e) {
|
||||
notifications.show({ color: "red", message: (e as Error).message });
|
||||
}
|
||||
};
|
||||
|
||||
const mcpURL = `${window.location.origin}/mcp`;
|
||||
const claudeCmd = justCreated
|
||||
? `claude mcp add kanban --transport http ${mcpURL} --header "Authorization: Bearer ${justCreated.token}"`
|
||||
: "";
|
||||
|
||||
return (
|
||||
<Modal opened={opened} onClose={onClose} title="MCP Tokens" size="lg">
|
||||
<Stack gap="md">
|
||||
<Text size="sm" c="dimmed">
|
||||
Cada token deja conectar un cliente Claude al kanban como tu usuario.
|
||||
El valor solo aparece UNA vez al crearlo. Si lo pierdes, generas otro y revocas el antiguo.
|
||||
</Text>
|
||||
|
||||
<Group align="end">
|
||||
<TextInput
|
||||
label="Nombre del token"
|
||||
placeholder="ej. portatil, sobremesa..."
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.currentTarget.value)}
|
||||
style={{ flex: 1 }}
|
||||
disabled={creating}
|
||||
/>
|
||||
<Button onClick={create} loading={creating}>
|
||||
Generar
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{justCreated && (
|
||||
<Alert color="yellow" title="Copia el token ahora — no se mostrara mas">
|
||||
<Stack gap="xs">
|
||||
<Group gap="xs" align="center">
|
||||
<Code style={{ flex: 1, wordBreak: "break-all" }}>{justCreated.token}</Code>
|
||||
<CopyButton value={justCreated.token}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label={copied ? "Copiado" : "Copiar token"}>
|
||||
<ActionIcon variant="subtle" onClick={copy}>
|
||||
{copied ? <IconCheck size={16} /> : <IconCopy size={16} />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CopyButton>
|
||||
</Group>
|
||||
<Divider />
|
||||
<Text size="xs" c="dimmed">
|
||||
Pega este comando en tu PC para registrar el MCP en Claude Code:
|
||||
</Text>
|
||||
<Group gap="xs" align="center">
|
||||
<Code block style={{ flex: 1 }}>{claudeCmd}</Code>
|
||||
<CopyButton value={claudeCmd}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label={copied ? "Copiado" : "Copiar comando"}>
|
||||
<ActionIcon variant="subtle" onClick={copy}>
|
||||
{copied ? <IconCheck size={16} /> : <IconCopy size={16} />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CopyButton>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Divider label="Tokens activos" labelPosition="left" />
|
||||
|
||||
{loading ? (
|
||||
<Group justify="center" p="md">
|
||||
<Loader size="sm" />
|
||||
</Group>
|
||||
) : tokens.length === 0 ? (
|
||||
<Text size="sm" c="dimmed" ta="center" py="md">
|
||||
Sin tokens. Genera uno arriba.
|
||||
</Text>
|
||||
) : (
|
||||
<Table withTableBorder withColumnBorders verticalSpacing="xs" highlightOnHover>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Nombre</Table.Th>
|
||||
<Table.Th>Creado</Table.Th>
|
||||
<Table.Th>Ultimo uso</Table.Th>
|
||||
<Table.Th w={60} />
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{tokens.map((t) => (
|
||||
<Table.Tr key={t.id}>
|
||||
<Table.Td>{t.name}</Table.Td>
|
||||
<Table.Td>{formatDateTimeShort(t.created_at)}</Table.Td>
|
||||
<Table.Td>
|
||||
{t.last_used_at ? formatDateTimeShort(t.last_used_at) : <Text c="dimmed">nunca</Text>}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Tooltip label="Revocar">
|
||||
<ActionIcon color="red" variant="subtle" onClick={() => revoke(t.id)}>
|
||||
<IconTrash size={14} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
|
||||
<Box>
|
||||
<Text size="xs" c="dimmed">
|
||||
Endpoint MCP: <Code>{mcpURL}</Code>
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user