From 6db9c99080438a10049cae3106c1fd3277442b4d Mon Sep 17 00:00:00 2001 From: Nicholas Penree Date: Mon, 16 Dec 2024 19:12:33 -0500 Subject: [PATCH] feat(logs): add number of lines filter --- .../dashboard/docker/logs/docker-logs-id.tsx | 19 +- .../docker/logs/line-count-filter.tsx | 173 ++++++++++++++++++ 2 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 apps/dokploy/components/dashboard/docker/logs/line-count-filter.tsx diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx index 110e0faa..9c3e2dda 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -3,6 +3,7 @@ import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { Download as DownloadIcon, Loader2 } from "lucide-react"; import React, { useEffect, useRef } from "react"; +import { LineCountFilter } from "./line-count-filter"; import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter"; import { StatusLogsFilter } from "./status-logs-filter"; import { TerminalLine } from "./terminal-line"; @@ -76,16 +77,16 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { setSearch(e.target.value || ""); }; - const handleLines = (e: React.ChangeEvent) => { + const handleLines = (lines: number) => { setRawLogs(""); setFilteredLogs([]); - setLines(Number(e.target.value) || 1); + setLines(lines); }; const handleSince = (value: TimeFilter) => { - setRawLogs(""); - setFilteredLogs([]); - setSince(value); + setRawLogs(""); + setFilteredLogs([]); + setSince(value); }; useEffect(() => { @@ -223,13 +224,7 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => {
- + void; + title?: string; +} + +export function LineCountFilter({ + value, + onValueChange, + title = "Limit to", +}: LineCountFilterProps) { + const [open, setOpen] = React.useState(false); + const [inputValue, setInputValue] = React.useState(""); + const pendingValueRef = useRef(null); + + const isPresetValue = lineCountOptions.some( + (option) => option.value === value, + ); + + const debouncedValueChange = useCallback( + debounce((numValue: number) => { + if (numValue > 0 && numValue !== value) { + onValueChange(numValue); + pendingValueRef.current = null; + } + }, 500), + [onValueChange, value], + ); + + const handleInputChange = (input: string) => { + setInputValue(input); + + // Extract numbers from input and convert + const numValue = Number.parseInt(input.replace(/[^0-9]/g, "")); + if (!Number.isNaN(numValue)) { + pendingValueRef.current = numValue; + debouncedValueChange(numValue); + } + }; + + const handleSelect = (selectedValue: string) => { + const preset = lineCountOptions.find((opt) => opt.label === selectedValue); + if (preset) { + if (preset.value !== value) { + onValueChange(preset.value); + } + setInputValue(""); + setOpen(false); + return; + } + + const numValue = Number.parseInt(selectedValue); + if ( + !Number.isNaN(numValue) && + numValue > 0 && + numValue !== value && + numValue !== pendingValueRef.current + ) { + onValueChange(numValue); + setInputValue(""); + setOpen(false); + } + }; + + React.useEffect(() => { + return () => { + debouncedValueChange.cancel(); + }; + }, [debouncedValueChange]); + + const displayValue = isPresetValue + ? lineCountOptions.find((option) => option.value === value)?.label + : `${value} lines`; + + return ( + + + + + + +
+ + { + if (e.key === "Enter") { + e.preventDefault(); + const numValue = Number.parseInt( + inputValue.replace(/[^0-9]/g, ""), + ); + if ( + !Number.isNaN(numValue) && + numValue > 0 && + numValue !== value && + numValue !== pendingValueRef.current + ) { + handleSelect(inputValue); + } + } + }} + /> +
+ + + {lineCountOptions.map((option) => { + const isSelected = value === option.value; + return ( + handleSelect(option.label)} + className="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 aria-selected:bg-accent aria-selected:text-accent-foreground" + > +
+ +
+ {option.label} +
+ ); + })} +
+
+
+
+
+ ); +} + +export default LineCountFilter;