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
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const formatTimestampTooltip = (value: string | number) => {
const date = new Date(value);
const offset = new Date().getTimezoneOffset() * -1;
const localDate = addMinutes(date, offset);
return format(localDate, "MMM dd HH:mm:ss.SS aa");
return format(localDate, "MMM dd HH:mm aa");
};

const formatTimestampLabel = (timestamp: string | number | Date) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ const formatValue = (value: string | number): string => {
const statusFamily = Math.floor(Number.parseInt(value) / 100);
switch (statusFamily) {
case 5:
return "5XX (Error)";
return "5xx (Error)";
case 4:
return "4XX (Warning)";
return "4xx (Warning)";
case 2:
return "2XX (Success)";
return "2xx (Success)";
default:
return `${statusFamily}xx`;
}
Expand Down Expand Up @@ -106,6 +106,10 @@ export const ControlCloud = () => {
updateFilters([]);
});

useKeyboardShortcut({ key: "c", meta: true }, () => {
setFocusedIndex(0);
});

const handleRemoveFilter = useCallback(
(id: string) => {
removeFilter(id);
Expand Down Expand Up @@ -192,7 +196,7 @@ export const ControlCloud = () => {

return (
<div
className="px-3 py-2 w-full flex items-center min-h-10 border-b border-gray-4 gap-2 text-xs flex-wrap"
className="px-3 py-2 w-full flex items-start min-h-10 border-b border-gray-4 gap-2 text-xs flex-wrap"
onKeyDown={handleKeyDown}
>
{filters.map((filter, index) => (
Expand All @@ -205,9 +209,12 @@ export const ControlCloud = () => {
index={index}
/>
))}
<div className="flex items-center px-2 py-1 gap-1 ml-auto">
<div className="flex items-center px-2 py-1 gap-2 ml-auto">
<span className="text-gray-9 text-[13px]">Clear filters</span>
<KeyboardButton shortcut="d" modifierKey="⌘" />
<div className="w-px h-4 bg-gray-4" />
<span className="text-gray-9 text-[13px]">Focus filters</span>
<KeyboardButton shortcut="c" modifierKey="⌘" />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { isDisplayProperty, useLogsContext } from "@/app/(app)/logs-v2/context/logs";
import { useKeyboardShortcut } from "@/app/(app)/logs-v2/hooks/use-keyboard-shortcut";
import { KeyboardButton } from "@/components/keyboard-button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
type KeyboardEvent,
type PropsWithChildren,
useCallback,
useEffect,
useState,
} from "react";

const DISPLAY_PROPERTIES = [
{ id: "time", label: "Time" },
{ id: "response_status", label: "Status" },
{ id: "method", label: "Method" },
{ id: "path", label: "Path" },
{ id: "response_body", label: "Response Body" },
{ id: "request_id", label: "Request ID" },
{ id: "host", label: "Host" },
{ id: "request_headers", label: "Request Headers" },
{ id: "request_body", label: "Request Body" },
{ id: "response_headers", label: "Response Headers" },
];

const DisplayPropertyItem = ({
label,
selected,
onClick,
isFocused,
index,
}: {
label: string;
selected: boolean;
onClick: () => void;
isFocused: boolean;
index: number;
}) => (
<div
data-item-index={index}
className={`font-medium text-xs p-1.5 rounded-md hover:bg-gray-4 cursor-pointer whitespace-nowrap
${selected ? "bg-gray-4 text-gray-12" : "text-gray-9"}
${isFocused ? "ring-2 ring-accent-7" : ""}`}
onClick={onClick}
tabIndex={isFocused ? 0 : -1}
role="button"
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onClick();
}
}}
>
{label}
</div>
);

const PopoverHeader = () => (
<div className="flex w-full justify-between items-center px-1 py-1">
<span className="text-gray-9 text-[13px]">Display Properties...</span>
<KeyboardButton shortcut="D" />
</div>
);

export const DisplayPopover = ({ children }: PropsWithChildren) => {
const [open, setOpen] = useState(false);
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
const { displayProperties, toggleDisplayProperty } = useLogsContext();

useKeyboardShortcut("d", () => {
setOpen((prev) => !prev);
if (!open) {
setFocusedIndex(0);
}
});

const handleKeyNavigation = useCallback(
(e: KeyboardEvent) => {
const itemsPerRow = Math.floor(384 / 120); // Approximate width / item width
const totalItems = DISPLAY_PROPERTIES.length;
const currentRow = Math.floor((focusedIndex ?? 0) / itemsPerRow);
const currentCol = (focusedIndex ?? 0) % itemsPerRow;

const moveToIndex = (newIndex: number) => {
e.preventDefault();
setFocusedIndex(Math.max(0, Math.min(newIndex, totalItems - 1)));
};

switch (e.key) {
case "ArrowRight":
case "l": {
moveToIndex((focusedIndex ?? -1) + 1);
break;
}
case "ArrowLeft":
case "h": {
moveToIndex((focusedIndex ?? 1) - 1);
break;
}
case "ArrowDown":
case "j": {
const nextRowIndex = (currentRow + 1) * itemsPerRow + currentCol;
if (nextRowIndex < totalItems) {
moveToIndex(nextRowIndex);
}
break;
}
case "ArrowUp":
case "k": {
const prevRowIndex = (currentRow - 1) * itemsPerRow + currentCol;
if (prevRowIndex >= 0) {
moveToIndex(prevRowIndex);
}
break;
}
case "Enter":
case " ": {
if (focusedIndex !== null) {
const prop = DISPLAY_PROPERTIES[focusedIndex];
if (isDisplayProperty(prop.id)) {
toggleDisplayProperty(prop.id);
}
}
break;
}
}
},
[focusedIndex, toggleDisplayProperty],
);

useEffect(() => {
if (!open) {
setFocusedIndex(null);
}
}, [open]);

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>{children}</PopoverTrigger>
<PopoverContent
className="bg-gray-1 dark:bg-black drop-shadow-2xl p-2 border-gray-6 rounded-lg w-96"
align="start"
onKeyDown={handleKeyNavigation}
>
<div className="flex flex-col gap-2">
<PopoverHeader />
<div className="flex flex-wrap gap-2">
{DISPLAY_PROPERTIES.map((prop, index) => (
<DisplayPropertyItem
key={prop.id}
label={prop.label}
selected={displayProperties.has(prop.id as any)}
onClick={() => {
if (isDisplayProperty(prop.id)) {
toggleDisplayProperty(prop.id);
}
}}
isFocused={focusedIndex === index}
index={index}
/>
))}
</div>
</div>
</PopoverContent>
</Popover>
);
};

export default DisplayPopover;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Sliders } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { cn } from "@unkey/ui/src/lib/utils";
import { DisplayPopover } from "./components/display-popover";

export const LogsDisplay = () => {
return (
<DisplayPopover>
<div className="group">
<Button
variant="ghost"
className={cn("group-data-[state=open]:bg-gray-4 px-2")}
aria-label="Filter logs"
aria-haspopup="true"
title="Press 'F' to toggle filters"
>
<Sliders className="text-accent-9 size-4" />
<span className="text-accent-12 font-medium text-[13px]">Display</span>
</Button>
</div>
</DisplayPopover>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const FilterCheckbox = <TCheckbox extends BaseCheckboxOption>({
>
<div className="flex justify-between items-center">
<label
className="flex items-center gap-2 cursor-pointer"
className="flex items-center gap-[18px] cursor-pointer"
// biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: its okay
role="checkbox"
aria-checked={checkboxes.every((checkbox) => checkbox.checked)}
Expand All @@ -78,18 +78,18 @@ export const FilterCheckbox = <TCheckbox extends BaseCheckboxOption>({
<Checkbox
tabIndex={0}
checked={checkboxes.every((checkbox) => checkbox.checked)}
className="size-[14px] rounded border-gray-4 [&_svg]:size-3"
className="size-4 rounded border-gray-4 [&_svg]:size-3"
onClick={handleSelectAll}
/>
<span className="text-xs text-accent-12 ml-2">
<span className="text-xs text-accent-12">
{checkboxes.every((checkbox) => checkbox.checked) ? "Unselect All" : "Select All"}
</span>
</label>
</div>
{checkboxes.map((checkbox, index) => (
<label
key={checkbox.id}
className="flex gap-4 items-center py-1 cursor-pointer"
className="flex gap-[18px] items-center py-1 cursor-pointer"
// biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: its okay
role="checkbox"
aria-checked={checkbox.checked}
Expand All @@ -98,7 +98,7 @@ export const FilterCheckbox = <TCheckbox extends BaseCheckboxOption>({
<Checkbox
tabIndex={0}
checked={checkbox.checked}
className="size-[14px] rounded border-gray-4 [&_svg]:size-3"
className="size-4 rounded border-gray-4 [&_svg]:size-3"
onClick={() => handleCheckboxChange(index)}
/>
{renderOptionContent ? renderOptionContent(checkbox) : null}
Expand Down
Loading