feat: implement dynamic log type filtering and selection in LogContainer and LogMenu components

This commit is contained in:
Sam Chau
2025-01-08 18:02:00 +10:30
parent 59a8578542
commit b4b59b7fe2
3 changed files with 57 additions and 32 deletions

View File

@@ -4,15 +4,10 @@ import {Logs} from '@api/logs';
import LogMenu from './LogMenu';
import LogViewer from './LogViewer';
const guessLogType = filename => {
if (filename.startsWith('importarr')) return 'Importarr';
if (filename.startsWith('profilarr')) return 'General';
return 'Other';
};
const LogContainer = () => {
const [allLogFiles, setAllLogFiles] = useState([]);
const [logType, setLogType] = useState('General');
const [logTypes, setLogTypes] = useState(new Set());
const [selectedType, setSelectedType] = useState('');
const [filteredFiles, setFilteredFiles] = useState([]);
const [selectedFile, setSelectedFile] = useState('');
const [filters, setFilters] = useState({lines: '', level: '', search: ''});
@@ -26,17 +21,43 @@ const LogContainer = () => {
}, []);
useEffect(() => {
const matched = allLogFiles
.filter(f => guessLogType(f.filename) === logType)
.sort((a, b) => b.last_modified - a.last_modified);
setFilteredFiles(matched);
if (matched.length) {
setSelectedFile(matched[0].filename);
} else {
setSelectedFile('');
setLogContent([]);
if (allLogFiles.length > 0) {
// Extract unique log types from filenames
const types = new Set(
allLogFiles.map(f => {
const match = f.filename.match(/^([a-zA-Z]+)/);
return match ? match[1].toLowerCase() : 'other';
})
);
setLogTypes(types);
// Set initial type if not already set
if (!selectedType && types.size > 0) {
setSelectedType(Array.from(types)[0]);
}
}
}, [logType, allLogFiles]);
}, [allLogFiles]);
useEffect(() => {
if (selectedType) {
const matched = allLogFiles
.filter(f => {
const fileType =
f.filename.match(/^([a-zA-Z]+)/)?.[1].toLowerCase() ||
'other';
return fileType === selectedType;
})
.sort((a, b) => b.last_modified - a.last_modified);
setFilteredFiles(matched);
if (matched.length) {
setSelectedFile(matched[0].filename);
} else {
setSelectedFile('');
setLogContent([]);
}
}
}, [selectedType, allLogFiles]);
useEffect(() => {
if (selectedFile) {
@@ -95,8 +116,9 @@ const LogContainer = () => {
return (
<div className='h-full flex flex-col space-y-4'>
<LogMenu
logType={logType}
setLogType={setLogType}
logTypes={Array.from(logTypes)}
selectedType={selectedType}
setSelectedType={setSelectedType}
selectedFile={selectedFile}
setSelectedFile={setSelectedFile}
filteredFiles={filteredFiles}

View File

@@ -1,9 +1,11 @@
// LogMenu.jsx
import React from 'react';
import {Search} from 'lucide-react';
const LogMenu = ({
logType,
setLogType,
logTypes,
selectedType,
setSelectedType,
selectedFile,
setSelectedFile,
filteredFiles,
@@ -13,20 +15,25 @@ const LogMenu = ({
const selectStyles =
'bg-gray-900 text-sm text-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500';
const formatLogType = type => {
return type.charAt(0).toUpperCase() + type.slice(1);
};
return (
<div className='bg-gray-800 rounded-lg border border-gray-700 shadow-xl p-4'>
<div className='space-y-4'>
{/* Top Row Filters */}
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4'>
{/* Log Type Selection */}
<div className='relative'>
<select
className={`w-full px-3 py-1.5 rounded-md appearance-none cursor-pointer ${selectStyles}`}
value={logType}
onChange={e => setLogType(e.target.value)}>
<option value='General'>Type: General</option>
<option value='Importarr'>Type: Importarr</option>
<option value='Other'>Type: Other</option>
value={selectedType}
onChange={e => setSelectedType(e.target.value)}>
{logTypes.map(type => (
<option key={type} value={type}>
Type: {formatLogType(type)}
</option>
))}
</select>
<div className='absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none'>
<svg
@@ -38,7 +45,6 @@ const LogMenu = ({
</div>
</div>
{/* Log File Selection */}
<div className='relative'>
<select
className={`w-full px-3 py-1.5 rounded-md appearance-none cursor-pointer ${selectStyles}`}
@@ -60,7 +66,6 @@ const LogMenu = ({
</div>
</div>
{/* Lines Filter */}
<input
type='number'
className={`w-full px-3 py-1.5 rounded-md ${selectStyles}`}
@@ -69,7 +74,6 @@ const LogMenu = ({
placeholder='Lines: last N lines...'
/>
{/* Log Level Filter */}
<div className='relative'>
<select
className={`w-full px-3 py-1.5 rounded-md appearance-none cursor-pointer ${selectStyles}`}
@@ -94,7 +98,6 @@ const LogMenu = ({
</div>
</div>
{/* Search Bar - Full Width */}
<div className='relative'>
<div className='absolute inset-y-0 left-3 flex items-center pointer-events-none'>
<Search size={16} className='text-gray-400' />

View File

@@ -74,7 +74,7 @@ const LogViewer = ({
</div>
</div>
<div
className='h-[calc(100vh-28rem)] overflow-y-auto p-4'
className='h-[calc(100vh-28rem)] overflow-y-auto p-4 scrollable'
style={{fontSize: `${zoom}rem`}}>
{loading && (
<div className='flex items-center justify-center p-4 text-gray-400'>