Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 17 additions & 14 deletions e2e/virtual-servers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ test.describe("Virtual Servers page", () => {
await expect(page.getByText("testVS")).toBeVisible();

const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" });
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-emerald-500/);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-active/);
await expect(card.getByLabel("Enabled")).toBeVisible();

await expect(card.getByTestId("tool-count")).toHaveText("2");
Expand Down Expand Up @@ -435,15 +435,15 @@ test.describe("Virtual Servers page", () => {
await page.waitForLoadState("networkidle");

const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" });
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-red-500/);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-inactive/);
await expect(card.getByLabel("Disabled")).toBeVisible();

await page.getByRole("button", { name: "Actions for testVS" }).click();
await page.getByRole("menuitem", { name: "Activate" }).click();

await expect.poll(() => stateRequestCount).toBe(1);
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-emerald-500/);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-active/);
await expect(card.getByLabel("Enabled")).toBeVisible();
await expect(
page.locator("[data-sonner-toast]").filter({ hasText: "testVS activated." }),
Expand Down Expand Up @@ -485,7 +485,7 @@ test.describe("Virtual Servers page", () => {
await dialog.getByRole("button", { name: "Cancel" }).click();
await expect(dialog).toHaveCount(0);
expect(stateRequestCount).toBe(0);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-emerald-500/);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-active/);

await page.getByRole("button", { name: "Actions for testVS" }).click();
await page.getByRole("menuitem", { name: "Deactivate" }).click();
Expand All @@ -496,7 +496,7 @@ test.describe("Virtual Servers page", () => {

await expect.poll(() => stateRequestCount).toBe(1);
await expect(dialog).toHaveCount(0);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-red-500/);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-inactive/);
await expect(card.getByLabel("Disabled")).toBeVisible();
await expect(
page.locator("[data-sonner-toast]").filter({ hasText: "testVS deactivated." }),
Expand Down Expand Up @@ -1116,7 +1116,7 @@ test.describe("Virtual Servers page", () => {
);
});

test("disables the Upload action button on virtual server cards", async ({ page }) => {
test("shows only the actions menu in the virtual server card header", async ({ page }) => {
await page.route("**/servers?*", async (route) => {
await route.fulfill({
status: 200,
Expand All @@ -1128,7 +1128,8 @@ test.describe("Virtual Servers page", () => {
await page.goto(APP.GATEWAYS);
await page.waitForLoadState("networkidle");

await expect(page.getByRole("button", { name: /Open testVS \(coming soon\)/ })).toBeDisabled();
await expect(page.getByRole("button", { name: /Open testVS \(coming soon\)/ })).toHaveCount(0);
await expect(page.getByRole("button", { name: "Actions for testVS" })).toBeVisible();
});

test("navigates to create server UI from the create server card", async ({ page }) => {
Expand Down Expand Up @@ -1183,7 +1184,7 @@ test.describe("Virtual Servers page", () => {

const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" });

await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-red-500/);
await expect(card.getByTestId("status-indicator")).toHaveClass(/bg-tool-status-inactive/);
await expect(card.getByRole("img", { name: "Disabled" })).toBeVisible();

await expect(card.getByText("disabled")).toBeVisible();
Expand Down Expand Up @@ -1220,7 +1221,7 @@ test.describe("Virtual Servers page", () => {
await expect(card2.getByText("private")).toBeVisible();
});

test("places empty virtual servers after servers with components", async ({ page }) => {
test("renders virtual servers in the order the API returned them", async ({ page }) => {
const emptyServer = {
...MOCK_VIRTUAL_SERVER,
id: "empty-server-id",
Expand All @@ -1244,13 +1245,15 @@ test.describe("Virtual Servers page", () => {
await page.goto(APP.GATEWAYS);
await page.waitForLoadState("networkidle");

// Cards are uniform now, so empty servers are no longer sorted to the end
// to keep a full-width row from splitting the grid.
const cards = page.getByTestId("virtual-server-card");
await expect(cards).toHaveCount(2);
await expect(cards.nth(0)).toHaveAttribute("data-server-name", "testVS");
await expect(cards.nth(1)).toHaveAttribute("data-server-name", "peach-thistle-shark");
await expect(cards.nth(1)).toHaveClass(/col-span-full/);
await expect(cards.nth(0)).toHaveAttribute("data-server-name", "peach-thistle-shark");
await expect(cards.nth(1)).toHaveAttribute("data-server-name", "testVS");
await expect(cards.nth(0)).not.toHaveClass(/col-span-full/);
await expect(
cards.nth(1).getByRole("button", { name: "Add sources and components" }),
cards.nth(0).getByRole("button", { name: "Add sources and components" }),
).toBeVisible();
});

Expand Down Expand Up @@ -1322,7 +1325,7 @@ test.describe("Virtual Servers page", () => {

const card = page.getByTestId("virtual-server-card").filter({ hasText: "testVS" });

await expect(card).toHaveClass(/col-span-full/);
await expect(card).not.toHaveClass(/col-span-full/);
await expect(card.getByRole("button", { name: "Add sources and components" })).toBeVisible();
await expect(card.getByTestId("tool-count")).toHaveCount(0);
await expect(card.getByTestId("resource-count")).toHaveCount(0);
Expand Down
8 changes: 4 additions & 4 deletions src/components/gateways/ConnectSourceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function ConnectSourceCard({ onAction }: { onAction: () => void }) {
size="sm"
role="button"
tabIndex={0}
className="min-h-35 cursor-pointer justify-center transition-colors hover:bg-muted/40"
className="cursor-pointer transition-opacity hover:opacity-90"
onClick={onAction}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
Expand All @@ -21,14 +21,14 @@ export function ConnectSourceCard({ onAction }: { onAction: () => void }) {
>
<CardHeader className="gap-3">
<div className="flex items-center gap-3">
<span className="flex size-6 items-center justify-center rounded-sm bg-primary text-primary-foreground">
<Plus className="size-4" />
<span className="flex size-6 shrink-0 items-center justify-center rounded bg-tool-add-icon-bg text-tool-add-icon-fg shadow-sm">
<Plus className="size-3.5" />
</span>
<CardTitle>{intl.formatMessage({ id: "gateways.createServer.card.title" })}</CardTitle>
</div>
</CardHeader>
<CardContent>
<CardDescription className="text-[13px] leading-4">
<CardDescription className="leading-relaxed">
{intl.formatMessage({ id: "gateways.createServer.card.description" })}
</CardDescription>
</CardContent>
Expand Down
8 changes: 4 additions & 4 deletions src/components/gateways/GatewayCards.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ describe("VirtualServerCard", () => {
expect(screen.getByText("My Test Server")).toBeTruthy();
});

it("shows green status indicator for enabled server", () => {
it("shows an active status indicator for enabled server", () => {
renderWithProviders(<VirtualServerCard server={mockServer} onViewDetails={vi.fn()} />);
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-emerald-500");
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-active");
});

it("shows red status indicator for disabled server", () => {
it("shows an inactive status indicator for disabled server", () => {
renderWithProviders(<VirtualServerCard server={emptyServer} onViewDetails={vi.fn()} />);
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-red-500");
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-inactive");
});

it("calls onViewDetails when card is clicked", () => {
Expand Down
13 changes: 7 additions & 6 deletions src/components/gateways/VirtualServerCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,19 @@ describe("VirtualServerCard", () => {
expect(onViewDetails).toHaveBeenCalledWith(expect.objectContaining({ id: "vs-1" }));
});

it("shows a green enabled indicator for enabled server", () => {
it("shows an active enabled indicator for enabled server", () => {
renderWithProviders(
<VirtualServerCard server={makeServer({ enabled: true })} onViewDetails={vi.fn()} />,
);
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-emerald-500");
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-active");
expect(screen.getByRole("img", { name: "Enabled" })).toBeTruthy();
});

it("shows a red disabled indicator for disabled server", () => {
it("shows an inactive disabled indicator for disabled server", () => {
renderWithProviders(
<VirtualServerCard server={makeServer({ enabled: false })} onViewDetails={vi.fn()} />,
);
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-red-500");
expect(screen.getByTestId("status-indicator")).toHaveClass("bg-tool-status-inactive");
expect(screen.getByRole("img", { name: "Disabled" })).toBeTruthy();
});

Expand Down Expand Up @@ -231,13 +231,14 @@ describe("VirtualServerCard", () => {
expect(document.querySelector(".custom-class")).toBeTruthy();
});

it("shows upload button for non-empty server", () => {
it("shows only the actions menu in the card header", () => {
renderWithProviders(
<VirtualServerCard
server={makeServer({ associatedTools: ["t1"] })}
onViewDetails={vi.fn()}
/>,
);
expect(screen.getByRole("button", { name: /Open.*coming soon/i })).toBeTruthy();
expect(screen.queryByRole("button", { name: /Open.*coming soon/i })).toBeNull();
expect(screen.getByRole("button", { name: /Actions for/i })).toBeTruthy();
});
});
28 changes: 7 additions & 21 deletions src/components/gateways/VirtualServerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
import type { VirtualServer } from "@/types/server";
import { Box, EllipsisVertical, MessageSquareCode, Plus, Upload, Wrench } from "lucide-react";
import { Box, EllipsisVertical, MessageSquareCode, Plus, Wrench } from "lucide-react";
import { useIntl } from "react-intl";

export function VirtualServerCard({
Expand Down Expand Up @@ -52,26 +52,22 @@ export function VirtualServerCard({
return (
<Card
size="sm"
className={cn(
isEmptyComposition ? "min-h-29 justify-center" : "min-h-35 justify-between",
"cursor-pointer transition-colors hover:bg-accent/50",
className,
)}
className={cn("cursor-pointer transition-colors hover:bg-accent/50", className)}
data-testid="virtual-server-card"
data-server-name={server.name}
onClick={() => onViewDetails(server)}
>
<CardHeader className="gap-3">
<div className="flex items-center gap-3">
<span className="flex size-6 shrink-0 items-center justify-center rounded-sm bg-primary text-primary-foreground">
<MCPIcon className="size-4 [&_path]:fill-current" />
<span className="flex size-6 shrink-0 items-center justify-center rounded bg-server-icon-bg text-black">
<MCPIcon className="size-3.5 [&_path]:fill-current" />
</span>
<div className="flex min-w-0 flex-1 items-center gap-2">
<CardTitle className="truncate">{server.name}</CardTitle>
<CardTitle className="truncate font-semibold">{server.name}</CardTitle>
<span
className={cn(
"size-1.5 rounded-full",
server.enabled ? "bg-emerald-500" : "bg-red-500",
"size-1.5 shrink-0 rounded-full",
server.enabled ? "bg-tool-status-active" : "bg-tool-status-inactive",
)}
data-testid="status-indicator"
role="img"
Expand All @@ -81,16 +77,6 @@ export function VirtualServerCard({
/>
</div>
<div className="flex shrink-0 items-center gap-1">
{!isEmptyComposition && (
<Button
variant="ghost"
size="icon-xs"
aria-label={`Open ${server.name} (coming soon)`}
disabled
>
<Upload className="size-4" />
</Button>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
Expand Down
10 changes: 7 additions & 3 deletions src/components/gateways/VirtualServerDetailsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ export function VirtualServerDetailsPanel({
aria-hidden="true"
onClick={onClose}
className={cn(
"absolute inset-0 z-10 bg-black/10 transition-opacity duration-150 supports-backdrop-filter:backdrop-blur-xs",
"absolute inset-0 z-10 bg-black/50 transition-opacity duration-150 supports-backdrop-filter:backdrop-blur-xs",
"data-[state=open]:opacity-100 data-[state=closed]:opacity-0 data-[state=closed]:pointer-events-none",
)}
/>
Expand Down Expand Up @@ -637,7 +637,7 @@ export function VirtualServerDetailsPanel({
</div>
</div>

<aside className="relative border-t border-border bg-background lg:border-l lg:border-t-0">
<aside className="relative border-t border-border bg-popover lg:border-l lg:border-t-0">
<Button
ref={closeButtonRef}
type="button"
Expand All @@ -658,7 +658,11 @@ export function VirtualServerDetailsPanel({
<dl className="space-y-4">
<DetailRow label={intl.formatMessage({ id: "gateways.details.status" })}>
<span className="flex items-center gap-2">
<Activity className="size-3.5 text-emerald-400" />
<Activity
className={`size-3.5 ${
server.enabled ? "text-tool-status-active" : "text-tool-status-inactive"
}`}
/>
{server.enabled
? intl.formatMessage({ id: "gateways.details.status.active" })
: intl.formatMessage({ id: "gateways.details.status.inactive" })}
Expand Down
14 changes: 7 additions & 7 deletions src/components/prompts/PromptDefinitionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ export function PromptDefinitionTable({
const intl = useIntl();

return (
<Table>
<Table className="min-w-full border-separate border-spacing-y-1.5">
<TableHeader>
<TableRow className="hover:bg-transparent">
<TableHead className="h-9 w-[30%] px-4 py-2.5 text-xs font-medium">
<TableRow className="border-none hover:bg-transparent">
<TableHead className="h-9 w-[30%] border-b border-border px-4 py-2.5 text-xs font-medium">
{intl.formatMessage({ id: "prompts.details.label.name" })}
</TableHead>
<TableHead className="h-9 px-4 py-2.5 text-xs font-medium">
<TableHead className="h-9 border-b border-border px-4 py-2.5 text-xs font-medium">
{intl.formatMessage({ id: "prompts.details.label.promptId" })}
</TableHead>
<TableHead className="h-9 w-[40px] px-4 py-2.5" />
<TableHead className="h-9 w-[40px] border-b border-border px-4 py-2.5" />
</TableRow>
</TableHeader>
<TableBody className="[&_tr]:border-0">
<TableBody>
{prompts.map((prompt) => (
<TableRow
key={prompt.id}
Expand All @@ -79,7 +79,7 @@ export function PromptDefinitionTable({
onSelectPrompt(prompt);
}
}}
className="cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-inset"
className="cursor-pointer border-0 bg-neutral-50 hover:bg-neutral-100 data-[state=selected]:bg-neutral-200 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-inset dark:bg-neutral-800/50 dark:hover:bg-neutral-700/60 dark:data-[state=selected]:bg-neutral-700 [&>td:first-child]:rounded-l-lg [&>td:last-child]:rounded-r-lg"
>
<TableCell className="px-4 py-3 text-sm text-foreground">
<span className="line-clamp-1">{prompt.displayName || prompt.name}</span>
Expand Down
12 changes: 7 additions & 5 deletions src/components/prompts/PromptDetailsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function PromptDetailsPanel({
aria-hidden="true"
onClick={onClose}
className={cn(
"absolute inset-0 z-10 bg-black/10 transition-opacity duration-150 supports-backdrop-filter:backdrop-blur-xs",
"absolute inset-0 z-10 bg-black/50 transition-opacity duration-150 supports-backdrop-filter:backdrop-blur-xs",
"data-[state=open]:opacity-100 data-[state=closed]:opacity-0 data-[state=closed]:pointer-events-none",
)}
/>
Expand All @@ -161,13 +161,13 @@ export function PromptDetailsPanel({
)}
>
<div className="grid min-h-0 flex-1 grid-cols-1 overflow-hidden lg:grid-cols-[minmax(0,1fr)_320px]">
<div className="min-w-0 overflow-y-auto bg-background px-6 py-8 dark:bg-neutral-900 lg:px-12">
<div className="min-w-0 overflow-y-auto px-6 py-8 lg:px-12">
<h2 id={headingId} className="sr-only">
{intl.formatMessage({ id: "prompts.details.srHeading" }, { title })}
</h2>

<div className="flex min-w-0 items-start gap-3">
<span className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-sm bg-emerald-300 text-neutral-950">
<span className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-sm bg-prompt-icon-bg text-neutral-950">
<MessageSquareCode className="size-4" />
</span>
<span aria-hidden="true" className="truncate text-xl font-semibold text-foreground">
Expand Down Expand Up @@ -263,7 +263,7 @@ export function PromptDetailsPanel({
</Tabs>
</div>

<aside className="relative overflow-y-auto border-t border-border bg-background lg:border-l lg:border-t-0 dark:bg-neutral-900">
<aside className="relative overflow-y-auto border-t border-border lg:border-l lg:border-t-0">
<Button
ref={closeButtonRef}
type="button"
Expand All @@ -288,7 +288,9 @@ export function PromptDetailsPanel({
<span className="flex items-center gap-2">
<Activity
className={`size-3.5 ${
selected.enabled ? "text-emerald-400" : "text-gray-400"
selected.enabled
? "text-tool-status-active"
: "text-tool-status-inactive"
}`}
/>
{selected.enabled
Expand Down
Loading
Loading