From 4ffadd33da0cfd35b0d33aa363fb8c85be580ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=20M=E1=BA=A1nh=20T=C6=B0=E1=BB=9Dng?= <41409442+vncloudsco@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:36:43 +0700 Subject: [PATCH 1/4] Feature log details --- .gitignore | 1 - src/components/logs/LogDetailsDialog.tsx | 246 +++++++++++++++++++++++ src/components/pages/Logs.tsx | 85 ++++++-- src/components/ui/scroll-area.tsx | 46 +++++ src/types/index.ts | 10 + 5 files changed, 374 insertions(+), 14 deletions(-) create mode 100644 src/components/logs/LogDetailsDialog.tsx create mode 100644 src/components/ui/scroll-area.tsx diff --git a/.gitignore b/.gitignore index 0fe7503..9994045 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # Logs -logs *.log npm-debug.log* yarn-debug.log* diff --git a/src/components/logs/LogDetailsDialog.tsx b/src/components/logs/LogDetailsDialog.tsx new file mode 100644 index 0000000..e6a8eb5 --- /dev/null +++ b/src/components/logs/LogDetailsDialog.tsx @@ -0,0 +1,246 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Badge } from "@/components/ui/badge"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { LogEntry } from "@/types"; + +interface LogDetailsDialogProps { + log: LogEntry | null; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function LogDetailsDialog({ log, open, onOpenChange }: LogDetailsDialogProps) { + if (!log) return null; + + const getLevelColor = ( + level: string + ): "destructive" | "default" | "secondary" | "outline" => { + switch (level) { + case "error": + return "destructive"; + case "warning": + return "outline"; + case "info": + return "default"; + default: + return "secondary"; + } + }; + + const getTypeColor = (type: string) => { + switch (type) { + case "access": + return "default"; + case "error": + return "destructive"; + case "system": + return "secondary"; + default: + return "outline"; + } + }; + + return ( + + + + + Log Details + {log.level} + {log.type} + + + {new Date(log.timestamp).toLocaleString()} + + + + +
+ {/* Basic Information */} +
+

Basic Information

+
+
+ Source: {log.source} +
+
+ Timestamp:{" "} + {new Date(log.timestamp).toLocaleString()} +
+ {log.domain && ( +
+ Domain:{" "} + + {log.domain} + +
+ )} + {log.ip && ( +
+ IP Address: {log.ip} +
+ )} +
+
+ + {/* Request Information */} + {(log.method || log.path || log.uri || log.statusCode) && ( +
+

Request Information

+
+ {log.method && ( +
+ Method:{" "} + {log.method} +
+ )} + {log.path && ( +
+ Path:{" "} + + {log.path} + +
+ )} + {log.uri && ( +
+ URI:{" "} + + {log.uri} + +
+ )} + {log.statusCode && ( +
+ Status Code:{" "} + = 500 + ? "destructive" + : log.statusCode >= 400 + ? "outline" + : "default" + } + > + {log.statusCode} + +
+ )} + {log.responseTime && ( +
+ Response Time:{" "} + {log.responseTime}ms +
+ )} +
+
+ )} + + {/* ModSecurity Specific Information */} + {(log.ruleId || log.severity || log.tags || log.file || log.uniqueId) && ( +
+

+ ModSecurity WAF Details +

+
+ {log.ruleId && ( +
+ Rule ID: + + {log.ruleId} + +
+ )} + {log.severity && ( +
+ Severity: + = 3 + ? "destructive" + : parseInt(log.severity) >= 2 + ? "outline" + : "default" + } + > + Level {log.severity} + +
+ )} + {log.tags && log.tags.length > 0 && ( +
+ Tags: +
+ {log.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+ )} + {log.file && ( +
+ Rule File: + + {log.file} + +
+ )} + {log.line && ( +
+ Line Number: + {log.line} +
+ )} + {log.uniqueId && ( +
+ Unique ID: + + {log.uniqueId} + +
+ )} + {log.data && ( +
+ Data: + + {log.data} + +
+ )} +
+
+ )} + + {/* Message */} +
+

Message

+
+

{log.message}

+
+
+ + {/* Full Log Entry */} + {log.fullMessage && ( +
+

Complete Log Entry

+
+
+                    {log.fullMessage}
+                  
+
+
+ )} +
+
+
+
+ ); +} diff --git a/src/components/pages/Logs.tsx b/src/components/pages/Logs.tsx index ee25a68..bb5d875 100644 --- a/src/components/pages/Logs.tsx +++ b/src/components/pages/Logs.tsx @@ -73,6 +73,7 @@ import { useSuspenseLogs, useLogs } from "@/queries/logs.query-options"; +import { LogDetailsDialog } from "@/components/logs/LogDetailsDialog"; // Component for fast-loading statistics data const LogStatistics = () => { @@ -160,7 +161,9 @@ const LogEntries = ({ autoRefresh, setAutoRefresh, toast, - onRefetch + onRefetch, + selectedLog, + setSelectedLog }: { page: number; limit: number; @@ -182,6 +185,8 @@ const LogEntries = ({ setAutoRefresh: (refresh: boolean) => void; toast: any; onRefetch: (refetch: () => Promise) => void; + selectedLog: LogEntry | null; + setSelectedLog: (log: LogEntry | null) => void; }) => { const [isPageChanging, setIsPageChanging] = useState(false); // Build query parameters @@ -363,11 +368,21 @@ const LogEntries = ({ { accessorKey: "message", header: "Message", - cell: ({ row }) => ( -
- {row.getValue("message")} -
- ), + cell: ({ row }) => { + const log = row.original; + const displayMessage = log.fullMessage || log.message; + return ( +
+ {/* Show truncated version in table, full message in title tooltip */} +
{log.message}
+ {log.fullMessage && log.fullMessage.length > log.message.length && ( +
+ Click for full details +
+ )} +
+ ); + }, }, { accessorKey: "details", @@ -375,7 +390,7 @@ const LogEntries = ({ cell: ({ row }) => { const log = row.original; return ( -
+
{log.ip &&
IP: {log.ip}
} {log.method && log.path && (
@@ -384,6 +399,19 @@ const LogEntries = ({ )} {log.statusCode &&
Status: {log.statusCode}
} {log.responseTime &&
RT: {log.responseTime}ms
} + {/* ModSecurity specific details */} + {log.ruleId && ( +
Rule ID: {log.ruleId}
+ )} + {log.severity && ( +
Severity: {log.severity}
+ )} + {log.tags && log.tags.length > 0 && ( +
+ Tags: {log.tags.join(', ')} +
+ )} + {log.uri &&
URI: {log.uri}
}
); }, @@ -582,6 +610,8 @@ const LogEntries = ({ data-state={ rowSelection[String(log.id || index)] && "selected" } + className="cursor-pointer hover:bg-muted/50" + onClick={() => setSelectedLog(log)} > {new Date(log.timestamp).toLocaleString()} @@ -610,13 +640,17 @@ const LogEntries = ({ )} - - {log.message} + +
+ {log.message} +
+ {log.fullMessage && log.fullMessage.length > log.message.length && ( +
+ Click for full details +
+ )}
- + {log.ip &&
IP: {log.ip}
} {log.method && log.path && (
@@ -627,6 +661,21 @@ const LogEntries = ({ {log.responseTime && (
RT: {log.responseTime}ms
)} + {/* ModSecurity specific details */} + {log.ruleId && ( +
+ Rule ID: {log.ruleId} +
+ )} + {log.severity && ( +
Severity: {log.severity}
+ )} + {log.tags && log.tags.length > 0 && ( +
+ Tags: {log.tags.join(', ')} +
+ )} + {log.uri &&
URI: {log.uri}
} )) @@ -726,6 +775,7 @@ const Logs = () => { const [autoRefresh, setAutoRefresh] = useState(false); const [logsRefetch, setLogsRefetch] = useState<(() => Promise) | null>(null); const [isReloading, setIsReloading] = useState(false); + const [selectedLog, setSelectedLog] = useState(null); // URL state management with nuqs const [page, setPage] = useQueryState("page", parseAsInteger.withDefault(1)); @@ -897,6 +947,15 @@ const Logs = () => { setAutoRefresh={setAutoRefresh} toast={toast} onRefetch={(refetch) => setLogsRefetch(() => refetch)} + selectedLog={selectedLog} + setSelectedLog={setSelectedLog} + /> + + {/* Log Details Dialog */} + !open && setSelectedLog(null)} />
); diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx new file mode 100644 index 0000000..64b0c9b --- /dev/null +++ b/src/components/ui/scroll-area.tsx @@ -0,0 +1,46 @@ +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +const ScrollArea = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + {children} + + + + +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName + +const ScrollBar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, orientation = "vertical", ...props }, ref) => ( + + + +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName + +export { ScrollArea, ScrollBar } diff --git a/src/types/index.ts b/src/types/index.ts index 5d2f479..995c400 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -162,6 +162,16 @@ export interface LogEntry { path?: string; statusCode?: number; responseTime?: number; + // ModSecurity specific fields + ruleId?: string; + severity?: string; + tags?: string[]; + uri?: string; + uniqueId?: string; + file?: string; + line?: string; + data?: string; + fullMessage?: string; // Complete log message without truncation } export interface NotificationChannel { From 1152dc6785aa6fb939d6391c02e55b9c70155c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=20M=E1=BA=A1nh=20T=C6=B0=E1=BB=9Dng?= <41409442+vncloudsco@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:02:24 +0700 Subject: [PATCH 2/4] version update --- src-tauri/Cargo.toml | 4 ++-- src-tauri/tauri.conf.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 9939114..825678d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "Nginx_WAF" -version = "0.1.0" +version = "0.1.2" description = "Nginx WAF - Advanced Nginx Management Platform" authors = ["TinyActive"] -edition = "2024" +edition = "2025" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 655258b..587fcca 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Nginx WAF", - "version": "0.1.1", + "version": "0.1.2", "identifier": "com.tinyactive.nginx-waf", "build": { "beforeDevCommand": "npm run dev", From a74945db8b507e3dac456fb3c843cb9189086ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=20M=E1=BA=A1nh=20T=C6=B0=E1=BB=9Dng?= <41409442+vncloudsco@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:07:31 +0700 Subject: [PATCH 3/4] fix edition version --- src-tauri/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 825678d..96b1622 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -3,7 +3,7 @@ name = "Nginx_WAF" version = "0.1.2" description = "Nginx WAF - Advanced Nginx Management Platform" authors = ["TinyActive"] -edition = "2025" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 35cdbf01eae70786dd7a64665bd3fef9f65fc2e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vi=20M=E1=BA=A1nh=20T=C6=B0=E1=BB=9Dng?= <41409442+vncloudsco@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:17:44 +0700 Subject: [PATCH 4/4] update version --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d0af72..c4937ce 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,13 +127,13 @@ jobs: with: path: ./artifacts - - name: Create Draft Release 0.1.1 + - name: Create Draft Release 0.1.2 uses: softprops/action-gh-release@v1 with: - tag_name: 0.1.1 - name: Release 0.1.1 + tag_name: 0.1.2 + name: Release 0.1.2 body: | - Automated release for Nginx WAF Desktop Client v0.1.0. + Automated release for Nginx WAF Desktop Client v0.1.2. ## Changes - Cross-platform builds for Linux x86_64, Windows x86_64, Mac Intel, Mac ARM.