|
|
@@ -16,9 +16,13 @@ import {
|
|
|
VSCodePanelTab,
|
|
|
VSCodePanelView,
|
|
|
} from "@vscode/webview-ui-toolkit/react"
|
|
|
-import { useCallback, useState } from "react"
|
|
|
+import { RefreshCcwIcon, Trash2Icon } from "lucide-react"
|
|
|
+import { useState } from "react"
|
|
|
import DangerButton from "@/components/common/DangerButton"
|
|
|
+import { Button } from "@/components/ui/button"
|
|
|
+import { Switch } from "@/components/ui/switch"
|
|
|
import { useExtensionState } from "@/context/ExtensionStateContext"
|
|
|
+import { cn } from "@/lib/utils"
|
|
|
import { McpServiceClient } from "@/services/grpc-client"
|
|
|
import { getMcpServerDisplayName } from "@/utils/mcp"
|
|
|
import McpResourceRow from "./McpResourceRow"
|
|
|
@@ -53,17 +57,6 @@ const ServerRow = ({
|
|
|
const [isDeleting, setIsDeleting] = useState(false)
|
|
|
const [isRestarting, setIsRestarting] = useState(false)
|
|
|
|
|
|
- const getStatusColor = useCallback((status: McpServer["status"]) => {
|
|
|
- switch (status) {
|
|
|
- case "connected":
|
|
|
- return "var(--vscode-testing-iconPassed)"
|
|
|
- case "connecting":
|
|
|
- return "var(--vscode-charts-yellow)"
|
|
|
- case "disconnected":
|
|
|
- return "var(--vscode-testing-iconFailed)"
|
|
|
- }
|
|
|
- }, [])
|
|
|
-
|
|
|
const handleRowClick = () => {
|
|
|
if (!server.error && isExpandable) {
|
|
|
setIsExpanded(!isExpanded)
|
|
|
@@ -173,111 +166,54 @@ const ServerRow = ({
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
- <div style={{ marginBottom: "10px" }}>
|
|
|
- <div
|
|
|
- onClick={handleRowClick}
|
|
|
- style={{
|
|
|
- display: "flex",
|
|
|
- alignItems: "center",
|
|
|
- padding: "8px",
|
|
|
- background: "var(--vscode-textCodeBlock-background)",
|
|
|
-
|
|
|
- cursor: server.error ? "default" : isExpandable ? "pointer" : "default",
|
|
|
- borderRadius: isExpanded || server.error ? "4px 4px 0 0" : "4px",
|
|
|
- opacity: server.disabled ? 0.6 : 1,
|
|
|
- }}>
|
|
|
+ <div className="mb-2.5">
|
|
|
+ <div className="flex bg-code-block-background p-2 gap-4 items-center" onClick={handleRowClick}>
|
|
|
{!server.error && isExpandable && (
|
|
|
- <span className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`} style={{ marginRight: "8px" }} />
|
|
|
+ <span
|
|
|
+ className={cn("mr-2 codicon", {
|
|
|
+ "codicon-chevron-right": !isExpanded,
|
|
|
+ "codicon-chevron-down": isExpanded,
|
|
|
+ })}
|
|
|
+ />
|
|
|
)}
|
|
|
- <span
|
|
|
- style={{
|
|
|
- flex: 1,
|
|
|
- overflow: "hidden",
|
|
|
- wordBreak: "break-all",
|
|
|
- whiteSpace: "normal",
|
|
|
- display: "flex",
|
|
|
- alignItems: "center",
|
|
|
- marginRight: "4px",
|
|
|
- }}>
|
|
|
+ <span className="flex-1 overflow-hidden break-all whitespace-normal flex items-center">
|
|
|
{getMcpServerDisplayName(server.name, mcpMarketplaceCatalog)}
|
|
|
</span>
|
|
|
{/* Collapsed view controls */}
|
|
|
{!server.error && (
|
|
|
- <div style={{ display: "flex", alignItems: "center", gap: "4px", marginLeft: "8px" }}>
|
|
|
- <VSCodeButton
|
|
|
- appearance="icon"
|
|
|
- disabled={server.status === "connecting" || isRestarting}
|
|
|
- onClick={(e) => {
|
|
|
- e.stopPropagation()
|
|
|
- handleRestart()
|
|
|
- }}
|
|
|
- title="Restart Server">
|
|
|
- <span className="codicon codicon-sync"></span>
|
|
|
- </VSCodeButton>
|
|
|
- {hasTrashIcon && (
|
|
|
- <VSCodeButton
|
|
|
- appearance="icon"
|
|
|
- disabled={isDeleting}
|
|
|
- onClick={(e) => {
|
|
|
- e.stopPropagation()
|
|
|
- handleDelete()
|
|
|
- }}
|
|
|
- title="Delete Server">
|
|
|
- <span className="codicon codicon-trash"></span>
|
|
|
- </VSCodeButton>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- {/* Toggle Switch */}
|
|
|
- <div onClick={(e) => e.stopPropagation()} style={{ display: "flex", alignItems: "center", marginLeft: "8px" }}>
|
|
|
- <div
|
|
|
- aria-checked={!server.disabled}
|
|
|
- onClick={() => {
|
|
|
- handleToggleMcpServer()
|
|
|
+ <Button
|
|
|
+ disabled={server.status === "connecting" || isRestarting || server.disabled}
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ handleRestart()
|
|
|
}}
|
|
|
- onKeyDown={(e) => {
|
|
|
- if (e.key === "Enter" || e.key === " ") {
|
|
|
- e.preventDefault()
|
|
|
- handleToggleMcpServer()
|
|
|
- }
|
|
|
- }}
|
|
|
- role="switch"
|
|
|
- style={{
|
|
|
- width: "20px",
|
|
|
- height: "10px",
|
|
|
- backgroundColor: server.disabled
|
|
|
- ? "var(--vscode-titleBar-inactiveForeground)"
|
|
|
- : "var(--vscode-testing-iconPassed)",
|
|
|
- borderRadius: "5px",
|
|
|
- position: "relative",
|
|
|
- cursor: "pointer",
|
|
|
- transition: "background-color 0.2s",
|
|
|
- opacity: server.disabled ? 0.5 : 0.9,
|
|
|
+ size="icon"
|
|
|
+ title="Restart Server"
|
|
|
+ variant="icon">
|
|
|
+ <RefreshCcwIcon />
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ {!server.error && hasTrashIcon && (
|
|
|
+ <Button
|
|
|
+ disabled={isDeleting}
|
|
|
+ onClick={(e) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ handleDelete()
|
|
|
}}
|
|
|
- tabIndex={0}>
|
|
|
- <div
|
|
|
- style={{
|
|
|
- width: "6px",
|
|
|
- height: "6px",
|
|
|
- backgroundColor: "white",
|
|
|
- border: "1px solid color-mix(in srgb, #666666 65%, transparent)",
|
|
|
- borderRadius: "50%",
|
|
|
- position: "absolute",
|
|
|
- top: "1px",
|
|
|
- left: server.disabled ? "2px" : "12px",
|
|
|
- transition: "left 0.2s",
|
|
|
- }}
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ size="icon"
|
|
|
+ title="Delete Server"
|
|
|
+ variant="icon">
|
|
|
+ <Trash2Icon />
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ {/* Toggle Switch */}
|
|
|
+ <Switch checked={!server.disabled} key={server.name} onClick={handleToggleMcpServer} />
|
|
|
<div
|
|
|
- style={{
|
|
|
- width: "8px",
|
|
|
- height: "8px",
|
|
|
- borderRadius: "50%",
|
|
|
- background: getStatusColor(server.status),
|
|
|
- marginLeft: "8px",
|
|
|
- }}
|
|
|
+ className={cn("h-2 w-2 ml-0.5 rounded-full", {
|
|
|
+ "bg-success": server.status === "connected",
|
|
|
+ "bg-warning": server.status === "connecting",
|
|
|
+ "bg-error": server.status === "disconnected",
|
|
|
+ })}
|
|
|
/>
|
|
|
</div>
|
|
|
|