2022-09-14 17:31:38 +00:00
|
|
|
import Head from 'next/head';
|
2022-09-09 18:50:28 +00:00
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import useSWR from 'swr';
|
|
|
|
import useUser from '../../lib/hooks/useUser';
|
|
|
|
import conditionalClass from '../../lib/utils/conditional-class';
|
|
|
|
import { Button } from '../common/Button/Button';
|
|
|
|
import { Container } from '../common/Container/Container';
|
|
|
|
import { Header } from '../common/Header/Header';
|
|
|
|
import { Paginator } from '../common/Paginator/Paginator';
|
|
|
|
import styles from './AuditPage.module.scss';
|
|
|
|
|
|
|
|
export const AuditLogDetail = ({ entry }: { entry: any }) => {
|
|
|
|
const [revealed, setRevealed] = useState(false);
|
|
|
|
return (
|
|
|
|
<div className={styles.detail}>
|
|
|
|
<span className={[styles.data, styles.ip_address].join(' ')}>
|
|
|
|
From IP: {entry.actor_ip}
|
|
|
|
</span>
|
|
|
|
{entry.location && (
|
|
|
|
<span className={[styles.data, styles.location].join(' ')}>
|
|
|
|
Approx. location: {entry.location.city}, {entry.location.country} (
|
|
|
|
{entry.location.ll.join(', ')})
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
{entry.actor_ua && (
|
|
|
|
<span className={[styles.data, styles.user_agent].join(' ')}>
|
|
|
|
User Agent: {entry.actor_ua}
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
{entry.user_agent && (
|
|
|
|
<span className={[styles.data, styles.user_agent_info].join(' ')}>
|
|
|
|
Browser: {entry.user_agent.browser} {entry.user_agent.version}
|
|
|
|
{' on '}
|
|
|
|
{entry.user_agent.platform} ({entry.user_agent.os})
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
{entry.content && (
|
|
|
|
<span className={[styles.data, styles.content].join(' ')}>
|
|
|
|
Log details:{' '}
|
|
|
|
{revealed ? (
|
|
|
|
entry.content
|
|
|
|
) : (
|
|
|
|
<Button onClick={() => setRevealed(true)} variant="link">
|
|
|
|
Reveal potentially sensitive information
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const AuditLog = ({ entry }: { entry: any }) => {
|
|
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className={[
|
|
|
|
styles.log,
|
|
|
|
conditionalClass(entry.flagged, styles.flagged),
|
|
|
|
].join(' ')}
|
|
|
|
>
|
|
|
|
<div className={styles.head}>
|
|
|
|
<span className={styles.action}>{entry.action}</span>
|
|
|
|
<span className={styles.timestamp}>
|
|
|
|
{new Date(entry.created_at).toString()}
|
|
|
|
</span>
|
|
|
|
<span className={styles.user}>{entry.actor?.username}</span>
|
|
|
|
<Button onClick={() => setExpanded(!expanded)} variant="link">
|
|
|
|
{expanded ? 'Hide' : 'Show'}
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
{expanded && <AuditLogDetail entry={entry} />}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const AuditLogs = ({}) => {
|
|
|
|
const [searchParams, setSearchParams] = useState(new URLSearchParams({}));
|
|
|
|
const [types, setTypes] = useState<boolean[]>([]);
|
|
|
|
const [pageIndex, setPageIndex] = useState(1);
|
|
|
|
const { data } = useSWR(
|
2022-09-10 09:54:45 +00:00
|
|
|
`/api/admin/audit?page=${pageIndex}&pageSize=26&${searchParams.toString()}`
|
2022-09-09 18:50:28 +00:00
|
|
|
);
|
|
|
|
const filter = useSWR<string[]>('/api/admin/audit/filter');
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!filter?.data || types.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-09-10 06:29:17 +00:00
|
|
|
setTypes(
|
|
|
|
Array.from<never, boolean>(
|
|
|
|
{ length: filter.data.length },
|
|
|
|
(_, k) => k > 0
|
|
|
|
)
|
|
|
|
);
|
2022-09-09 18:50:28 +00:00
|
|
|
}, [types, filter.data]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!filter?.data || !types.length) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const urlparams = new URLSearchParams();
|
|
|
|
urlparams.set(
|
|
|
|
'actions',
|
|
|
|
filter.data
|
|
|
|
.reduce<string[]>((array, current, index) => {
|
|
|
|
return [...array, types[index] ? current : ''];
|
|
|
|
}, [])
|
|
|
|
.filter((item) => item)
|
|
|
|
.join(',')
|
|
|
|
);
|
|
|
|
|
|
|
|
setSearchParams(urlparams);
|
|
|
|
}, [filter.data, types]);
|
|
|
|
|
2022-09-10 06:29:17 +00:00
|
|
|
if (!data || !filter.data || !types.length) {
|
2022-09-09 18:50:28 +00:00
|
|
|
return <></>;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={styles.wrapper}>
|
|
|
|
<div className={styles.filters}>
|
|
|
|
<div className={styles.boxes}>
|
|
|
|
{filter.data?.map((item: string, index: number) => (
|
|
|
|
<div className={styles.box} key={item}>
|
|
|
|
<input
|
|
|
|
type="checkbox"
|
|
|
|
id={'chbx' + item}
|
|
|
|
checked={types[index]}
|
|
|
|
onChange={(e) => {
|
|
|
|
const newTypes = types.slice();
|
|
|
|
newTypes[index] = e.target.checked;
|
|
|
|
setTypes(newTypes);
|
|
|
|
}}
|
|
|
|
/>{' '}
|
|
|
|
<label htmlFor={'chbx' + item}>{item}</label>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className={styles.logs}>
|
|
|
|
<div className={styles.log}>
|
|
|
|
<div className={[styles.head, styles.header].join(' ')}>
|
|
|
|
<span className={styles.action}>Action</span>
|
|
|
|
<span className={styles.timestamp}>Time</span>
|
|
|
|
<span className={styles.user}>Actor</span>
|
|
|
|
<span className={styles.actions}>Actions</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{data.list.map((entry: any) => (
|
|
|
|
<AuditLog entry={entry} key={entry.id} />
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Paginator
|
|
|
|
setPage={setPageIndex}
|
|
|
|
pagination={data.pagination}
|
|
|
|
></Paginator>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export const AuditPage = () => {
|
|
|
|
const { user } = useUser({ redirectTo: '/login' });
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2022-09-14 17:31:38 +00:00
|
|
|
<Head>
|
|
|
|
<title>Audit logs | Icy Network Administration</title>
|
|
|
|
</Head>
|
2022-09-09 18:50:28 +00:00
|
|
|
<Header user={user}></Header>
|
|
|
|
<Container>
|
|
|
|
<h1>Audit logs</h1>
|
|
|
|
<AuditLogs />
|
|
|
|
</Container>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|