From 7577e40b257b87cc55b300d5964fafe933830aed Mon Sep 17 00:00:00 2001 From: 190km Date: Mon, 16 Dec 2024 19:49:09 +0100 Subject: [PATCH 01/11] feat(logs): added filter log type component --- .../docker/logs/status-logs-filter.tsx | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx diff --git a/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx new file mode 100644 index 00000000..d6bd81bf --- /dev/null +++ b/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx @@ -0,0 +1,107 @@ +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; +import { CheckIcon, PlusCircle } from "lucide-react"; +import React from "react"; + +interface StatusLogsFilterProps { + value?: string[]; + setValue?: (value: string[]) => void; + title?: string; + options: { + label: string; + value: string; + icon?: React.ComponentType<{ className?: string }>; + }[]; +} + +export function StatusLogsFilter({ + value = [], + setValue, + title, + options, +}: StatusLogsFilterProps) { + const selectedValues = new Set(value as string[]); + + return ( + + + + + + + + + No results found. + + {options.map((option) => { + const isSelected = selectedValues.has(option.value); + return ( + { + if (isSelected) { + selectedValues.delete(option.value); + } else { + selectedValues.add(option.value); + } + const filterValues = Array.from(selectedValues); + setValue?.(filterValues.length ? filterValues : []); + }} + > +
+ +
+ {option.icon && ( + + )} + {option.label} +
+ ); + })} +
+ +
+
+
+
+ ); +} From b03011a94ff556398b2b4e499b6b17ae8dd9a23d Mon Sep 17 00:00:00 2001 From: 190km Date: Mon, 16 Dec 2024 19:50:13 +0100 Subject: [PATCH 02/11] feat(logs): replaced the log type component with the new --- .../dashboard/docker/logs/docker-logs-id.tsx | 85 +++++++++++-------- 1 file changed, 48 insertions(+), 37 deletions(-) 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 db1c774b..951f1ff7 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -9,18 +9,50 @@ import { SelectValue, } from "@/components/ui/select"; import { api } from "@/utils/api"; -import { Download as DownloadIcon, Loader2 } from "lucide-react"; +import { + CheckCircle2Icon, + Download as DownloadIcon, + Loader2, + Bug, + InfoIcon, + CircleX, + TriangleAlert, +} from "lucide-react"; import React, { useEffect, useRef } from "react"; import { TerminalLine } from "./terminal-line"; import { type LogLine, getLogType, parseLogs } from "./utils"; +import { StatusLogsFilter } from "./status-logs-filter"; interface Props { containerId: string; serverId?: string | null; } + type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; -type TypeFilter = "all" | "error" | "warning" | "success" | "info" | "debug"; + +export const priorities = [ + { + label: "Info", + value: "info", + }, + { + label: "Success", + value: "success", + }, + { + label: "Warning", + value: "warning", + }, + { + label: "Debug", + value: "debug", + }, + { + label: "Error", + value: "error", + }, +]; export const DockerLogsId: React.FC = ({ containerId, serverId }) => { const { data } = api.docker.getConfig.useQuery( @@ -40,7 +72,7 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { const [search, setSearch] = React.useState(""); const [since, setSince] = React.useState("all"); - const [typeFilter, setTypeFilter] = React.useState("all"); + const [typeFilter, setTypeFilter] = React.useState([]); const scrollRef = useRef(null); const [isLoading, setIsLoading] = React.useState(false); @@ -74,10 +106,6 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { setSince(value); }; - const handleTypeFilter = (value: TypeFilter) => { - setTypeFilter(value); - }; - useEffect(() => { if (!containerId) return; @@ -179,11 +207,13 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { const handleFilter = (logs: LogLine[]) => { return logs.filter((log) => { const logType = getLogType(log.message).type; + + if (typeFilter.length === 0) { + return true; + } - const matchesType = typeFilter === "all" || logType === typeFilter; - - return matchesType; - }); + return typeFilter.includes(logType); + }); }; useEffect(() => { @@ -232,32 +262,13 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { All time - - + + Date: Mon, 16 Dec 2024 19:56:47 +0100 Subject: [PATCH 03/11] fix: fixed lint --- .../components/dashboard/docker/logs/docker-logs-id.tsx | 9 ++------- .../dashboard/docker/logs/status-logs-filter.tsx | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) 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 951f1ff7..34f74e2a 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -10,13 +10,8 @@ import { } from "@/components/ui/select"; import { api } from "@/utils/api"; import { - CheckCircle2Icon, Download as DownloadIcon, - Loader2, - Bug, - InfoIcon, - CircleX, - TriangleAlert, + Loader2 } from "lucide-react"; import React, { useEffect, useRef } from "react"; import { TerminalLine } from "./terminal-line"; @@ -262,7 +257,7 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { All time - + Date: Mon, 16 Dec 2024 21:27:32 +0100 Subject: [PATCH 04/11] feat(logs): added show/hide timestamp option --- .../dashboard/docker/logs/docker-logs-id.tsx | 25 +++++++++++++------ .../dashboard/docker/logs/terminal-line.tsx | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) 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 34f74e2a..477f2641 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -1,4 +1,3 @@ -import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -7,6 +6,7 @@ import { SelectItem, SelectTrigger, SelectValue, + SelectSeparator } from "@/components/ui/select"; import { api } from "@/utils/api"; import { @@ -23,8 +23,7 @@ interface Props { serverId?: string | null; } - -type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; +type TimeFilter = "all" | "timestamp" | "1h" | "6h" | "24h" | "168h" | "720h"; export const priorities = [ { @@ -65,7 +64,7 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { const [autoScroll, setAutoScroll] = React.useState(true); const [lines, setLines] = React.useState(100); const [search, setSearch] = React.useState(""); - + const [showTimestamp, setShowTimestamp] = React.useState(true); const [since, setSince] = React.useState("all"); const [typeFilter, setTypeFilter] = React.useState([]); const scrollRef = useRef(null); @@ -96,9 +95,13 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { }; const handleSince = (value: TimeFilter) => { - setRawLogs(""); - setFilteredLogs([]); - setSince(value); + if (value === "timestamp") { + setShowTimestamp(!showTimestamp); + } else { + setRawLogs(""); + setFilteredLogs([]); + setSince(value); + } }; useEffect(() => { @@ -255,6 +258,10 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { Last 7 days Last 30 days All time + + + {showTimestamp ? "Hide timestamp" : "Show timestamp"} + @@ -272,12 +279,13 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { onChange={handleSearch} className="inline-flex h-9 text-sm placeholder-gray-400 w-full sm:w-auto" /> + - -
- {filteredLogs.length > 0 ? ( - filteredLogs.map((filteredLog: LogLine, index: number) => ( - - )) - ) : isLoading ? ( -
- -
- ) : ( -
- No logs found -
- )} -
- - - - ); -}; \ No newline at end of file + + +
+ {filteredLogs.length > 0 ? ( + filteredLogs.map((filteredLog: LogLine, index: number) => ( + + )) + ) : isLoading ? ( +
+ +
+ ) : ( +
+ No logs found +
+ )} +
+ + + + ); +}; diff --git a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx new file mode 100644 index 00000000..6cc8785b --- /dev/null +++ b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx @@ -0,0 +1,121 @@ +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandGroup, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Separator } from "@/components/ui/separator"; +import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; +import { CheckIcon } from "lucide-react"; +import React from "react"; + +type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; + +const timeRanges = [ + { + label: "All time", + value: "all", + }, + { + label: "Last hour", + value: "1h", + }, + { + label: "Last 6 hours", + value: "6h", + }, + { + label: "Last 24 hours", + value: "24h", + }, + { + label: "Last 7 days", + value: "168h", + }, + { + label: "Last 30 days", + value: "720h", + }, +]; + +interface SinceLogsFilterProps { + value: string; + onValueChange: (value: TimeFilter) => void; + showTimestamp: boolean; + onTimestampChange: (show: boolean) => void; + title?: string; +} + +export function SinceLogsFilter({ + value, + onValueChange, + showTimestamp, + onTimestampChange, + title = "Time range", +}: SinceLogsFilterProps) { + const selectedLabel = + timeRanges.find((range) => range.value === value)?.label ?? + "Select time range"; + + return ( + + + + + + + + + {timeRanges.map((range) => { + const isSelected = value === range.value; + return ( + onValueChange(range.value)} + > +
+ +
+ {range.label} +
+ ); + })} +
+
+
+ +
+ Show timestamps + +
+
+
+ ); +} diff --git a/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx index 70a6dfc8..3ef11517 100644 --- a/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/status-logs-filter.tsx @@ -1,107 +1,170 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Separator } from "@/components/ui/separator"; -import { cn } from "@/lib/utils"; -import { CheckIcon } from "lucide-react"; -import React from "react"; - -interface StatusLogsFilterProps { - value?: string[]; - setValue?: (value: string[]) => void; - title?: string; - options: { - label: string; - value: string; - icon?: React.ComponentType<{ className?: string }>; - }[]; -} - -export function StatusLogsFilter({ - value = [], - setValue, - title, - options, -}: StatusLogsFilterProps) { - const selectedValues = new Set(value as string[]); - - return ( - - - - - - - - - No results found. - - {options.map((option) => { - const isSelected = selectedValues.has(option.value); - return ( - { - if (isSelected) { - selectedValues.delete(option.value); - } else { - selectedValues.add(option.value); - } - const filterValues = Array.from(selectedValues); - setValue?.(filterValues.length ? filterValues : []); - }} - > -
- -
- {option.icon && ( - - )} - {option.label} -
- ); - })} -
- -
-
-
-
- ); -} +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandGroup, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; +import { CheckIcon } from "lucide-react"; +import type React from "react"; + +interface StatusLogsFilterProps { + value?: string[]; + setValue?: (value: string[]) => void; + title?: string; + options: { + label: string; + value: string; + icon?: React.ComponentType<{ className?: string }>; + }[]; +} + +export function StatusLogsFilter({ + value = [], + setValue, + title, + options, +}: StatusLogsFilterProps) { + const selectedValues = new Set(value as string[]); + const allSelected = selectedValues.size === 0; + + const getSelectedBadges = () => { + if (allSelected) { + return ( + + All + + ); + } + + if (selectedValues.size >= 1) { + const selected = options.find((opt) => selectedValues.has(opt.value)); + return ( + <> + + {selected?.label} + + {selectedValues.size > 1 && ( + + +{selectedValues.size - 1} + + )} + + ); + } + + return null; + }; + + return ( + + + + + + + + + { + setValue?.([]); // Empty array means "All" + }} + > +
+ +
+ All +
+ {options.map((option) => { + const isSelected = selectedValues.has(option.value); + return ( + { + const newValues = new Set(selectedValues); + if (isSelected) { + newValues.delete(option.value); + } else { + newValues.add(option.value); + } + setValue?.(Array.from(newValues)); + }} + > +
+ +
+ {option.icon && ( + + )} + + {option.label} + +
+ ); + })} +
+
+
+
+
+ ); +} diff --git a/apps/dokploy/components/dashboard/docker/logs/utils.ts b/apps/dokploy/components/dashboard/docker/logs/utils.ts index 409c6989..cf0b30bb 100644 --- a/apps/dokploy/components/dashboard/docker/logs/utils.ts +++ b/apps/dokploy/components/dashboard/docker/logs/utils.ts @@ -1,5 +1,5 @@ -export type LogType = "error" | "warning" | "success" | "info" | "debug"; -export type LogVariant = "red" | "yellow" | "green" | "blue" | "orange"; +export type LogType = "error" | "warning" | "success" | "info" | "debug"; +export type LogVariant = "red" | "yellow" | "green" | "blue" | "orange"; export interface LogLine { rawTimestamp: string | null; @@ -138,8 +138,12 @@ export const getLogType = (message: string): LogStyle => { if ( /(?:^|\s)(?:info|inf):?\s/i.test(lowerMessage) || - /\[(info|log|debug|trace|server|db|api|http|request|response)\]/i.test(lowerMessage) || - /\b(?:version|config|import|load|get|HTTP|PATCH|POST|debug)\b:?/i.test(lowerMessage) + /\[(info|log|debug|trace|server|db|api|http|request|response)\]/i.test( + lowerMessage, + ) || + /\b(?:version|config|import|load|get|HTTP|PATCH|POST|debug)\b:?/i.test( + lowerMessage, + ) ) { return LOG_STYLES.debug; } From 87a5ce2053457b7114ae60c3a091e39e8a3b3ea7 Mon Sep 17 00:00:00 2001 From: 190km Date: Mon, 16 Dec 2024 22:55:36 +0100 Subject: [PATCH 06/11] fix: timestamp width --- apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx index f93b0d2f..cdbbb2c8 100644 --- a/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/terminal-line.tsx @@ -91,7 +91,7 @@ export function TerminalLine({ log, noTimestamp, searchTerm }: LogLineProps) { {/* */} {tooltip(color, rawTimestamp)} {!noTimestamp && ( - + {formattedTime} )} From bd16e03602259782df630b4f1e8230e4a873931a Mon Sep 17 00:00:00 2001 From: Nicholas Penree Date: Mon, 16 Dec 2024 17:01:44 -0500 Subject: [PATCH 07/11] chore: lint --- .../dashboard/docker/logs/docker-logs-id.tsx | 16 +++++----------- .../dashboard/docker/logs/since-logs-filter.tsx | 6 +++--- 2 files changed, 8 insertions(+), 14 deletions(-) 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 3e490a1f..110e0faa 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -3,7 +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 { SinceLogsFilter } from "./since-logs-filter"; +import { SinceLogsFilter, type TimeFilter } from "./since-logs-filter"; import { StatusLogsFilter } from "./status-logs-filter"; import { TerminalLine } from "./terminal-line"; import { type LogLine, getLogType, parseLogs } from "./utils"; @@ -13,8 +13,6 @@ interface Props { serverId?: string | null; } -type TimeFilter = "all" | "timestamp" | "1h" | "6h" | "24h" | "168h" | "720h"; - export const priorities = [ { label: "Info", @@ -84,14 +82,10 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => { setLines(Number(e.target.value) || 1); }; - const handleSince = (value: string) => { - if (value === "timestamp") { - setShowTimestamp(!showTimestamp); - } else { - setRawLogs(""); - setFilteredLogs([]); - setSince(value); - } + const handleSince = (value: TimeFilter) => { + setRawLogs(""); + setFilteredLogs([]); + setSince(value); }; useEffect(() => { diff --git a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx index 6cc8785b..09dbaff8 100644 --- a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx @@ -17,9 +17,9 @@ import { cn } from "@/lib/utils"; import { CheckIcon } from "lucide-react"; import React from "react"; -type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; +export type TimeFilter = "all" | "1h" | "6h" | "24h" | "168h" | "720h"; -const timeRanges = [ +const timeRanges: Array<{ label: string; value: TimeFilter }> = [ { label: "All time", value: "all", @@ -44,7 +44,7 @@ const timeRanges = [ label: "Last 30 days", value: "720h", }, -]; +] as const; interface SinceLogsFilterProps { value: string; From 81c85ce15536d752f8bf34759d11c1cafec6cd15 Mon Sep 17 00:00:00 2001 From: Nicholas Penree Date: Mon, 16 Dec 2024 17:09:54 -0500 Subject: [PATCH 08/11] fix: don't trigger if already selected --- .../components/dashboard/docker/logs/since-logs-filter.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx index 09dbaff8..61524ee9 100644 --- a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx @@ -91,7 +91,11 @@ export function SinceLogsFilter({ return ( onValueChange(range.value)} + onSelect={() => { + if (!isSelected) { + onValueChange(range.value); + } + }} >
Date: Mon, 16 Dec 2024 17:18:11 -0500 Subject: [PATCH 09/11] chore: lint --- .../components/dashboard/docker/logs/since-logs-filter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx index 61524ee9..b7caafe7 100644 --- a/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/since-logs-filter.tsx @@ -47,7 +47,7 @@ const timeRanges: Array<{ label: string; value: TimeFilter }> = [ ] as const; interface SinceLogsFilterProps { - value: string; + value: TimeFilter; onValueChange: (value: TimeFilter) => void; showTimestamp: boolean; onTimestampChange: (show: boolean) => void; From 6db9c99080438a10049cae3106c1fd3277442b4d Mon Sep 17 00:00:00 2001 From: Nicholas Penree Date: Mon, 16 Dec 2024 19:12:33 -0500 Subject: [PATCH 10/11] 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; From b3313cf975772a83e23a68ebcb68f4f9fbe01575 Mon Sep 17 00:00:00 2001 From: usopp Date: Tue, 17 Dec 2024 19:16:40 +0100 Subject: [PATCH 11/11] style: better white style --- .../application/deployments/show-deployment.tsx | 2 +- .../compose/deployments/show-deployment-compose.tsx | 2 +- .../dashboard/docker/logs/docker-logs-id.tsx | 2 +- apps/dokploy/components/ui/badge.tsx | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx index 96e32d8c..380b22d9 100644 --- a/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx +++ b/apps/dokploy/components/dashboard/application/deployments/show-deployment.tsx @@ -111,7 +111,7 @@ export const ShowDeployment = ({ logPath, open, onClose, serverId }: Props) => {
{ filteredLogs.length > 0 ? filteredLogs.map((log: LogLine, index: number) => ( 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 9c3e2dda..3f30c292 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -263,7 +263,7 @@ export const DockerLogsId: React.FC = ({ containerId, serverId }) => {
{filteredLogs.length > 0 ? ( filteredLogs.map((filteredLog: LogLine, index: number) => ( diff --git a/apps/dokploy/components/ui/badge.tsx b/apps/dokploy/components/ui/badge.tsx index 911b0071..9c41234d 100644 --- a/apps/dokploy/components/ui/badge.tsx +++ b/apps/dokploy/components/ui/badge.tsx @@ -14,14 +14,14 @@ const badgeVariants = cva( "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - red: "border-transparent select-none items-center whitespace-nowrap font-medium bg-red-500/15 text-destructive text-xs h-4 px-1 py-1 rounded-md", + red: "border-transparent select-none items-center whitespace-nowrap font-medium bg-red-600/20 dark:bg-red-500/15 text-destructive text-xs h-4 px-1 py-1 rounded-md", yellow: - "border-transparent select-none items-center whitespace-nowrap font-medium bg-yellow-500/15 text-yellow-500 text-xs h-4 px-1 py-1 rounded-md", + "border-transparent select-none items-center whitespace-nowrap font-medium bg-yellow-600/20 dark:bg-yellow-500/15 dark:text-yellow-500 text-yellow-600 text-xs h-4 px-1 py-1 rounded-md", orange: - "border-transparent select-none items-center whitespace-nowrap font-medium bg-orange-500/15 text-orange-500 text-xs h-4 px-1 py-1 rounded-md", + "border-transparent select-none items-center whitespace-nowrap font-medium bg-orange-600/20 dark:bg-orange-500/15 dark:text-orange-500 text-orange-600 text-xs h-4 px-1 py-1 rounded-md", green: - "border-transparent select-none items-center whitespace-nowrap font-medium bg-emerald-500/15 text-emerald-500 text-xs h-4 px-1 py-1 rounded-md", - blue: "border-transparent select-none items-center whitespace-nowrap font-medium bg-blue-500/15 text-blue-500 text-xs h-4 px-1 py-1 rounded-md", + "border-transparent select-none items-center whitespace-nowrap font-medium bg-emerald-600/20 dark:bg-emerald-500/15 dark:text-emerald-500 text-emerald-600 text-xs h-4 px-1 py-1 rounded-md", + blue: "border-transparent select-none items-center whitespace-nowrap font-medium bg-blue-600/20 dark:bg-blue-500/15 dark:text-blue-500 text-blue-600 text-xs h-4 px-1 py-1 rounded-md", blank: "border-transparent select-none items-center whitespace-nowrap font-medium dark:bg-white/15 bg-black/15 text-foreground text-xs h-4 px-1 py-1 rounded-md", outline: "text-foreground",