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
45 changes: 17 additions & 28 deletions ui/app/workspace/mcp-logs/views/mcpLogDetailsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ import { useGetMCPLogByIdQuery } from "@/lib/store";
import type { MCPToolLogEntry } from "@/lib/types/logs";
import { downloadAsJson } from "@/lib/utils/browser-download";
import { addMilliseconds, format, isValid } from "date-fns";
import { ChevronDown, ChevronUp, Download, Loader2, MoreVertical, Trash2 } from "lucide-react";
import { SheetNavigationButtons } from "@/components/sheetNavigationButtons";
import { useSheetNavigation } from "@/hooks/useSheetNavigation";
import { Download, Loader2, MoreVertical, Trash2 } from "lucide-react";
import { useState, type ReactNode } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { toast } from "sonner";

interface MCPLogDetailSheetProps {
Expand Down Expand Up @@ -86,8 +87,12 @@ export function MCPLogDetailSheet({
});

// Keyboard navigation: arrow up/down to navigate between logs
useHotkeys("up", () => onNavigate?.("prev"), { enabled: open && hasPrev, preventDefault: true });
useHotkeys("down", () => onNavigate?.("next"), { enabled: open && hasNext, preventDefault: true });
const { prev: prevKeys, next: nextKeys } = useSheetNavigation({
enabled: open,
hasPrev,
hasNext,
onNavigate: (direction) => onNavigate?.(direction),
});

if (!log) return null;

Expand Down Expand Up @@ -119,30 +124,14 @@ export function MCPLogDetailSheet({
</Badge>
</SheetTitle>
</div>
<div className="flex items-center">
<Button
variant="ghost"
className="size-8"
disabled={!hasPrev}
onClick={() => onNavigate?.("prev")}
aria-label="Previous log"
data-testid="mcp-log-nav-prev"
type="button"
>
<ChevronUp className="size-4" />
</Button>
<Button
variant="ghost"
className="size-8"
disabled={!hasNext}
onClick={() => onNavigate?.("next")}
aria-label="Next log"
data-testid="mcp-log-nav-next"
type="button"
>
<ChevronDown className="size-4" />
</Button>
</div>
<SheetNavigationButtons
hasPrev={hasPrev}
hasNext={hasNext}
onNavigate={(dir) => onNavigate?.(dir)}
prevKeys={prevKeys}
nextKeys={nextKeys}
entityLabel="log"
/>
Comment thread
impoiler marked this conversation as resolved.
Comment thread
impoiler marked this conversation as resolved.
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
<DropdownMenuTrigger asChild>
Expand Down
54 changes: 29 additions & 25 deletions ui/app/workspace/mcp-registry/views/mcpClientSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { SheetNavigationButtons } from "@/components/sheetNavigationButtons";
import { useSheetNavigation } from "@/hooks/useSheetNavigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { ChevronDown, ChevronRight, Info, Plus, Trash2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { OAuth2Authorizer } from "./oauth2Authorizer";

Expand Down Expand Up @@ -212,25 +212,21 @@ export default function MCPClientSheet({ mcpClient, onClose, onSubmitSuccess, on
});
}, [form, mcpClient]);

const handleNavigate = (direction: "prev" | "next") => {
if (form.formState.isDirty || vkConfigsDirty) {
setPendingNavDirection(direction);
} else {
onNavigate?.(direction);
}
};

const confirmNavigation = () => {
if (pendingNavDirection) {
onNavigate?.(pendingNavDirection);
setPendingNavDirection(null);
}
};
const isDirty = form.formState.isDirty || vkConfigsDirty;

const cancelNavigation = () => setPendingNavDirection(null);
const handleNavigate = useCallback(
(direction: "prev" | "next") => {
if (isDirty) {
setPendingNavDirection(direction);
} else {
onNavigate?.(direction);
}
},
[isDirty, onNavigate],
);
Comment thread
impoiler marked this conversation as resolved.

const { prev: prevKeys, next: nextKeys } = useSheetNavigation({
enabled: !!onNavigate,
enabled: !pendingNavDirection,
hasPrev,
hasNext,
onNavigate: handleNavigate,
Expand Down Expand Up @@ -406,7 +402,8 @@ export default function MCPClientSheet({ mcpClient, onClose, onSubmitSuccess, on
};

return (
<Sheet open onOpenChange={(open) => !open && !oauthFlow && onClose()}>
<>
<Sheet open onOpenChange={(open) => !open && !oauthFlow && onClose()}>
<SheetContent className="flex w-full flex-col overflow-x-hidden pt-4 sm:max-w-[60%]">
<SheetHeader className="w-full p-0 px-8 py-4" showCloseButton={false} headerClassName="mb-0 sticky -top-4 bg-card z-10">
<div className="flex w-full items-center justify-between">
Expand Down Expand Up @@ -1310,20 +1307,27 @@ export default function MCPClientSheet({ mcpClient, onClose, onSubmitSuccess, on
isPerUserOauth={oauthFlow.isPerUserOauth}
/>
)}
<AlertDialog open={!!pendingNavDirection} onOpenChange={(open) => !open && cancelNavigation()}>
</Sheet>
<AlertDialog open={!!pendingNavDirection} onOpenChange={(open) => !open && setPendingNavDirection(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Unsaved Changes</AlertDialogTitle>
<AlertDialogDescription>
You have unsaved changes. Navigating away will discard them.
</AlertDialogDescription>
<AlertDialogDescription>You have unsaved changes. Are you sure you want to navigate away? Your changes will be lost.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={cancelNavigation}>Stay</AlertDialogCancel>
<AlertDialogAction onClick={confirmNavigation}>Discard & Navigate</AlertDialogAction>
<AlertDialogCancel onClick={() => setPendingNavDirection(null)}>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
const dir = pendingNavDirection;
setPendingNavDirection(null);
if (dir) onNavigate?.(dir);
}}
>
Discard Changes
</AlertDialogAction>
Comment thread
impoiler marked this conversation as resolved.
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</Sheet>
</>
);
}
Loading