Search function added to audit logs for admins

This commit is contained in:
NPS Agent
2026-05-12 10:19:19 +09:30
parent 5968294081
commit 7545d1da47
2 changed files with 78 additions and 14 deletions
+77 -14
View File
@@ -636,33 +636,96 @@ function Field({ label, children }) {
}
function AuditScreen({ entries, onOpen }) {
const [filter, setFilter] = React.useState('all');
const [actorFilter, setActorFilter] = React.useState('all');
const [eventFilter, setEventFilter] = React.useState('all');
const [auditSearch, setAuditSearch] = React.useState('');
const EVENT_TYPES = [
{ value: 'all', label: 'All events' },
{ value: 'task', label: 'Tasks (all)' },
{ value: 'task_created', label: 'Created' },
{ value: 'task_moved', label: 'Moved' },
{ value: 'task_completed', label: 'Completed' },
{ value: 'task_deleted', label: 'Deleted' },
{ value: 'task_restored', label: 'Restored' },
{ value: 'user', label: 'User management' },
{ value: 'workspace_updated', label: 'Workspace' },
];
const groups = React.useMemo(() => {
const filtered = filter === 'all'
? entries
: filter === 'system'
? entries.filter(e => e.actor === 'system')
: entries.filter(e => e.actor === filter);
let filtered = entries;
// 1. Actor Filter
if (actorFilter !== 'all') {
filtered = actorFilter === 'system'
? filtered.filter(e => e.actor === 'system')
: filtered.filter(e => e.actor === actorFilter);
}
// 2. Event Type Filter
if (eventFilter !== 'all') {
if (eventFilter === 'task') {
filtered = filtered.filter(e => e.action.startsWith('task_'));
} else if (eventFilter === 'user') {
filtered = filtered.filter(e => e.action.startsWith('user_') || e.action === 'password_changed');
} else {
filtered = filtered.filter(e => e.action === eventFilter);
}
}
// 3. Search Query
if (auditSearch.trim()) {
const q = auditSearch.toLowerCase();
filtered = filtered.filter(e =>
e.summary.toLowerCase().includes(q) ||
e.action.toLowerCase().includes(q) ||
(e.actor !== 'system' && (findUser(e.actor) || {}).name?.toLowerCase().includes(q))
);
}
const out = {};
filtered.forEach(e => {
const day = new Date(e.at).toDateString();
(out[day] = out[day] || []).push(e);
});
return out;
}, [filter, entries]);
}, [actorFilter, eventFilter, auditSearch, entries]);
return (
<div className="audit">
<header className="audit__head">
<div>
<header className="audit__head" style={{ gap: '20px' }}>
<div style={{ flex: 1, minWidth: '300px' }}>
<h1 className="audit__title">Audit log</h1>
<p className="audit__sub">Everything that's happened in the workspace · <span className="mono">last 7 days</span></p>
<p className="audit__sub">History of the workspace · <span className="mono">last 7 days</span></p>
<div style={{ marginTop: '1rem', display: 'flex', gap: '8px' }}>
<div className="topbar__search" style={{ maxWidth: 'none', flex: 1 }}>
<Icon.Search />
<input
className="topbar__search-input"
placeholder="Search audit trail…"
value={auditSearch}
onChange={e => setAuditSearch(e.target.value)}
/>
{auditSearch && <IconBtn label="Clear" onClick={() => setAuditSearch('')}><Icon.Close /></IconBtn>}
</div>
<select
className="field__input"
style={{ width: '180px', padding: '0 8px', height: '32px', fontSize: '12.5px' }}
value={eventFilter}
onChange={e => setEventFilter(e.target.value)}
>
{EVENT_TYPES.map(t => <option key={t.value} value={t.value}>{t.label}</option>)}
</select>
</div>
</div>
<div className="audit__filter">
<FilterChip on={filter==='all'} onClick={() => setFilter('all')}>All</FilterChip>
<FilterChip on={filter==='system'} onClick={() => setFilter('system')}>System</FilterChip>
<div className="audit__filter" style={{ alignSelf: 'flex-end' }}>
<FilterChip on={actorFilter==='all'} onClick={() => setActorFilter('all')}>All actors</FilterChip>
<FilterChip on={actorFilter==='system'} onClick={() => setActorFilter('system')}>System</FilterChip>
{dbUsers.map(u => (
<FilterChip key={u.id} on={filter===u.id} onClick={() => setFilter(u.id)}>
<FilterChip key={u.id} on={actorFilter===u.id} onClick={() => setActorFilter(u.id)}>
<Avatar user={u} size={16} /> {u.name}
</FilterChip>
))}