feat: implement system log detail view with dynamic status badges and optimize log file retrieval performance
This commit is contained in:
parent
37960a9dc5
commit
2552ca9511
@ -10,14 +10,10 @@
|
|||||||
|
|
||||||
class LogController extends Controller
|
class LogController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Display a listing of log entries.
|
|
||||||
*/
|
|
||||||
public function index(Request $request): Response
|
public function index(Request $request): Response
|
||||||
{
|
{
|
||||||
$logFiles = $this->getLogFiles();
|
$logFiles = $this->getLogFiles();
|
||||||
|
|
||||||
// Default to the latest log file if not specified or doesn't exist
|
|
||||||
$selectedFile = $request->input('file');
|
$selectedFile = $request->input('file');
|
||||||
|
|
||||||
if (! $selectedFile || ! in_array($selectedFile, $logFiles)) {
|
if (! $selectedFile || ! in_array($selectedFile, $logFiles)) {
|
||||||
@ -33,9 +29,6 @@ public function index(Request $request): Response
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all .log files from storage/logs.
|
|
||||||
*/
|
|
||||||
private function getLogFiles(): array
|
private function getLogFiles(): array
|
||||||
{
|
{
|
||||||
$path = storage_path('logs');
|
$path = storage_path('logs');
|
||||||
@ -46,16 +39,14 @@ private function getLogFiles(): array
|
|||||||
$files = File::files($path);
|
$files = File::files($path);
|
||||||
|
|
||||||
return collect($files)
|
return collect($files)
|
||||||
->map(fn ($file) => $file->getFilename())
|
->map(fn($file) => $file->getFilename())
|
||||||
->filter(fn ($filename) => str_ends_with($filename, '.log'))
|
->filter(fn($filename) => str_ends_with($filename, '.log'))
|
||||||
->sortDesc() // Latest dates first
|
->sortDesc()
|
||||||
|
->take(10)
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a log file into an array of entries.
|
|
||||||
*/
|
|
||||||
private function parseLogFile($filename): array
|
private function parseLogFile($filename): array
|
||||||
{
|
{
|
||||||
$path = storage_path("logs/{$filename}");
|
$path = storage_path("logs/{$filename}");
|
||||||
@ -63,12 +54,8 @@ private function parseLogFile($filename): array
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit file reading to prevent memory issues with huge logs
|
|
||||||
// For simplicity, we read the whole file here, but in production consider chunking.
|
|
||||||
$content = File::get($path);
|
$content = File::get($path);
|
||||||
|
|
||||||
// Pattern: [timestamp] environment.LEVEL: Message
|
|
||||||
// Handles multi-line messages (like stack traces) using lookahead
|
|
||||||
$pattern = '/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+)\.(\w+): ([\s\S]*?)(?=\n^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]|$)/m';
|
$pattern = '/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+)\.(\w+): ([\s\S]*?)(?=\n^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]|$)/m';
|
||||||
|
|
||||||
preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
|
preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
|
||||||
@ -84,7 +71,6 @@ private function parseLogFile($filename): array
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return latest logs first
|
|
||||||
return array_reverse($logs);
|
return array_reverse($logs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
resources/js/lib/log-helpers.ts
Normal file
18
resources/js/lib/log-helpers.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const getLevelColor = (level: string) => {
|
||||||
|
switch (level.toUpperCase()) {
|
||||||
|
case 'EMERGENCY':
|
||||||
|
case 'ALERT':
|
||||||
|
case 'CRITICAL':
|
||||||
|
case 'ERROR':
|
||||||
|
return 'destructive';
|
||||||
|
case 'WARNING':
|
||||||
|
return 'outline';
|
||||||
|
case 'NOTICE':
|
||||||
|
case 'INFO':
|
||||||
|
return 'secondary';
|
||||||
|
case 'DEBUG':
|
||||||
|
return 'outline';
|
||||||
|
default:
|
||||||
|
return 'default';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,8 @@ import { Head, router } from '@inertiajs/react';
|
|||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { getColumns, SystemLog } from './partials/columns';
|
import { getColumns } from './partials/columns';
|
||||||
|
import { SystemLog } from '@/types';
|
||||||
import system from '@/routes/system';
|
import system from '@/routes/system';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@ -78,6 +79,7 @@ export default function SystemLogIndex({ logFiles, selectedFile, logs }: Props)
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
showSelection={false}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -4,38 +4,13 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Eye } from 'lucide-react';
|
import { Eye } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||||
|
import { SystemLog } from '@/types';
|
||||||
export interface SystemLog {
|
import { getLevelColor } from '@/lib/log-helpers';
|
||||||
id: number;
|
|
||||||
timestamp: string;
|
|
||||||
env: string;
|
|
||||||
level: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ColumnProps {
|
interface ColumnProps {
|
||||||
onView: (log: SystemLog) => void;
|
onView: (log: SystemLog) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLevelColor = (level: string) => {
|
|
||||||
switch (level.toUpperCase()) {
|
|
||||||
case 'EMERGENCY':
|
|
||||||
case 'ALERT':
|
|
||||||
case 'CRITICAL':
|
|
||||||
case 'ERROR':
|
|
||||||
return 'destructive';
|
|
||||||
case 'WARNING':
|
|
||||||
return 'outline'; // Fallback if no warning variant
|
|
||||||
case 'NOTICE':
|
|
||||||
case 'INFO':
|
|
||||||
return 'secondary';
|
|
||||||
case 'DEBUG':
|
|
||||||
return 'outline';
|
|
||||||
default:
|
|
||||||
return 'default';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getColumns = ({ onView }: ColumnProps): ColumnDef<SystemLog>[] => [
|
export const getColumns = ({ onView }: ColumnProps): ColumnDef<SystemLog>[] => [
|
||||||
{
|
{
|
||||||
accessorKey: "timestamp",
|
accessorKey: "timestamp",
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { SystemLog } from "./columns"
|
import { SystemLog } from "@/types"
|
||||||
|
import { getLevelColor } from "@/lib/log-helpers"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
log: SystemLog | null;
|
log: SystemLog | null;
|
||||||
@ -21,7 +22,7 @@ export function LogDetailModal({ log, onClose }: Props) {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<DialogTitle>Detail Log</DialogTitle>
|
<DialogTitle>Detail Log</DialogTitle>
|
||||||
<Badge variant="outline">{log.level}</Badge>
|
<Badge variant={getLevelColor(log.level)}>{log.level}</Badge>
|
||||||
<span className="text-sm text-muted-foreground">{log.timestamp}</span>
|
<span className="text-sm text-muted-foreground">{log.timestamp}</span>
|
||||||
</div>
|
</div>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@ -8,4 +8,5 @@ export type * from './payroll';
|
|||||||
export type * from './purchase';
|
export type * from './purchase';
|
||||||
export type * from './general-setting';
|
export type * from './general-setting';
|
||||||
export type * from './activity';
|
export type * from './activity';
|
||||||
|
export type * from './system-log';
|
||||||
|
|
||||||
|
|||||||
7
resources/js/types/system-log.ts
Normal file
7
resources/js/types/system-log.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface SystemLog {
|
||||||
|
id: number;
|
||||||
|
timestamp: string;
|
||||||
|
env: string;
|
||||||
|
level: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user